合并两个分支和Git历史

时间:2015-10-13 10:19:02

标签: git merge git-merge

在合并两个分支时,请有人告诉我Git在历史方面做了什么。

如果我有两个分支都在积极开发并且都包含提交,这与下面的时间轴类似:

Branch #1: ----(branch)----C1----------C2-------(merge)------C5
                  \                             /
                   \                           /
                    \                         /
Branch #2:           -----------C3----------C4

一旦两个分支合并,分支1的历史记录如何查看C5(提交#5)?我的印象是Git将合并所有历史记录给我以下内容:

Branch #1: ----------------C1----C3----C2----C4--------------C5

这是正确的理解吗?

如果是这样,在紧急情况下,如何撤消合并,因为分支#2的所有历史记录肯定会与分支#1的历史纠缠在一起。

5 个答案:

答案 0 :(得分:6)

TL;博士

在两个分支之间具有线性历史记录只能通过合并之前在另一个之上重新设置一个分支来完成。如果您只合并两个已分歧的分支,Git将通过创建合并提交来加入两行历史记录。

与合并提交合并

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

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

  1. 第一个父C2,即分支上的提交 其他分支合并到的地方,即branch1
  2. 第二个父C4,即合并的分支上的提交,即branch2
  3. 当您git log branch1时,Git会按照以下两条历史记录显示:

    C1--C2--C5 <- branch1
            /
      C3--C4 <- branch2
    

    如果您在git logbranch2,则会交换历史记录:

         C3--C4 <- branch2
             /
    C1--C2--C5 <- branch1
    

    合并没有合并提交

    两个分支之间的提交似乎在同一行历史记录中的情况 - 没有涉及合并提交 - 是 rebase 的结果。

    branch2之上重新branch1conceptually different合并两个分支的操作​​。

    合并完成时:

    git checkout branch1
    git merge branch2
    

    重新定位完成:

    git checkout branch2
    git rebase branch1
    

    基本上意味着:

      

    查找branch2 not branch1可以访问的所有提交 - 在本例中为C3C4 - 并应用它们在branch1的最新提交之上。

    所以最终的历史将如下所示:

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

    请注意branch1仍然指向C2 - 与rebase之前的提交相同。由于两个分支之间的历史记录现在是在同一行,因此将它们合并为:

    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
    

答案 1 :(得分:2)

合并实际上不会创建您描述的内容,合并将创建具有两个父级提交的提交。在提交内部,分支#2的更改将在主分支上重放。这解释了会发生什么:https://git-scm.com/docs/git-merge

您所描述的是当您将分支#2重新分支到分支#1:https://git-scm.com/docs/git-rebase时会发生什么(某种程度)。然而,你会得到C1,C2,C3,C4,C5和rebase。变基不是按时间排序的。当您将分支#2重新分支到分支#1时,#2中的每个提交都会应用于#1。

撤销rebase或合并就像在提交的情况下将头重置为合并提交之前一样简单,并在开始重新定位之前重置为#1分支中的最后一次提交。

最后,我当然不知道你的确切设置,但如果在两个分支中都有很多活跃的开发,我会经常将#2合并到#1中,因为只偶尔这样做可能最终导致很多冲突。

答案 2 :(得分: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中使用的概念。

答案 3 :(得分:0)

要撤消合并,您可以使用:git checkout banrch#1; git reset --hard c2-hash

答案 4 :(得分:0)

  

这是正确的理解吗?

合并后,分支#1的历史记录看起来就像您在问题中发布的那样,尽管基础merge mechanism并不那么简单。

将branch-2合并到branch-1将“杂乱”(不确定这是否是正确的词)branch-1的历史。另一种方法是进行squash合并,它合并了branch-2中的所有提交(不在branch-1中),并将它们合并为branch-1中的单个提交。这样,branch-1的历史记录保持清晰,如果你需要从branch-2中退出更改,你只需要恢复一次提交(压缩的合并提交)。

注意:关于使用壁球合并的观点在人与人之间以及团队之间存在巨大差异。因此,在实际在生产存储库中使用它之前,请先阅读它。在某些情况下,更清晰的分支历史可能更好,而在其他情况下,更精细的历史(由非压缩合并生成)可能是首选。一些相关的文章和链接: