当创建一个新分支时,基础分支是什么?

29

我需要确认/纠正我创建分支时的假设。如果我在主分支中,在执行以下操作后:

git checkout -b some_branch

这意味着我从主分支开始创建了一个新分支。

另一方面,如果我检出另一个分支,并在那里创建一个新分支:

git checkout some_branch
git checkout -b other_branch

这意味着我使用从 some_branch 提交的所有当前代码创建了 other_branch,对吗?

而且,无论当前的分支是什么,如果完成了以下操作:

git branch branch_2 branch_1

然后将使用branch_1作为基础创建branch_2。 这些假设是否正确?


1
是的,这是正确的。branch 只会创建分支但不会 checkout - Christoph
1个回答

42
在 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指回AA是第一个提交,所以它没有指向任何地方...所以它就不用指了。我们称A为根提交,它让我们(和Git)停止向后工作。

这些内部箭头有点烦人,注意到B的哈希ID实际上是C本身的一部分,所以它永远不会改变(如果我们试图更改C的这部分,我们将得到一个新的、不同的提交)。因此,我们可以停止画它们,而改写成:

A--B--C   <-- master

分支名称上的箭头并非恒定不变,这就是最新提交的概念来源。

假设我们想要在master分支添加一个新的提交。我们进行Git所需的所有常规设置(添加或修改一些文件并使用git add),然后运行git commit。Git:

  • 写出一个新的提交D(获得一个新的、唯一的哈希ID)。这个新的提交指向C

 A--B--C     <-- master
        \
         D
然后,更改 master (或者更准确地说,它存储的哈希 ID)以便指向我们刚创建的新提交:
 A--B--C
        \
         D   <-- master

当然,现在没有必要再保留图纸中的这个细节了:

    A--B--C--D   <-- master

这就是 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--B   <-- newbr, thirdbr
    \
     C--D  <-- master
这就是为什么在Git中创建分支如此之快:它几乎什么都不做! 它只是创建一个指向某个现有提交的标签。某些分支名称指向的提交称为该分支的“tip提交”。请注意,一个提交可以同时成为多个分支的tip。这是Git的一部分:一些提交在同一时间出现在许多不同的分支上。然而,分支名称的特殊属性在于它们会移动,而且它们会自动移动。当我们进行新的提交时,Git会将新提交的ID写入master。那么,Git如何知道要使用master?Git如何知道在创建提交D时要使用C作为父提交? 现在我们有三个标签:master、newbr和thirdbr。首先,让我们执行“git checkout thirdbr”,然后绘制结果:
A--B   <-- newbr, thirdbr (HEAD)
    \
     C--D  <-- master

在图中,除了我在这里添加了单词HEAD,其他并没有改变。1 HEAD 是Git用来确定当前分支和提交的方法。

那么现在我们照常修改文件,使用git addgit commit。 Git会写入新的提交,将其父提交设置为提交B。Git发现当前分支是thirdbrthirdbr指向B,因此当前的提交B。 让我们在图中画出新的提交E:

     E
    /
A--B   <-- newbr
    \
     C--D  <-- master

现在唯一要做的就是移动当前分支 name thirdbr,使其指向新提交 E

     E   <-- thirdbr (HEAD)
    /
A--B   <-- newbr
    \
     C--D  <-- master

我们完成了:我们已经在分支thirdbr中添加了一个新的提交(它仍然是HEAD,因此仍然是当前分支,但现在E是当前提交)。

当您将提交添加到当前分支时,HEAD会告诉您当前提交是什么以及新提交的位置。 HEAD通常包含一个分支的名称,这就是git checkout的作用:它检出特定的提交——通常是现有分支的末端提交——然后设置文件HEAD以记住分支的名称。实际上是分支名称自己记住了末端提交。

使用 git checkout -b newname commit 的意思是:“检出指定的commit,然后创建一个指向该提交的新分支newname,最后将HEAD设置为该新名称。” 如果省略commit部分,则默认使用HEAD。由于HEAD始终是当前提交,因此Git可以跳过“检出”部分,只需创建新的分支名称并将其存储到HEAD文件中。


1虽然在图形中没有任何更改,但Git确实必须更新我们的工作树和索引,以便我们可以按照提交B时的方式获取文件。


只是想知道如果处于分离模式,HEAD会包含什么? - MK Yung
1
@MKYung:在这种情况下,它保存了原始哈希ID。Git不需要执行“HEAD”->分支->哈希操作,而是直接执行“HEAD”->哈希操作。git commit不再需要执行哈希->HEAD->分支操作,而是直接将新的哈希写入HEAD - torek
1
在GitHub和其他来源上有关于“base”分支的文档。 - IEnjoyEatingVegetables

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接