考虑以下场景:

我在自己的Git repo中开发了一个小型实验项目a。它现在已经成熟,我希望A成为更大的项目B的一部分,该项目有自己的大仓库。现在我想将A添加为B的子目录。

我如何将A合并为B,而不丢失任何方面的历史?


当前回答

如果您想将来自存储库B分支的文件放在存储库a的子树中,并保留历史记录,请继续阅读。(在下面的示例中,我假设我们希望回购协议B的主分支合并为回购协议A的主分支。)

在回购协议A中,首先执行以下操作以使回购协议B可用:

git remote add B ../B # Add repo B as a new remote.
git fetch B

现在我们在回购a中创建了一个全新的分支(只有一个提交),我们称之为new_b_root。生成的提交将包含在repo B的主分支的第一次提交中提交的文件,但这些文件放在名为path/to/B-files/的子目录中。

git checkout --orphan new_b_root master
git rm -rf . # Remove all files.
git cherry-pick -n `git rev-list --max-parents=0 B/master`
mkdir -p path/to/b-files
git mv README path/to/b-files/
git commit --date="$(git log --format='%ai' $(git rev-list --max-parents=0 B/master))"

解释:checkout命令的--孤儿选项从A的主分支检出文件,但不创建任何提交。我们可以选择任何提交,因为接下来我们无论如何都要清除所有文件。然后,在尚未提交(-n)的情况下,我们从B的主分支中选择第一个提交。(cherry pick保留了原始的提交消息,而直接签出似乎无法做到这一点。)然后我们创建一个子树,将所有来自repo B的文件放在那里。然后我们必须将cherry stick中引入的所有文件移动到子树中。在上面的示例中,只有一个README文件可以移动。然后我们提交B-repo根提交,同时,我们还保留原始提交的时间戳。

现在,我们将在新创建的new_B_root上创建一个新的B/master分支。我们称新分支为b:

git checkout -b b B/master
git rebase -s recursive -Xsubtree=path/to/b-files/ new_b_root

现在,我们将b分支合并为A/master:

git checkout master
git merge --allow-unrelated-histories --no-commit b
git commit -m 'Merge repo B into repo A.'

最后,您可以删除B个远程和临时分支:

git remote remove B
git branch -D new_b_root b

最终图形的结构如下:

其他回答

如果您想单独维护项目,子模块方法是很好的。然而,如果您真的想将两个项目合并到同一个存储库中,那么您还有更多的工作要做。

第一件事是使用gitfilter分支将第二个存储库中所有内容的名称重写到您希望它们结束的子目录中。因此,您将使用projb/foo.c和projb/bar.html代替foo.c和bar.html。

然后,您应该能够执行以下操作:

git remote add projb [wherever]
git pull projb

git pull将执行git fetch,然后执行git merge。如果您要拉到的存储库还没有projb/目录,那么应该不会有冲突。

进一步搜索表明,在将gitk合并为git时也做了类似的操作。Junio C Hamano在这里写道:http://www.mail-archive.com/git@vger.kernel.org/msg03395.html

以下是两种可能的解决方案:

子模块

要么将存储库A复制到较大项目B中的单独目录中,要么(也许更好)将存储库B克隆到项目B的子目录中。然后使用git子模块将此存储库设置为存储库B的子模块。

对于松散耦合的存储库来说,这是一个很好的解决方案,存储库a中的开发仍在继续,而开发的主要部分是a中的独立开发。另请参阅GitWiki上的SubmoduleSupport和GitSubmoduleTutorial页面。

子树合并

您可以使用子树合并策略将存储库A合并到项目B的子目录中。Markus Prinz在《子树合并与你》中描述了这一点。

git remote add -f Bproject /path/to/B
git merge -s ours --allow-unrelated-histories --no-commit Bproject/master
git read-tree --prefix=dir-B/ -u Bproject/master
git commit -m "Merge B project as our subdirectory"
git pull -s subtree Bproject master

(选项--Git>=2.9.0需要允许不相关的历史记录。)

或者你可以使用apenwarr(Avery Pennarun)的git子树工具(GitHub上的存储库),例如,在他的博客文章《git子模块的新替代方案:git子树》中宣布了这一点。


我认为在您的情况下(A是大型项目B的一部分),正确的解决方案是使用子树合并。

几天来我一直在尝试做同样的事情,我使用的是git2.7.2。子树不会保留历史。

如果不再使用旧项目,可以使用此方法。

我建议你先在B分支机构工作,然后在该分支机构工作。

以下是没有分支的步骤:

cd B

# You are going to merge A into B, so first move all of B's files into a sub dir
mkdir B

# Move all files to B, till there is nothing in the dir but .git and B
git mv <files> B

git add .

git commit -m "Moving content of project B in preparation for merge from A"


# Now merge A into B
git remote add -f A <A repo url>

git merge A/<branch>

mkdir A

# move all the files into subdir A, excluding .git
git mv <files> A

git commit -m "Moved A into subdir"


# Move B's files back to root    
git mv B/* ./

rm -rf B

git commit -m "Reset B to original state"

git push

如果您现在在分区A中记录任何文件,您将获得完整的历史记录

git log --follow A/<file>

这是帮助我做到这一点的帖子:

http://saintgimp.org/2013/01/22/merging-two-git-repositories-into-one-repository-without-losing-file-history/

当您希望在一次提交中合并三个或更多项目时,请执行其他答案中所述的步骤(远程添加-f,合并)。然后,(软)将索引重置为旧头(没有合并)。添加所有文件(git-Add-A)并提交它们(消息“将项目A、B、C和D合并到一个项目中”)。这现在是master的提交id。

现在,使用以下内容创建.git/info/places:

<commit-id of master> <list of commit ids of all parents>

运行gitfilter分支--head^。。头头^2.头头^3.头。如果你有三个以上的分支,就加上同样多的头。。当你有树枝的时候,就把头伸过去。要更新标记,请追加--tag-namefilter cat。不要总是添加,因为这可能会导致某些提交的重写。有关详细信息,请参阅过滤器分支的手册页,搜索“移植物”。

现在,你的最后一次承诺与正确的父母相关。

除了使用远程add->fetch->merge策略的所有答案之外:如果您想保留其他存储库中的标记,但不想将它们全部泄漏到一个公共命名空间中(可能会发生冲突),您可能需要稍微更改fetch命令:

git fetch --no-tags other_repo
git fetch --no-tags other_repo 'refs/tags/*:refs/tags/other_repo/*'

第一个命令像往常一样获取所有分支,但省略了附加到提交的标记,第二个命令也省略了通常的标记获取机制(git help fetch了解更多信息),并使用git的refspec功能获取将它们从X映射到other_repo/X的所有标记。

引用(分支、标记)只是git中的文件,您可以使用目录来命名。上面的两个命令将保留第一个存储库中的标记,而另一个存储库的标记将以other_repo作为前缀/

操作完成后,最好移除另一个遥控器,这样您就不会意外地以正常方式获取标签并造成混乱。