在 Git 中,没有所谓的分支的“基础分支”。相反,只有一个“当前提交”,Git 称之为“分支的提示”。
画图来理解,您可以开始绘制(至少部分)Git 的提交图。这是一个微小仓库包含三个提交的示例:
A <-B <-C <--master
每个提交的"真名"都是一个由哈希算法生成的长长的不好记的哈希ID,如c0ffeeface1deadbead...
等。这个哈希ID唯一标识着该特定提交,并实际上是通过哈希(因此称其为"哈希ID")该提交的内容来生成的。它们看起来很随机,无法记住,所以在这里我只用单个大写字母。
Git "看到"图表的方式是先读取一个分支名称,比如master
。这个分支名称包含类似于C
的提交的哈希ID。我们说master
指向提交C
。
同时,提交C
本身包含其前一个(或父)提交B
的哈希ID。因此,我们说C
指向B
,就像master
指向C
一样。
同样地,提交B
指回A
。 A
是第一个提交,所以它没有指向任何地方...所以它就不用指了。我们称A
为根提交,它让我们(和Git)停止向后工作。
这些内部箭头有点烦人,注意到B
的哈希ID实际上是C
本身的一部分,所以它永远不会改变(如果我们试图更改C
的这部分,我们将得到一个新的、不同的提交)。因此,我们可以停止画它们,而改写成:
A
分支名称上的箭头并非恒定不变,这就是最新提交的概念来源。
假设我们想要在master
分支添加一个新的提交。我们进行Git所需的所有常规设置(添加或修改一些文件并使用git add
),然后运行git commit
。Git:
A--B--C <-- master
\
D
然后,
更改 master
(或者更准确地说,它存储的哈希 ID)以便指向我们刚创建的新提交:
A
\
D <
当然,现在没有必要再保留图纸中的这个细节了:
A
这就是 Git 中分支的生长方式。
要创建一个新分支,Git 只需创建一个指向某个现有提交的分支名称:
A
\
B
\
C
\
D <-- master
我们可以选择这些提交中的任何一个,然后创建一个新的分支并指向它。让我们选择B
并将 newbr
指向它:
A
\
B <-- newbr
\
C
\
D <-- master
我们可以使用 git branch newbr <thing-that-finds-B>
来完成此操作。
但是如何找到提交的呢?
我们如何找到 B
? 一种方法是运行 git log
,并剪切复制哈希 ID。但另一种方法是使用分支名称。现在名称 newbr
指向提交 B
。如果我们想让另一个分支也指向提交 B
:
git branch thirdbr newbr
这将使Git查找newbr
,它指向B
,并创建新的名称thirdbr
,该名称也指向B
:
A
\
C
这就是为什么在Git中创建分支如此之快:它几乎什么都不做! 它只是创建一个指向某个现有提交的标签。某些分支名称指向的提交称为该分支的“tip提交”。请注意,一个提交可以同时成为多个分支的tip。这是Git的一部分:一些提交在同一时间出现在许多不同的分支上。然而,分支名称的特殊属性在于它们会
移动,而且它们会自动移动。当我们进行新的提交时,Git会将新提交的ID写入master。那么,Git如何知道要使用master?Git如何知道在创建提交D时要使用C作为父提交?
现在我们有三个标签:master、newbr和thirdbr。首先,让我们执行“git checkout thirdbr”,然后绘制结果:A
\
C
在图中,除了我在这里添加了单词HEAD
,其他并没有改变。1 HEAD 是Git用来确定当前分支和提交的方法。
那么现在我们照常修改文件,使用git add
和git commit
。 Git会写入新的提交,将其父提交设置为提交B
。Git发现当前分支是thirdbr
,thirdbr
指向B
,因此当前的提交是B
。 让我们在图中画出新的提交E
:
E
/
A
\
C
现在唯一要做的就是移动当前分支 name thirdbr
,使其指向新提交 E
:
E <
/
A
\
C
我们完成了:我们已经在分支thirdbr
中添加了一个新的提交(它仍然是HEAD,因此仍然是当前分支,但现在E
是当前提交)。
当您将提交添加到当前分支时,HEAD会告诉您当前提交是什么以及新提交的位置。 HEAD通常包含一个分支的名称,这就是git checkout
的作用:它检出特定的提交——通常是现有分支的末端提交——然后设置文件HEAD
以记住分支的名称。实际上是分支名称自己记住了末端提交。
使用 git checkout -b newname commit
的意思是:“检出指定的commit,然后创建一个指向该提交的新分支newname,最后将HEAD设置为该新名称。” 如果省略commit部分,则默认使用HEAD。由于HEAD始终是当前提交,因此Git可以跳过“检出”部分,只需创建新的分支名称并将其存储到HEAD
文件中。
1虽然在图形中没有任何更改,但Git确实必须更新我们的工作树和索引,以便我们可以按照提交B
时的方式获取文件。
branch
只会创建分支但不会checkout
。 - Christoph