什么是Git的分支?

引言 

        分支模型被称为Git 杀手性能 “killer feature” ),使众多的VCS甘拜下风。其分支处理方式使得Git既能在几乎一瞬间创建新分支,也能同样快捷地在不同分支之间来回切换。

        几乎所有的VCS(版本控制系统)都支持分支。说到分支,是指可以从主线上把某个工作(例如增加性能或去除bug等)分离出来进行,而不会对主线的工作产生影响。等到把分离出来的工作完成后,再通过合并把它融入到主线中去。

    形象地说,分支有点像树干上长出树枝。但是这里的树干及树枝都是由Git中的提交“连接”而成的,就像一个“串”。所以也可以说,分支是以Git的提交为元素所形成的有限序列,不妨称之为提交串

    每个Git仓库有一个主分支,就相当于树干,是在仓库建立时自动生成的,名为mainmaster。在首次提交成功之后,任一分支(作为提交串或有限序列)都可记为X=x1,……,xn),其中X是分支名,x1是首次提交,而最后一个元素xn则是X中最新的提交。Git中常常可以只用几个字符来表示一个提交(称为提交号),所以在下面的图1中,仓库的主分支可以表示为master =98ca9, 34ac2, f30ab),而master上的分支testing则可以表示成testing =98ca9, 34ac2, f30ab, 87ab2)。

                                  图1   主分支master与 主分支上的分支testing

    从图1中还可看出两点,一是分支testing的“分叉点”在提交f30ab处(这是分支的起点);二是在分支testing中又进行过一次提交(87ab2),而且仓库的HEAD指针现在指向testing分支(从而指向提交87ab2),说明testing是当前分支,而87ab2testing中的最新提交

    在Git仓库中创建一个新分支很容易,只要在Git Bash命令窗口输入:

 $ git branch X($是Git的命令提示符),

就会在当前分支创建一个名为X的新分支。如果要在指定的提交xxxxx处创建一个名为Y的分支,则需要在分支名Y后面输入指定的提交号:

$ git branch Y xxxxx

如果想要切换当前活动分支,使Git的HEAD指针指向分支X就需要命令:

$git switch X     (switch也可换成checkout)

此外,用命令$git branch 还可以列出仓库里所有分支,且在活动分支名前标一个*号。

      要体验 Git分支功能所带来的效率,就需要理解Git如何处理分支,因而需要了解Git的提交。

一、提交对象

        在进行提交时Git会存储一个提交对象commit object,其中包含着一个指针,它指向此次提交时暂存内容的快照(实际上是指向所谓“树对象”tree,见图2)。除此之外,提交对象中还包含:1)作者、提交者姓名及电子邮箱地址,2)输入的提交信息,3)指向“上一个提交”(当前提交的“父级”提交)的指针。可见,提交对象中包括了“快照放在哪里、哪个作者要干啥以及父级提交是谁”等信息。

        由此看来,提交对象一般包含有两个指针。但也不尽然:例如一个仓库的首次提交是“从天而降”的,它没有父级(或者说有“零个”父级),所以第二个指针为空。此外,尽管一般的提交恰有一个父级,但对于由两个(或多个)分支合并产生的提交,却会有两个(或多个)父级(的指针)。

       关于Git提交的内部(底层)原理,参见https://git-scm.com/book/en/v2/Git-Internals-Git-Objects 。本文希望给出一个较简单易懂的叙述,但不一定十分严谨。

二、几个例子

        为了易于理解,让我们先来考察两个例子。

例1  假设建立Git仓库后,我们在当前工作目录中编写了三个文件,名为README、test.rb和LICENSE。下面我们想把这三个文件提交给Git的本地仓库(以对它们进行追踪),这需要两个步骤:

1)把这三个文件添加到Git的暂存区,命令是:$ git add README test.rb LICENSE

此时Git计算每个文件的“校验和”(由SHA-1 生成的哈希值),将当前版本的文件快照存储到 Git 存储库(作为 blob对象),并将校验和添加到暂存区域等待提交。

2)提交的命令为:$ git commit -m 'Initial commit'

此时Git 首先校验工作目录的每个子目录并将它们作为“树对象”存入存储库中;然后Git 创建提交对象。注意,一个Git仓库中,上述对象的“校验和”常常只需要写出前面几位就够了,例如图2至图3中是五位。

        现在来回顾Git存储库在提交过程中出现的变化:首先,在git add命令完成后增加了三个 blob对象(对应着三个文件的内容,如图2右侧所示);然后,在git commit命令完成后又增加了两个对象,一个是目录树对象(列有目录结构和内容并指明哪些文件存储成哪些 blob ,如图2中间所示),另一个就是提交对象(图2左边的 98ca9)。

        而且,从图2中各对象的关系可见,一次提交产生的所有对象是可以被连结在一起的(通过追踪指针或哈希值),而且以提交对象为其首。

        于是我们看到,Git仓库中的每一次提交都会在Git存储库中生成一组对象(如图2所示)。这组对象以提交对象为首,记录了此次提交时仓库的状态(包括工作区状态和各文件版本的状态)。

        回到图1中,就不难理解用五个字符98ca9表示的首次提交了,它取自该提交中提交对象的哈希值的前五位    。

        图2首次提交的一个示意图,其中仍用哈希值的前五位表示该提交的各个组成部分

   

                              图2  Git提交生成的对象:提交对象、目录树对象、blob对象

 2  在例1的基础上,假定我们接下来在工作区中修改了三个文件中的一个。然后执行一下查看工作区状态的命令(这可能是Git最常用的一个命令):

$ git status

Git此时就会提醒我们,需要重新提交更改了的文件。当再次提交完成后,新的提交对象就会包含一个指向父级提交对象的指针,示意图如下:

图3  左边为首次提交98ca9,右边为第二次提交34ac2,其中98ca9为父级提交

     

结论  从上面的两个例子我们可以看出如下几点:

1、Git的每一个提交都可以用其提交对象的哈希值(包含40个字符)表示,不过常常只需要引用其前几位就够了,在上面的示意图(图1-图3)中使用了前五位,不过Git也常常引用前七位。

2、所有的提交对象在Git存储库中会自然形成一些提交串,每个分支都是一个“串”,由那些指向父级提交的指针把它们串起来,而分支的最新提交则是这个串的头。

3、在建立了Git仓库而没有提交的时侯,Git存储库只有一个主分支,它是一个 “空串”。在只有主分支的情况下,Git存储库中只有一个串,即主分支。其中的提交构成一个序列,形如master=x1,……,xn)。

注意:因为Git的提交都可以用其提交对象的哈希值(或者其哈希值的前几位)来表示,所以称这个值之为该提交的提交号 (commit ID)。

在下图的上半部分中,主分支包含4个提交: master=98ca9,  34ac2,  f30ab,  c269e)。如果这时我们发现需要修改第3个提交f30ab的内容,就可以在主分支上的提交f30ab处,新建一个分支testingGit命令为:

$git branch testing f30ab;

$git checkout testing

第一行命令提交f30ab处建立新分支testing,第二行命令把仓库的HEAD指针切换到新分支testing,从而所进行的修改都会不影响主分支的内容。  

                    图4  在主分支的f30ab处建立分支testing后,已完成了一个提交87ab2

       

假定修改完成后,我们进行提交,其提交号为87ab2。那么此时的testing分支作为提交序列就是testing =98ca9,34ac2,f30ab,87ab2),见图4中的下半部分

三、分支是什么

        尽管分支X可以直观地画成一个串,但别忘了它实际上是由提交构成的序列,序列的最后一个元素xn是最新提交,也就是“提交串的头“。

        抓住了这个“头”也就确定了这个分支。于是Git需要设置分支的所谓“指针”来盯住这个“头”!因此,在实现的层面上来看,我们创建一个新分支只涉及到下面两件事:

1)为新分支命名;

2)创建一个与新分支同名的文本文件,用该文件记录分支中最新提交xn 提交号(是全部40个字符而不只是前几位)!注意,在新创建分支时,该文件中记录的是分支点处的提交号。

例4  在上一个例子中,当我们在主分支上的提交f30ab处新建分支testing后,Git就创建了一个文本文件testing,在其中记录了提交f30ab的完整哈希值。不难想象,当我们在分支testing中完成新提交87ab2后,提交序列变成

testing =98ca9,34ac2,f30ab,87ab2),

从而文件testing的内容跟着变成了提交87ab2的哈希值!

而且,当新分支中又有新提交xn+1 完成后,该文件的内容会变成提交xn+1 哈希值,这也意味着分支指针向前移动了一步。

        于是我们可以这样来定义:Git 分支就是指向提交(对象)的可移动的指针,它随着分支里提交的变化而移动。除了主分支外,其它分支都是从某个提交处分叉的,我们称此提交为分叉点,例如在图4中,提交f30ab是分支testing的分叉点。

        本文最后,我们给出一组图片,补充说明分支建立时Git的一些动静。

 例5   查看仓库的分支情况(命令见下图),Git返回的信息是:仓库01有两个分支,主分支main和test,而且仓库的HEAD指针指向主分支main

     上述结论也可以从Windows资源管理器中看到。下图说明,在仓库文件夹01/.git/refs/heads/中,恰有两个描述分支的文件 maintest

       下面在当前分支main上创建一个新分支newone注意分支起点是提交4288: 

       上述命令执行后,在仓库文件夹01/.git/refs/heads/中,马上增加了一个描述分支的文件 newone

 而这个文件newone的内容只有一行40个字符,外加一个换行符:

有兴趣的读者不妨在分支newone中完成几个提交,同时观察一下文件newone内容的变化。

例6  我们也可以从Windows资源管理器中看到仓库的HEAD指针现在指向主分支main,如下图。

 图中下方的红箭头指向01仓库中的文件01/.git/HEAD,它用来记录谁是当前分支。而上方的箭头所指是文件HEAD的内容,它说明当前分支是主分支main。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值