我们的Git存储库最初是一个大型SVN存储库的一部分,其中每个项目都有自己的树,如下所示:

project1/branches
        /tags
        /trunk
project2/branches
        /tags
        /trunk

显然,使用svn mv将文件从一个文件移动到另一个文件非常容易。但是在Git中,每个项目都在它自己的存储库中,今天我被要求将一个子目录从project2移动到project1。我是这样做的:

$ git clone project2 
$ cd project2
$ git filter-branch --subdirectory-filter deeply/buried/java/source/directory/A -- --all
$ git remote rm origin  # so I don't accidentally overwrite the repo ;-)
$ mkdir -p deeply/buried/different/java/source/directory/B
$ for f in *.java; do 
>  git mv $f deeply/buried/different/java/source/directory/B
>  done
$ git commit -m "moved files to new subdirectory"
$ cd ..
$
$ git clone project1
$ cd project1
$ git remote add p2 ../project2
$ git fetch p2
$ git branch p2 remotes/p2/master
$ git merge p2 # --allow-unrelated-histories for git 2.9+
$ git remote rm p2
$ git push

但这似乎相当复杂。有没有更好的方法来做这类事情呢?还是我采取了正确的方法?

注意,这涉及到将历史合并到现有的存储库中,而不是简单地从另一个存储库的一部分创建一个新的独立存储库(如前面的问题中所述)。


当前回答

Git子树直观地工作,甚至保存历史。

使用示例: 将git repo添加为子目录:

git subtree add --prefix foo https://github.com/git/git.git master

解释:

#├── repo_bar
#│   ├── bar.txt
#└── repo_foo
#    └── foo.txt

cd repo_bar
git subtree add --prefix foo ../repo_foo master

#├── repo_bar
#│   ├── bar.txt
#│   └── foo
#│       └── foo.txt
#└── repo_foo
#    └── foo.txt

其他回答

试试这个

cd repo1

这将删除除上述目录之外的所有目录,仅为这些目录保留历史记录

git filter-branch --index-filter 'git rm --ignore-unmatch --cached -qr -- . && git reset -q $GIT_COMMIT -- dir1/ dir2/ dir3/ ' --prune-empty -- --all

现在,您可以在git远程中添加新的repo并将其推到该位置

git remote remove origin <old-repo>
git remote add origin <new-repo>
git push origin <current-branch>

添加-f来覆盖

保留目录名

子目录过滤器(或更短的命令git子树)工作得很好,但对我来说并不管用,因为他们从提交信息中删除了目录名。在我的场景中,我只想将一个存储库的部分合并到另一个存储库中,并保留历史记录WITH完整的路径名。

我的解决方案是使用树筛选器,简单地从源存储库的临时克隆中删除不需要的文件和目录,然后通过5个简单步骤将该克隆拉入目标存储库。

# 1. clone the source
git clone ssh://<user>@<source-repo url>
cd <source-repo>
# 2. remove the stuff we want to exclude
git filter-branch --tree-filter "rm -rf <files to exclude>" --prune-empty HEAD
# 3. move to target repo and create a merge branch (for safety)
cd <path to target-repo>
git checkout -b <merge branch>
# 4. Add the source-repo as remote 
git remote add source-repo <path to source-repo>
# 5. fetch it
git pull source-repo master
# 6. check that you got it right (better safe than sorry, right?)
gitk

我发现Ross Hendrickson的博客很有用。这是一种非常简单的方法,您可以创建应用于新回购的补丁。更多细节请参见链接页面。

它只包含三个步骤(复制自博客):

# Setup a directory to hold the patches
mkdir <patch-directory>

# Create the patches
git format-patch -o <patch-directory> --root /path/to/copy

# Apply the patches in the new repo using a 3 way merge in case of conflicts
# (merges from the other repo are not turned into patches). 
# The 3way can be omitted.
git am --3way <patch-directory>/*.patch

唯一的问题是我不能一次性应用所有补丁

git am --3way <patch-directory>/*.patch

在Windows下,我得到了一个InvalidArgument错误。所以我不得不一个接一个地打补丁。

对于类似的问题(尽管只针对特定存储库中的某些文件),这个脚本被证明是非常有用的:git-import

简短的版本是,它从现有的存储库中创建给定文件或目录($object)的补丁文件:

cd old_repo
git format-patch --thread -o "$temp" --root -- "$object"

然后应用到一个新的存储库:

cd new_repo
git am "$temp"/*.patch 

详情请查阅:

文档化的来源 git format-patch git是

下面的bash函数可以使用这种有用的方法。下面是一个用法示例:

gitcp <Repo1_basedir> <path_inside_repo1> <Repo2_basedir> . gitcp <Repo1_basedir> <path_inside_repo1>

gitcp ()
{
    fromdir="$1";
    frompath="$2";
    to="$3";
    echo "Moving git files from "$fromdir" at "$frompath" to "$to" ..";
    tmpdir=/tmp/gittmp;
    cd "$fromdir";
    git format-patch --thread -o $tmpdir --root -- "$frompath";
    cd "$to";
    git am $tmpdir/*.patch
}

在尝试了将文件或文件夹从一个Git存储库移动到另一个存储库的各种方法后,下面概述了唯一可靠的方法。

它包括克隆要从中移动文件或文件夹的存储库,将该文件或文件夹移动到根目录,重写Git历史记录,克隆目标存储库,并将具有历史记录的文件或文件夹直接拉到目标存储库中。

阶段一

Make a copy of repository A as the following steps make major changes to this copy which you should not push! git clone --branch <branch> --origin origin --progress \ -v <git repository A url> # eg. git clone --branch master --origin origin --progress \ # -v https://username@giturl/scm/projects/myprojects.git # (assuming myprojects is the repository you want to copy from) cd into it cd <git repository A directory> # eg. cd /c/Working/GIT/myprojects Delete the link to the original repository to avoid accidentally making any remote changes (eg. by pushing) git remote rm origin Go through your history and files, removing anything that is not in directory 1. The result is the contents of directory 1 spewed out into to the base of repository A. git filter-branch --subdirectory-filter <directory> -- --all # eg. git filter-branch --subdirectory-filter subfolder1/subfolder2/FOLDER_TO_KEEP -- --all For single file move only: go through what's left and remove everything except the desired file. (You may need to delete files you don't want with the same name and commit.) git filter-branch -f --index-filter \ 'git ls-files -s | grep $'\t'FILE_TO_KEEP$ | GIT_INDEX_FILE=$GIT_INDEX_FILE.new \ git update-index --index-info && \ mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE || echo "Nothing to do"' --prune-empty -- --all # eg. FILE_TO_KEEP = pom.xml to keep only the pom.xml file from FOLDER_TO_KEEP

第二阶段

清理步骤 Git重置——很难 清理步骤 Git gc -aggressive 清理步骤 git修剪

你可能想要将这些文件导入存储库B中的一个目录,而不是根目录:

创建那个目录 Mkdir <基本目录>;mkdir FOLDER_TO_KEEP 将文件移动到该目录 Git mv * <基本目录>git mv *文件夹to_keep 将文件添加到该目录 Git添加。 提交您的更改,我们准备将这些文件合并到 新的存储库 git提交

第三阶段

Make a copy of repository B if you don’t have one already git clone <git repository B url> # eg. git clone https://username@giturl/scm/projects/FOLDER_TO_KEEP.git (assuming FOLDER_TO_KEEP is the name of the new repository you are copying to) cd into it cd <git repository B directory> # eg. cd /c/Working/GIT/FOLDER_TO_KEEP Create a remote connection to repository A as a branch in repository B git remote add repo-A-branch <git repository A directory> # (repo-A-branch can be anything - it's just an arbitrary name) # eg. git remote add repo-A-branch /c/Working/GIT/myprojects Pull from this branch (containing only the directory you want to move) into repository B. git pull repo-A-branch master --allow-unrelated-histories The pull copies both files and history. Note: You can use a merge instead of a pull, but pull works better. Finally, you probably want to clean up a bit by removing the remote connection to repository A git remote rm repo-A-branch Push and you’re all set. git push