我们的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

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

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


当前回答

如果有关文件的路径在两个repo中是相同的,并且您希望只带来一个文件或一小组相关文件,一个简单的方法是使用git进行选择。

第一步是使用git fetch <remote-url>将从另一个repo提交到您自己的本地repo。这将使FETCH_HEAD指向从另一个repo提交的头;如果你想在你完成了其他的取回之后保留对该提交的引用,你可以使用git tag other-head FETCH_HEAD来标记它。

You will then need to create an initial commit for that file (if it doesn't exist) or a commit to bring the file to a state that can be patched with the first commit from the other repo you want to bring in. You may be able to do this with a git cherry-pick <commit-0> if commit-0 introduced the files you want, or you may need to construct the commit 'by hand'. Add -n to the cherry-pick options if you need to modify the initial commit to, e.g., drop files from that commit you don't want to bring in.

在此之后,您可以继续精选后续提交,在必要时再次使用-n。在最简单的情况下(所有提交都是你想要的,并且干净地应用),你可以在cherry-pick命令行上给出完整的提交列表:git cherry-pick <commit-1> <commit-2> <commit-3> ....

其他回答

如果有关文件的路径在两个repo中是相同的,并且您希望只带来一个文件或一小组相关文件,一个简单的方法是使用git进行选择。

第一步是使用git fetch <remote-url>将从另一个repo提交到您自己的本地repo。这将使FETCH_HEAD指向从另一个repo提交的头;如果你想在你完成了其他的取回之后保留对该提交的引用,你可以使用git tag other-head FETCH_HEAD来标记它。

You will then need to create an initial commit for that file (if it doesn't exist) or a commit to bring the file to a state that can be patched with the first commit from the other repo you want to bring in. You may be able to do this with a git cherry-pick <commit-0> if commit-0 introduced the files you want, or you may need to construct the commit 'by hand'. Add -n to the cherry-pick options if you need to modify the initial commit to, e.g., drop files from that commit you don't want to bring in.

在此之后,您可以继续精选后续提交,在必要时再次使用-n。在最简单的情况下(所有提交都是你想要的,并且干净地应用),你可以在cherry-pick命令行上给出完整的提交列表:git cherry-pick <commit-1> <commit-2> <commit-3> ....

在我的例子中,我不需要保存我正在迁移的回购,也不需要保存任何以前的历史记录。我用不同的遥控器,截取了同一根树枝的补丁

#Source directory
git remote rm origin
#Target directory
git remote add branch-name-from-old-repo ../source_directory

在这两个步骤中,我能够让另一个回购的分支出现在同一个回购中。

最后,我设置这个分支(我从另一个回购导入)遵循目标回购的主线(所以我可以准确地区分它们)

git br --set-upstream-to=origin/mainline

现在它的行为就好像它只是我在同一次回购中推的另一个分支。

如果你的历史记录是正常的,你可以将提交作为补丁取出,并将它们应用到新的存储库中:

cd repository
git log --pretty=email --patch-with-stat --reverse --full-index --binary -m --first-parent -- path/to/file_or_folder > patch
cd ../another_repository
git am --committer-date-is-author-date < ../repository/patch 

或者在一行里

git log --pretty=email --patch-with-stat --reverse --full-index --binary -m --first-parent -- path/to/file_or_folder | (cd /path/to/new_repository && git am --committer-date-is-author-date)

提示:如果源项目子目录中的提交应该被提取到一个新的存储库根目录,git am可以被赋予-p2这样的参数来从补丁中删除额外的目录。

(摘自Exherbo的文件)

对于类似的问题(尽管只针对特定存储库中的某些文件),这个脚本被证明是非常有用的: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 Stash迁移到GitLab的方法。

将旧的存储库克隆到本地。

git clone --bare <STASH-URL>

在GitLab中创建一个空存储库。

git push --mirror <GitLab-URL>

当我们将代码从stash迁移到GitLab时,我执行了上述操作,效果非常好。