分支是 Git 的利器,有了分支,开发人员可以独立做不同的事情而事情之间不会干扰。
何为分支
先了解 Git 是怎样存储数据的。假设有3个文件需要跟踪,将他们添加到暂存区域后,会为每个文件计算校验和,然后保存文件快照到 Git 仓库中(Git 使用 blob 类型的对象存储这些快照)。
使用 git commit 提交后,Git 会将已跟踪的数据保存为树(tree)对象,树对象中保存了各个校验和的索引,然后建立一个提交对象,其中包含了指向树对象的指针,另外,提交对象中还保存了作者和提交者等信息。
现在,Git 仓库中有五个对象:三个表示文件快照内容的 blob 对象;一个记录着目录树内容及其中各个文件对应 blob 对象索引的 tree 对象;以及一个包含指向 tree 对象(根目录)的索引和其他提交信息元数据的 commit 对象。
作些修改后再次提交,这次的提交对象就会包含一个指向上次提交对象的指针(即下图中的 parent 对象)。两次提交后,仓库历史会变成这个样子
Git 中的分支,其实本质上仅仅是个指向 commit 对象的可变指针。Git 使用 master 作为分支的默认名字。在若干次提交后,就已经有了一个指向最后一次提交对象的 master 分支,它在每次提交的时候都会自动向前移动。
使用如下命令可以创建一个新的分支(也就是新的指向 commit 对象的指针)
$ git branch testing # 创建 testing 分支
Git 保存了一个名为 HEAD 的特别指针,指向当前工作的本地分支,可以认为他是当前本地分支的别名。
需要注意的是:运行 git branch
命令,仅仅是建立了一个新的分支,但不会自动切换到这个分支中去。
切换分支:git checkout 分支名
运行 git checkout testing ,HEAD 就指向了 testing 分支。
如果此时在 testing 分支工作,修改文件并提交,testing 就会向前移动,同时 HEAD 也会跟着移动。
如果再切换到 master 分支,就会暂时放弃 testing 分支时的修改,而是回到了比较旧的版本,此时在此基础上重新修改并提交,就会产生新的版本,是不同于 testing 修改提交的版本,这时两个版本就出现了分歧,也就是分支开始走向不同的方向。
Git 分支的新建与合并
假设你的项目已经进行了好几次提交
此时想给项目添加一个新的特性,于是建立了 iss53 分支,来开发新特性。
git checkout -b 分支名 :加上 -b 参数可以建立分支并切换到该分支
于是,执行 git checkout -b iss53
相当于执行
$ git branch iss53
$ git checkout iss53
现在的状态图是这样的
经过几次提交的状态变成这样的
此时项目有个 bug 需要紧急修复,于是创建一个新的分支来修复项目问题。
此时需要切换回 master 分支,然后在此分支上分出新的分支。不过在切换之前,需要将工作区的修改以及暂存区全部提交,否则会阻止切换到 master 分支。
切换到 master 分支后,执行 git checkout -b 'hotfix' 创建 hotfix 分支并切换到该分支。
可以发现,切换到 master 后再创建分支,就会在 master 所指的节点创建新的分支(岔路口)
修复完成后,就将 hotfix 和 master 合并,然后发布到生产服务器。
git merge 分支名:将当前分支和分支名标识的分支合并
于是重新切换到 master 执行 git merge hotfix,此时的状态图是这样的。
由于 master 分支是在 hotfix 分支的直接上游,因此 master 可以直接右移到 hotfix 指向的节点,因为这种单线的历史分支不存在任何需要解决的分歧,所以这种合并过程可以称为快进(Fast forward)。
此时 master 分支和 hotfix 指向同一个节点,可以删除 hotfix 分支了
git branch -d 分支名:删除分支
于是执行 git branch -d hotfix ,此时修复已经完成,可以继续新特性的开发了,于是切换到 iss53 分支接着工作。
此时新特性开发好了,需要合并到 master 分支中,于是切换到 master 分支,执行合并操作。
$ git checkout master
$ git merge iss53
与 master 和 hotfix 合并不同的是,master 并不在 iss53 的直接上游,而是处在两个分叉的分支末尾。
对于这种分支的合并,Git 的处理方式是:
Git 会用两个分支的末端(C4 和 C5)以及它们的共同祖先(C2)进行一次简单的三方合并计算,对三方合并后的结果重新做一个新的快照,并自动创建一个指向它的提交对象(C6)。Git 可以自己裁决哪个共同祖先才是最佳合并基础。
合并完成后,就可以删除 iss53 分支了
执行 git branch -d iss53