4.Git分支 (Branching)
几乎每种VCS都有一些分支支持的方式。分支意味者你偏离了开发的主线而需要继续你的工作同时不弄乱主线。在许多其它VCS工具中,这在某种程度上来说是很昂贵的过程,通常需要你创建一个你源码目录的新的copy,这对一个大型的项目而言会花费很长的时间。
一些人认为Git的分支模型是其”杀手级功能”,这当然把Git与其它VCS社区区分开来。那么为什么它会如此特殊呢?Git分支方式不可思议的轻型,它使得分支操作几乎是即时的,而且在不同分支之间来回切换通常是非常快捷的。它不像其它一些VCSs,Git鼓励一个工作流经常分支与合并,即使一天中发生多次。理解并掌握这个功能会给你强有力的唯一的工具并最终切实改变你开发的方式。
4.1 什么是分支(What a Branch Is)
为了真正理解Git分支的方法,我们需要回溯一下探讨Git是怎么存储它的数据的。你可能会从第1章中记得,Git并不是存储那些更改集或者增量(deltas)的序列,而是存储一个快照的序列。
当你在Git中提交时,Git存储一个提交对象它包含了一个指针指向你缓存的内容的快照,作者和消息元数据,零个或多个提交的指针指向这个提交的父亲:对第一个提交为0个父亲。正常的提交为一个父亲,对一个来自于两个或多个分支合并的提交则可能有多个父亲。
为认识这一点,让我们假设你有一个目录包含了三个文件,你缓存了所有这三个文件并提交了。缓存每个文件的校验和(我们在第1章提到的SHA-1哈希),并在Git库中存储哪个文件的版本(Git以blobs来引用它) ,增加校验和到缓存区中:
$ git add README test.rb LICENSE
$ git commit -m 'initial commit of my project'
当你运行git commit来创建一个提交时,Git运算每个子目录的校验和(本例中只是项目根目录)并存储这些树对象到Git库中。Git然后创建一个具有元数据和一个指针指向根目录树的提交对象以便当需要时可以重建快照。
你的Git库现在包含了5个对象:三个文件中的每个对应存储在一个blob中,一个树用来列出目录内容以及指出哪个文件名存储在哪个blob中,一个具有指针指向树根以及所有提交元数据的提交。概念上来说,你Git库中的数据看起来如图3-1所示:
如果你做了一些更改并再次提交,那么下一个提交存储一个指针指向它前面的那个提交。两次提交后,你的历史可能看起来如图3-2所示:
Git 中的分支(branch)只是一个简单的轻型可移动的指针指向其中一个提交。Git中的缺省分支是master。当你初始开始提交时,你会获得一个master分支指向你的最后一个提交。每次当你提交时,它自动向前移动。
那么,如果你创建一个新的分支会发生什么情况呢? 也就是说,你这么做会创建一个新的指针用来移动。假定你创建了一个新的分支命名为testing。你可以用git branch命令来实现:
$ git branch testing
这会在你当前所在的同一个提交上创建一个新的指针 (如图:3-4)
那么, Git怎么知道你当前位于哪个分支上呢?它保存一个特殊的指针称为 HEAD。注意这于你可能用过的其它 VCS系统诸如 Subversion或者 CVS在 HEAD概念上有许多不同。在 Git中,这是一个指针指向了你目前工作的本地的分支。在本例中,你仍然工作在 master上。 Git分支命令仅仅创建一个新分支 --它并不切换到那个分支上 (如图 3-5):
为了切换到一个现存分支,你可以运行git checkout命令。让我们切换到这个新的testing 分支上:
$ git checkout testing
这个命令把HEAD指针指向testing分支(如图3-6):
这意味着什么呢? 那么,让我们再做一次提交:
$ vim test.rb
$ git commit -a -m 'made a change'
图3-7示例了结果:
这很有趣,因为现在你的testing 分支向前移动了,而你的master分支仍然指向你运行git checkout切换分支时的那个提交。那么,让我们再切回master分支:
$ git checkout master
图3-8显示了结果:
这个命令做了两件事。它把HEAD指针移回到master分支上,并恢复你的工作目录中的文件回到master分支指向的那个快照。这也就意味者你从此处向前所作的更改将偏离于项目的老版本。它本质上暂时回退了你在testing分支上所做的工作,这样你可以在一个不同的方向向前。
让我们再做一些更改并再次提交:
$ vim test.rb
$ git commit -a -m 'made other changes'
现在,你的项目历史已经分离(如图3-9)。你创建并切换到一个分支,在上面做工作,然后切回主分支做其它工作。所有这些更改是被隔离在分离的分支上:你可以来回在分支间切换,可以在当你准备好时合并它们到一起。你做这些工作只需要简单的使用branch和checkout命令。
因为Git中的分支实际上是一个文件,它包含了它指向的那个提交的40个字符的SHA-1校验和,因此,分支创建和销毁是非常便宜的。创建一个新分支就像简单地写一个41个字节到一个文件那么快(40个字符和一个新行)。
这与哪些其它的VCS工具的分支是明显不同的,那些工具会涉及到copy项目的所有文件到第二个目录中。这可能会花费几秒钟或者甚至分钟(依赖于项目的大小),然而在git中它几乎是即时的。同时,因为我们在提交时记录了父亲,因此,在合并时找到合适的合并基础是自动为我们做的并且通常情况下会非常简单。这些功能可以鼓励开发者们经常性地创建并使用分支。
下面让我们看一下为什么你需要这么做。