Table of Contents:

Part 1 - What is a commit hash?

在学习如何使用Git时,我曾经遇到过的最大困难之一就是mergerebase之间的区别。大多数人很快就理解了merge的概念,但是当试图理解rebase的不同之处时,他们会迷失方向。在这篇由三部分组成的文章中,我将以最简单的方式介绍这些差异。为了做到这一点,我们首先需要了解什么是commit hash

如果您曾经在Git中查看过提交历史记录,那么您可能会看到类似的情况:

commit a9ca2c9f4e1e0061075aa47cbb97201a43b0f66f 
Author: Alex Ford 
Date: Mon Sep 8 6:49:17 2014

Initial commit.

您可能已经将长串字母和数字(commit hash 值)视为某一特定提交的唯一ID。虽然这样是对的,但是你可能还不知道它是一个生成的SHA-1 hash,代表git 的 commit object。如果不了解Git提交对象的可怕细节,那么你只需知道它是根据它所代表的信息直接生成的,一个很大的加密字符串。因为它是根据提交中包含的信息生成的,所以哈希值不能更改。更改提交哈希值的唯一方法是更改有关提交的详细信息,该提交本质上用一个全新的哈希生成一个全新的提交。

除了提交作者、日期和存储的数据等所有明显的信息外,每个提交还包含上一个提交的hash值。这正是生成提交历史记录的方式。每个提交都知道它前面的提交的哈希值。

在这里插入图片描述

在上面的图片中,您可以看到我的SourceTree窗口中我创建的演示仓库。我在演示仓库做了三次commitSourceTree足够智能,可以读取我的仓库中的每个提交,并构建此历史的图形表示。可以看到commit 2直接引用commit 1,而commit 1直接引用commit 0。请注意,为了便于讨论,我把提交的commit message直接写成了序号,真正的commit message应该描述在该提交中所做的更改。

由于我的演示仓库只包含对主分支的三次提交,所以SourceTree的分支网络图是从每次提交到下一次提交的一条简单的直线。让我们先创建一个单独的分支来处理一个特性,从而使事情变得更复杂一些。

在这里插入图片描述
在上面的屏幕截图中,你可以看到我创建了一个名为feature1的分支,但两个分支的网络图是相同的。这是因为自创建分支以来,我没有做出任何新的commit。两个都指向相同的提交的引用(不明白什么是提交的引用可以参考这篇文章:Git仓库.git文件夹目录介绍
)。现在,master和我的feature1分支都指向完全相同的commit。现在,我们进行一些更改,并向feature1分支添加新的commit
在这里插入图片描述
可以看到,我们的Feature1分支已经移动到指向一个新的commitcommit 3。还可以看到,我们的分支网络图仍然是一条简单的直线。这是因为到目前为止,总共只有四个提交,每个提交都引用它前面的一个提交。如果我现在将feature1合并到master中,那么唯一会发生的事情就是master分支会跳到与feature1相同的commit上,即commit 3。这称为快进合并(fast-forward merge),因为它只是将master 分支的指针向上移动,以指向较新的commit

好吧,现在我们很高兴地结束了我们的feature1分支的工作,当老板突然打电话说一个新的bug已经被记录在案,这是首要任务。我们需要停止对feature1的工作,并立即致力于错误修复。要做到这一点,我们需要切换到master并作出commit。如果bug很大,我们可以考虑创建另一个分支并对该分支进行多次commit,但是我们会假装bug很小,并且很容易在一次commit中修复。
在这里插入图片描述

现在情况有点不一样了。在上面的截图中,我已经把你的注意力集中在仓库网络图上了。注意,现在我们的feature1分支上的commit 3已经在图中的暂时远离于主分支了。原因很简单。commit 4commit 3都有完全相同的祖先。还记得commits是如何存储前面的commit的吗?当我们签出master时,我们返回commit 2,因为commit 3只被feature1分支的指针引用。主分支指针仍然指向commit 2。因此,我们的hotfix commit(commit 4)commit 2列为前一个commit

该图向我们展示了commit 4commit 3都引用commit 2作为上一个commit。在这种情况下,我们将commit 2称为commit 3commit 4的共同祖先。既然我们的热修复程序已投入使用,我们可以返回到我们的feature1分支并完成工作。
在这里插入图片描述

我已经向我们的特色分支提交了两个新提交的commit 5commit 6。我们的feature1已经完成,现在是时候将我们的feature1分支合并回master了。此时,我们可以选择将feature1分支合并为master,或者将feature1master变基。现在,让我们来探讨 Part 2 - What is a merge?

Part 2 - What is a merge?

在第1部分中,我们留下了一个演示仓库。我们有一个称为feature1的功能分支,它已经准备好合并回master。
在这里插入图片描述
此时,我们可以选择将feature1合并到master中,或者选择rebase。我们将在第3部分中介绍再rebase。现在让我们看看如果merge会发生什么。将分支合并在一起是非常直接的。我们首先需要checkout到要合并到的branch。因为我们想将feature1合并到master中,所以需要切换到master
在这里插入图片描述

我切到了master,然后将feature1合并到其中。让我们来看看到底发生了什么,以及为什么SourceTree生成的图看起来是这样的。
还记得part 1中的commit 3commit 4是如何拥有共同的 previous commit吗?commit 2是这两个提交的共同祖先,因为commit 3是在另一个分支上生成的,commit 4是在主分支上生成的,而主分支不知道commit 3。在我们的feature1分支中,我们添加了更多的提交。commit 5直接引用commit 3,因为commit 4只在主分支上可用。commmit 6引用提交commit 5

当我们将feature1合并到master时,它并没有以某种方式神奇地将这些提交转移到master分支。它实际上创建了一个全新的commit,其中包含Feature1分支上所有提交的所有更改。表示“merge branch feature1”的提交如下所示:
在这里插入图片描述
如果你一直关注屏幕截图中的提交差异,那么你会看到我添加到index.txt的乱七八糟的文字。您可能已经注意到,这些行都是在对feature1分支的单独提交中逐个添加的。但是,在这里您可以在单个diff中看到所有这些更改

Git所做的就是将所有对feature1的提交的差异都粉碎成一个提交。这个新的commit已经做了一些我们还没有讨论过的事情。如果你看这个图,你会发现它有两个祖先。它有来自commit 4和commit 6的行。为什么会这样?提交可以存储对多个以前提交的引用。我现在才提出来,因为我不想过早引起混乱。

创建提交时,它可以存储对单个以前commit hash、多个commit hashs 或者不存储任何commit hash。通常只有第一次提交到存储库时没有以前的提交引用,而合并提交通常是存储多个以前提交引用的唯一提交。

如果您还记得第1部分,那么分支实际上只是指向特定提交的指针。
在这里插入图片描述

你可能会注意到feature1仍然指向commit 6,而master现在指向新的合并commit。这仅仅是因为我们将feature1合并为master。如果我们切到feature1并将master合并到其中,那么Git所要做的就是另一个fast-forward merge(快进合并),它将使feature1指针向上指向同一提交。
在这里插入图片描述
如果我们现在完全删除我们的Feature1分支,您可能会期望粉色分支线消失,但您错了。

请记住,Source Tree和任何其他Git GUI通过遍历提交并使用引用的提交散列将它们连接在一起来生成图形。分支只是指向特定提交的指针。从远程存储库中提取时,Git所做的一切都是:

  1. 下载本地计算机没有的任何提交。

  2. 将丢失的提交合并到本地存储库中,可以通过合并提交,也可以通过快进合并(如果自上一次请求以来没有进行任何更改)。

  3. 将本地分支指针向上移动到最新提交。

如果你曾经对masterorigin/master感到困惑,现在你知道它们是什么了。origin/master显示提交到叫做origin的远程仓库的master指向的内容。如果我将一个名为origin的远程仓库添加到我们的演示仓库,然后提交到我的本地仓库,那么历史记录将如下所示:

在这里插入图片描述

您可以看到master的图形指向最新的提交,而origin/master图形指向上一个合并提交。Source Tree甚至让我们知道有1个提交要推送到远程存储库。如果我们执行push操作,那么git将上传丢失的commit并更新远程分支指针,以显示origin/master现在指向与本地主分支相同的commit

在这里插入图片描述
希望您现在能够更好地理解Git中合并的工作原理。跳到part 3 - What is a rebase?,让我们深入研究rebase,看看它与合并的比较。

Part 3 - What is a rebase?

请看下一篇文章
Git:rebase 是什么

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐