tl;博士
两个分支之间的线性历史只能通过重新定位一个分支在另一个分支之上在合并之前来完成。如果你只是合并两个已经分歧的分支,Git 将通过创建一个 merge commit 来加入这两条历史记录。
与合并提交合并
在 Git 中,提交通常包含对一个父级的引用。 merge commit 是一种特殊类型的提交,它引用两个或多个父级。
在您的示例中,合并提交 C5 有两个父级:
-
第一个父节点是
C2,即分支上的提交
另一个分支合并到,即branch1
-
第二个父节点是
C4,即合并分支上的提交,即branch2。
当您在 branch1 上执行 git log 时,Git 将遵循两条历史记录向您显示:
C1--C2--C5 <- branch1
/
C3--C4 <- branch2
如果你对branch2 执行git log,历史行将被交换:
C3--C4 <- branch2
/
C1--C2--C5 <- branch1
在没有合并提交的情况下合并
两个分支之间的提交似乎在同一条历史记录中的场景——不涉及合并提交——是 rebase 的结果。
在branch1 之上重新定位branch2 是conceptually different 合并两个分支的操作。
合并完成时:
git checkout branch1
git merge branch2
变基是通过以下方式完成的:
git checkout branch2
git rebase branch1
这基本上意味着:
找到所有可以从branch2 访问但不能从branch1 (在本例中为C3 和C4)的提交——并将它们应用到@ 中的最新提交之上987654346@.
所以最终的历史会是这样的:
C1--C2--C3--C4 <- branch2
^
branch1
请注意,branch1 仍然指向 C2——与变基之前相同的提交。由于两个分支之间的历史记录现在在同一行,因此将它们合并为:
git checkout branch1
git merge branch2
won't create a merge commit。相反,Git 将简单地将branch1 向前移动,使其指向与branch2 相同的提交。此操作称为fast-forward:
换一种说法,当您尝试将一个提交与
可以通过遵循第一次提交的历史来达到的提交,
Git 通过向前移动指针来简化事情,因为有
没有分歧的工作可以合并在一起——这被称为“快进”。
撤消合并
撤消合并的方式将根据合并是否通过合并提交而有所不同。
如果有合并提交,您可以通过简单地将branch1 指向合并提交的第一个父级来撤消合并:
git checkout branch1 # branch1 points at the merge commit C5
git reset --hard branch1^ # branch1 now points at C2
如果合并是在 rebase 之后完成的,事情就有点棘手了。您基本上需要恢复 branch1 以指向它在合并之前所做的提交。如果合并是您最近执行的操作,您可以使用special reference ORIG_HEAD:
git reset --hard ORIG_HEAD
否则你将不得不求助于reflog:
git checkout branch1
git reflog # Find the commit that branch1 pointed to before the merge
git reset --hard HEAD@{n} # Move branch1 to point to that entry in the reflog