考虑以下场景:

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

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


当前回答

git子树很好,但它可能不是您想要的。

例如,如果projectA是在B中创建的目录,在git子树之后,

git log projectA

仅列出一个提交:合并。合并项目的提交针对不同的路径,因此不会显示。

格雷格·休吉尔(Greg Hewgill)的答案最接近,但实际上并没有说明如何重写路径。


解决方案出奇地简单。

(1) 在A中,

PREFIX=projectA #adjust this

git filter-branch --index-filter '
    git ls-files -s |
    sed "s,\t,&'"$PREFIX"'/," |
    GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info &&
    mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE
' HEAD

注意:这将重写历史;你可能想先备份a。

注意Bene:如果在文件名或路径中使用非ascii字符(或白色字符),则必须修改sed命令中的替代脚本。在这种情况下,“ls files-s”生成的记录中的文件位置以引号开头。

(2) 然后在B中,运行

git pull path/to/A

瞧!在B中有一个projectA目录。如果运行git-log-projectA,您将看到a中的所有提交。


在我的例子中,我需要两个子目录,projectA和projectB。在这种情况下,我也执行了步骤(1)到B。

其他回答

除了使用远程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作为前缀/

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

https://github.com/hraban/tomono作为基于脚本的解决方案的另一个例子。

我不是作者,但使用了它,它完成了任务。

一个积极的方面是,你将所有分支机构和所有历史记录纳入最终回购。对于我的repo(repo中没有重复的文件夹-实际上,它们来自tfs2git迁移),没有冲突,一切都是自动运行的。

它主要用于(参见名称)创建monoreos。

对于Windows用户:gitbash可以执行.sh文件。它带有标准的git安装。

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

第一件事是使用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

如果您试图简单地将两个存储库粘合在一起,那么子模块和子树合并是错误的工具,因为它们不能保留所有的文件历史记录(正如人们在其他答案中所指出的)。请参阅此处的答案,了解简单而正确的方法。

我在使用merge时不断丢失历史记录,所以我最终使用了rebase,因为在我的情况下,两个存储库的不同程度足以避免每次提交时合并:

git clone git@gitorious/projA.git projA
git clone git@gitorious/projB.git projB

cd projB
git remote add projA ../projA/
git fetch projA 
git rebase projA/master HEAD

=>解决冲突,然后根据需要继续。。。

git rebase --continue

这样做会导致一个项目具有来自projA的所有提交,然后是来自projB的提交