如何合并两个没有共同祖先的分支?

38

我在项目进行到一半的时候开始使用Git,在最初的两个提交中只是添加了一些初始设置(.gitignore和.gitattributes),第三个提交M2添加了SVN主干的内容:

I1 -- I2 -- M2 -- N -- .. -- Z

我将SVN历史记录导入了一个名为svn的分支,在该分支中,M1是SVN主干(与M2具有相同的内容,除了.gitignore和.gitattributes):

A -- B -- ... -- K -- L -- M1

Q: 如何最好地合并这两个分支?

我可以将 M1M2 合并成 M3,然后进行变基操作,但我不知道如何删除 I1I2 提交,以及是否可以安全地删除 M3 提交(我发现一些建议保留合并提交,但在这种情况下,M3 不再必要)。

A -- B -- ... -- K -- L -- M1
                             \
                              M3 -- N' -- .. -- Z'
                             /
               I1 -- I2 -- M2 -- N -- .. -- Z

另一种方法是手动挑选N .. Z提交记录并将其合并到svn分支,但我希望避免这种方法。

最优雅的解决方案是将由N .. Z提交引入的更改在svn分支之上重新派生,但我还没有找到两个没有共同祖先的分支所需的语法。

3个回答

34
免责声明: 我只在一个玩具存储库中使用过 "graft points"。但这是一个你可能没有听说过的晦涩特性,_可能_有助于你的情况。

你可以使用 "graft points" 来伪造祖先信息。例如,参见什么是.git/info/grafts?或直接查看关于graft point的git wiki条目

本质上,你需要创建一个文件.git/info/grafts,以欺骗git认为提交M1是提交M2的祖先:

$ cat .git/info/grafts
<your M2 commit hash> <your M1 commit hash>

随后,它看起来像M2是一个空提交,只是将I2M1合并到一个共同的树中。

主要缺点:嫁接点没有被提交,因此它没有被检出,但需要手动添加到存储库的每个本地工作副本中。



更新:使用git replace --graft代替。

如上所述,嫁接点已被取代。运行

git replace --graft <your M2 commit hash> <your M1 commit hash>

创建嫁接点,其存储位置为.git/refs/replace/。尽管默认情况下Git不会获取或推送这些引用,但可以使用以下命令在存储库之间进行同步:

git push origin 'refs/replace/*'
git fetch origin 'refs/replace/*:refs/replace/*'

(StackOverflow:如何在Git中推送'refs / replace'而不推送任何其他引用?)


4
您可以使用“git filter-branch”根据移植点重写历史记录,将移植的先辈关系转换为真正的先辈关系。但这将重写历史记录。 - Jakub Narębski
@Jakub:你能举个使用grafts的filter-branch示例吗?(作为答案以便能够点赞)我可以重写历史。 - alexandrul
如果我没记错,在嫁接文件中,您需要提及当前提交及其所有父提交。因此,在一行中,将包含M2、M1和I2的哈希值——除非您想要摆脱提交I1和I2。如果在嫁接行中只有M2和M1,则看起来像是M2在M1之上提交了I1和I2的内容。 - Esko Luontola
5
值得注意的是,作为伪造共同祖先的一种方式,“graft”已被“git replace”所取代。 - stsquad
@stsquad和git replace实际上已经提交到了代码库。或者至少可以这样做。 - Ivan Danilov

6

最优雅的解决方案是将N到Z提交引入的更改基于svn分支进行变基,但我还没有找到两个没有共同祖先的分支所需的语法。

尝试首先将I1和I2挑选到M1上,然后使用命令git rebase --onto M1' M2 Z(其中M1'是M1-I1-I2分支)。我不确定当没有共同祖先时是否可以使用rebase --onto,但如果不能,则有使用补丁的可能。使用git format-patch生成M2..Z的补丁,然后使用git am将它们应用于M1之上。这里有一些使用经验报告在转换旧的SVN和CVS存储库时使用。


5
我会执行以下步骤:
git checkout M1
git cherry-pick I1
git cherry-pick I2

这将在您的分支中添加 .gitignore 和 .gitattributes,使历史记录更加清晰。

然后只需将新提交设置在该提交之上:

git filter-branch --parent-filter 'if $GIT_COMMIT = $hash_of_N; then printf -- '-p
$hash_of_cherrypicked_I2\n'; else cat; fi'

这样做的缺点是你会重写历史记录。因此,另一种替代方法是创建一个类似于Linux内核脚本并将其放入您的代码库中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接