合并两个分支和Git历史记录

7
请问在将两个分支合并时,Git会如何处理历史记录?
如果我有两个正在积极开发的分支,它们都包含提交记录,类似于以下时间轴:
Branch #1: ----(branch)----C1----------C2-------(merge)------C5
                  \                             /
                   \                           /
                    \                         /
Branch #2:           -----------C3----------C4

一旦两个分支合并,分支1中C5(提交#5)的历史记录会如何在C5上呈现?我认为Git将合并所有历史记录,给我以下结果:
Branch #1: ----------------C1----C3----C2----C4--------------C5

这样理解是否正确?

如果是的话,在紧急情况下,我要如何撤销合并呢?因为分支#2的所有历史记录肯定与分支#1的历史记录交织在一起。


Git在合并时通常不会扁平化历史记录,因此在您提供的情况下,通常不会看到线性历史记录...分支1的历史记录将看起来像您第一个代码框中的整个树形结构。 - user456814
可能是回滚到以前的Git提交的重复问题。 - user456814
可能是撤销Git合并请求的重复问题。 - user456814
5个回答

8

简而言之

如果两个分支之间有线性历史,只能在合并之前将一个分支rebase到另一个分支的顶部。如果您只是合并了两个分支,那么Git将通过创建merge commit来连接这两行历史记录。

使用合并提交进行合并

在Git中,提交通常保存对一个父提交的引用。merge commit是一种特殊类型的提交,它引用了两个或更多父提交。

在您的示例中,合并提交C5有两个父提交:

  1. 第一个父提交C2,即另一个分支合并到的分支上的提交,即branch1
  2. 第二个父提交C4,即合并的分支上的提交,即branch2
当你在branch1上执行git log时,Git会跟随两条历史记录线路并向你展示这个结果:
C1--C2--C5 <- branch1
        /
  C3--C4 <- branch2

如果你在branch2上执行git log命令,则历史记录的行将会被交换。
     C3--C4 <- branch2
         /
C1--C2--C5 <- branch1

不使用合并提交的合并

在两个分支之间的提交看起来在同一历史线上,没有涉及到合并提交,这是变基的结果。

branch2变基到branch1之上是一种操作,与合并这两个分支的操作 概念上 不同

虽然合并是通过以下方式完成的:

git checkout branch1
git merge branch2

重新定位使用以下命令完成:
git checkout branch2
git rebase branch1

这基本意味着:

查找所有从branch2可达但从branch1不可达的提交记录 - 在此示例中为C3C4 - 并将它们应用于branch1最新提交的顶部。

因此,最终的历史记录将如下所示:

C1--C2--C3--C4 <- branch2
     ^
     branch1

注意,branch1 仍然指向 C2 - 与重新贴基前相同的提交。由于两个分支之间的历史现在处于同一条线上,因此可以使用以下命令将它们合并:
git checkout branch1
git merge branch2

不会创建合并提交。相反,Git将简单地将branch1向前移动,使其指向与branch2相同的提交。这个操作被称为快进

换句话说,当您尝试将一个提交与可以通过跟随第一个提交的历史记录到达的提交合并时,Git通过向前移动指针来简化事情,因为没有需要合并的分歧工作 - 这称为“快进”。

撤消合并

撤消合并的方式取决于合并是否是通过合并提交完成的。

如果存在合并提交,则可以通过将branch1移动到合并提交的第一个父提交来撤消合并:

git checkout branch1       # branch1 points at the merge commit C5
git reset --hard branch1^  # branch1 now points at C2

如果在变基之后进行了合并,情况会有些棘手。你基本上需要将 branch1 恢复到合并之前的提交点。如果合并是你最近执行的操作,你可以使用 特殊引用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

2
合并实际上并不会创建您描述的内容,合并将创建一个具有两个父提交的提交。在该提交中,分支#2的更改将在您的主分支上重播。以下链接解释了发生的情况:https://git-scm.com/docs/git-merge 当您将分支#2重新安排到分支#1时,您描述的情况(某种程度上)会发生:https://git-scm.com/docs/git-rebase。但是,使用rebase将得到C1,C2,C3,C4,C5。当重新安排时,提交不按时间顺序排序。当您将分支#2重新安排到分支#1时,每个#2分支中的提交都会应用到该点的#1。
撤消rebase或合并与在合并提交的情况下将head重置为之前的状态以及在开始rebasing之前将其重置为#1分支中的最后一个提交一样简单。
最后,我当然不知道您的确切设置,但如果两个分支都有大量活动开发,则经常将#2合并到#1中,因为偶尔这样做可能会导致许多冲突。

1
首先,您需要了解git中的分支只是一个标签。在您的示例中,您的第1个分支将设置为C1,然后是C2,然后是C5(合并后),而您的第2个分支将设置为C3,然后是C4。
然后,如果我理解正确,当git说“提交属于X分支”时,它意味着该提交是当前由分支标记的提交或该提交的祖先(直接或间接)。因此,在合并之后,所有从C1到C的提交都属于分支#1。
最后,要撤消合并,您有两种选择:
  • 如果您的合并尚未被其他开发人员拉取,您可以随时重置分支#1。 重置实际上只是将分支标签移动到另一个提交。 在您的情况下,需要在切换到分支#1之后使用git reset C2来完成。 如果您有要摆脱的本地修改,则可以执行git reset --hard C2。 您必须小心,因为重置是Git中具有丢失工作能力的少数命令之一。

  • 如果您的合并已经被共享,您仍然可以执行还原操作。 还原是一个新提交,其中包含擦除合并修改的修改。 这不是完美的解决方案,因为历史记录将被保留。

有关这些操作的更多信息,请单击此处:https://www.atlassian.com/git/tutorials/undoing-changes

我还建议阅读这本电子书:https://git-scm.com/book/en/v2,至少前三章,它们可以很好地理解Git中使用的概念。


0

要撤销合并,您可以使用以下命令:git checkout branch#1; git reset --hard c2-hash


0
这是正确的理解吗?
合并后,分支#1的历史记录将如您在问题中发布的那样,尽管底层的合并机制并不简单。
将分支2合并到分支1将“混乱”(不确定这是否是正确的词)分支1的历史记录。另一种选择是进行squash合并,它将分支2中的所有提交(不在分支1中的提交)组合为分支1中的单个提交。这样,分支1的历史记录保持干净,如果您需要撤消分支2中的更改,只需还原一个提交(squashed合并提交)即可。
注意:使用squash合并的观点因人而异,团队之间也有所不同。因此,在实际使用它之前最好先阅读相关资料。在某些情况下,更清晰的分支历史可能更可取,而在其他情况下,更详细的历史记录(由非squash合并生成)可能更可取。一些相关文章和链接:

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