1.分支简介
几乎所有的版本控制系统都支持某种形式的分支。使用分支意味着可以把你的工作从开发主线上分离开来,以免影响开发主线。Git的分支是其必杀技,它相对于其它版本控制系统来说,具有难以置信的轻量性,创建分支以及切换分支几乎都是瞬间完成。Git鼓励频繁的使用与合并分支。
Git保存的不是文件的变化或者差异,而是一系列不同时刻的文件快照。
在进行commit操作的时候,Git会保存一个提交对象(commit object),该对象包含一个指向暂存内容快照的指针,包含指向父commit对象的指针,以及提交者的名字,提交信息等。首次提交的对象没有父对象,普通提交操作产生的提交对象只有一个父对象,合并操作产生的提交对象有多个父对象。
暂存操作(git add)会计算每一个文件的校验和(也就是一个hash值,Git使用的hash算法是SHA-1),然后将当前版本的文件快照保存在Git仓库中(Git使用blob对象来保存它们),最终将校验和加入到暂存区域等待提交。
当执行git commit操作的时候,Git会计算每一个目录的校验和,然后在Git仓库中将这些校验和保存为树对象。git commit创建的提交对象包含指向树对象(项目根目录对应的树对象)的指针。
如上图所示,一个项目有一个根目录,其对应的tree对象的校验和是y46r5,根目录下面有三个子目录,对应的树对象的校验和分别为t5re4、h65er、hn63u,每个目录下面有一些文件,Git将其保存为blob对象,每个文件对应于一个blob对象,同时也知道每个文件的校验和。这样一来,Git就能够保存项目的树状结构,每一个时刻都有其不同的树状结构,就能够在需要的时候恢复到某一个对应的状态。
如下图,Git的提交对象包含指向内容快照的指针(hash值)以及指向父提交节点的指针(hash值), 每个快照对应于上图中所有的tree对象和blob对象组成的部分。
对于commit对象、tree对象、blob对象包含的详细内容,可以参考下图。 每个commit对象包含所指向的tree对象的hash值,每个tree对象包含所指向的blob对象的hash值。
Git分支的本质实际上只是一个指向提交(commit)对象的可变指针。Git默认分支的名字是master,几乎所有的仓库都有一个master分支,这是由于git init自动创建它,并且大多数人都懒得去修改它。
2.分支创建
分支的创建本质上只是创建了一个指向提交(commit)对象的指针。
使用 git branch <branch name> 创建一个分支。这条命令将创建一个指向当前提交对象的分支。
注意:这条命令不会自动切换到新的分支上去。
那么,Git又是如何知道当前在哪个分支上的呢?Git有一个名为HEAD的指针,它指向当前所在的本地分支(可以将其看作是当前分支的别名)。
如下图,创建一个testing分支,此时master和testing同时指向校验和以f30ab开头的提交对象,但是HEAD还是指向master,并没有指向testing(因为git branch不会自动切换分支)。
可以使用 git log --decorate 命令查看各个分支当前所指的对象。
3.分支切换
使用 git checkout <branch-name> 切换分支,其本质上是将HEAD指针指向branch-name指向的分支。例如 git checkout testing 会产生如下图所示结果:HEAD指向了testing分支。
这时候,在提交(commit)一次,会得到如下图所示的结果:master分支没有向前移动,HEAD所指向的testing分支向前移动了。也就是说当我们执行git commit操作的时候,HEAD指向的分支随着提交操作自动向前移动。
如果执行git checkout master,这时候Git会做两件事情:1.将HEAD指针指向master;2.将工作区的内容恢复成master所指向的快照内容(也就是master所指向的commit对象所指向的文件快照)。
注意:分支切换会改变你本地的工作区域的内容。会将本地工作区域的内容恢复到被切换到的分支上最后一个提交时候的样子。
分支的本质实际上仅是包含所指提交对象的校验和的文件(文件中存储的是所指提交对象的校验和,长度为40的hash字符串)。创建一个分支相当于给文件中写入一个hash字符串,所以创建Git分支相当的高效。
其它的版本控制系统,在创建分支的时候,会将所有的项目文件都复制一遍,并保存到特定的目录。这会导致其它的版本控制系统的效率非常低效。