赞
踩
引言
分支模型被称为Git 的 “杀手性能”( “killer feature” ),使众多的VCS甘拜下风。其分支处理方式使得Git既能在几乎一瞬间创建新分支,也能同样快捷地在不同分支之间来回切换。
几乎所有的VCS(版本控制系统)都支持分支。说到分支,是指可以从主线上把某个工作(例如增加性能或去除bug等)分离出来进行,而不会对主线的工作产生影响。等到把分离出来的工作完成后,再通过合并把它融入到主线中去。
形象地说,分支有点像树干上长出树枝。但是这里的树干及树枝都是由Git中的提交“连接”而成的,就像一个“串”。所以也可以说,分支是以Git的提交为元素所形成的有限序列,不妨称之为提交串。
每个Git仓库有一个主分支,就相当于树干,是在仓库建立时自动生成的,名为main或master。在首次提交成功之后,任一分支(作为提交串或有限序列)都可记为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是当前分支,而87ab2是testing中的最新提交。
在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)。
例3 在下图的上半部分中,主分支包含4个提交: master=(98ca9, 34ac2, f30ab, c269e)。如果这时我们发现需要修改第3个提交f30ab的内容,就可以在主分支上的提交f30ab处,新建一个分支testing。Git命令为:
$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/中,恰有两个描述分支的文件 main和test:
下面在当前分支main上创建一个新分支newone,注意分支起点是提交4288:
上述命令执行后,在仓库文件夹01/.git/refs/heads/中,马上增加了一个描述分支的文件 newone:
而这个文件newone的内容只有一行40个字符,外加一个换行符:
有兴趣的读者不妨在分支newone中完成几个提交,同时观察一下文件newone内容的变化。
例6 我们也可以从Windows资源管理器中看到仓库的HEAD指针现在指向主分支main,如下图。
图中下方的红箭头指向01仓库中的文件01/.git/HEAD,它用来记录谁是当前分支。而上方的箭头所指是文件HEAD的内容,它说明当前分支是主分支main。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。