合并两个分支和 Git 历史

Merging two branches and Git history

请有人告诉我合并两个分支时 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 的历史交织在一起。

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

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

您描述的是当您将分支#2 变基到分支#1 时发生的(某种)情况:https://git-scm.com/docs/git-rebase。但是,您将获得带有变基的 C1、C2、C3、C4、C5。变基时提交不按时间排序。当您将 branch#2 变基到 branch#1 时,#2 中的每个提交都会在此时应用于#1。

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

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

tl;博士

两个分支之间的线性历史只能通过变基一个分支在另一个之前合并来完成。如果你只是合并两个有分歧的分支,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

没有合并提交的合并

两个分支之间的提交似乎在同一条历史记录中的场景——不涉及合并提交——是rebase的结果。

branch1 之上重新设置 branch2conceptually different 合并两个分支的操作​​。

合并完成时:

git checkout branch1
git merge branch2

变基完成:

git checkout branch2
git rebase branch1

这基本上意味着:

Find all the commits that are reachable from branch2 but not from branch1 – in this case C3 and C4 – and apply them on top of the latest commit in branch1.

所以最终的历史将是这样的:

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

请注意,branch1 仍然指向 C2——与变基之前相同的提交。由于两个分支之间的历史现在在同一行,将它们合并为:

git checkout branch1
git merge branch2

. Instead, Git will simply move the branch1 forward so that it points to the same commit as branch2. This operation is called a fast-forward:

To phrase that another way, when you try to merge one commit with a commit that can be reached by following the first commit’s history, Git simplifies things by moving the pointer forward because there is no divergent work to merge together – this is called a “fast-forward.”

撤消合并

撤消合并的方式会有所不同,具体取决于合并是否通过 合并提交 完成。

如果有合并提交,您可以通过简单地将 branch1 移动到指向合并提交的第一个父级来撤消合并:

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

如果合并是在变基之后完成的,事情就有点棘手了。您基本上需要恢复 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

首先你需要了解 git 中的分支只是一个标签。在您的示例中,您的分支 #1 将设置为 C1,然后是 C2,然后是 C5(合并后),而您的分支 #2 将设置为 C3,然后是 C4。

然后,如果我理解正确,当 git 说 "a commit belong to branch X" 时,这意味着此提交是分支标记的当前提交或此提交的直接或间接祖先。所以是的,合并后,所有提交到 C1 的 C 都属于分支#1

最后,要撤消合并,您有两种可能性:

  • 如果您的合并没有被其他开发人员拉取,您可以随时重置您的分支#1。重置实际上只是将分支的标签移动到另一个提交。在您的情况下,它将在 git reset C2 完成对分支 #1 的结帐后完成。如果你有你想摆脱的局部修改,你可以做git reset --hard C2。您必须小心,因为重置是 git 中罕见的命令之一,能够丢失您的工作

  • 如果您的合并已被共享,您仍然可以还原。还原是一个 ew 提交,修改会删除合并的修改。这不是一个完美的解决方案,因为历史将被保留。

有关这些操作的更多信息:https://www.atlassian.com/git/tutorials/undoing-changes

我还建议阅读这本电子书:https://git-scm.com/book/en/v2,至少前三章可以很好地理解 git.

中使用的概念

Is this the correct understanding?

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

将 branch-2 合并到 branch-1 将 'clutter'(不确定这个词是否正确)branch-1 的历史记录。另一种方法是执行 squash 合并,合并 branch-2 中的所有提交(不在 branch-1 中)并将它们合并为 branch-1 中的单个提交。这样,branch-1 的历史保持干净,如果您需要从 branch-2 撤消更改,您只需要还原单个提交(压缩的合并提交)。

注意:对于使用 squash merge 的看法因人而异,因团队而异。因此,在您的生产存储库中实际使用它之前,最好仔细阅读它。在某些情况下,更清晰的分支历史可能更可取,而在其他情况下更详细的历史(由非压缩合并生成)可能更可取。一些相关文章和链接: