这里发生了一些事情。首先,了解 Git 中的分支实际上只是粘在特定提交上的“标签”,并且当您提交到分支时会自动移动,这很有用;Git 将使用新的提交哈希创建新的提交,并更新分支/标签以指向新的提交。(你可能会问,这和标签有什么不同呢?一个标签被粘在同一个提交上,当你调用 git commit 时不会得到更新。)
当您创建一个新分支时,所发生的一切就是 Git 创建一个新标签,指向您所在的同一提交。只有在签出此新分支时创建新提交时,您才会看到新分支与另一个分支不同。
当您再次开始合并分支时,真正的混乱开始了,这主要是由于 Git 称之为“快进合并”的奇怪事情,默认情况下它会这样做。让我们以您的第二个示例为例,假设您的 master 和 develop 位于最初的 origin/master 和 origin/develop 所在的位置:
当你要求 Git 将一个分支合并到另一个分支时,Git 会去弄清楚它需要做什么才能将这些分支之间的差异合并到目标分支中。假设你想将你为开发所做的更改合并到 master 中,所以你告诉 git:
$ git checkout master
$ git merge develop
Git 会查看分支,发现 develop 比 master 快了几个提交,但没有比这更复杂的了。因此,它将进行“快进”合并,只需将主标签粘贴到开发指向的提交上即可。任务完成,以前只在开发中的变化现在也掌握了。
如果每个分支都有额外的提交,就像在将 master 合并到开发之前的第一个示例中那样,“更复杂的事情”正在发生。你在 master 上做了一个提交,然后做了一个 git checkout develop,在那里做了一个提交,然后让 Git 将 master 合并回 development。Git 现在不能再通过移动分支标签来“作弊”了。它将需要弄清楚如何将两个分支的更改统一到其控制下的文件的单一状态中(假设,现在,它总是能够做到这一点,这与事实相差不远;它的很擅长。如果不能,你就有一个合并冲突,这真的没有听起来那么糟糕)。
合并后的新内容既不会是第一个分支的最后一个状态,也不会是第二个分支的状态。因此,它需要用一个全新的提交来表示,这就是您在第一个示例顶部看到的内容;Git 创建了它所谓的“合并提交”来表示新状态,其中每个分支的更改合并到一个状态中。
最后,您可以强制 Git 始终创建合并提交,即使严格来说,从技术上讲,它是不需要的。在命令行中,您可以使用标志 --no-ff 来执行此操作,以禁止快进。图形客户端将有一个复选框来完成相同的事情(在 SourceTree 中,它当前标记为“即使通过快进解决合并也创建提交”)。许多人(包括我自己)实际上建议只使用 --no-ff 合并,因为这样合并的行为总是记录在历史中,无论技术上是否有可能只是移动分支指针。