Git分支
分支
分支是Git的杀手级特征,而且Git鼓励在工作流程中频繁使用分支与合并,哪怕一天之内进行许多次都没有关系。因为Git分支非常轻量级,不像其他的版本控制,创建分支意味着要把项目完整的拷贝一份,而Git创建分支是在瞬间完成的,而与你工程的复杂程度无关。
因为在上文中已经说到,Git保存文件的最基本的对象是blob
对象,Git本质上只是一棵巨大的文件树,树的每一个节点就是blob
对象,而分支只是树的一个分叉。说白了,分支就是一个有名字的引用,它包含一个提交对象的的40位校验和,所以创建分支就是向一个文件写入 41 个字节(外加一个换行符)那么简单,所以自然就快了,而且与项目的复杂程度无关。
Git的默认分支是master,存储在.git\refs\heads\master
文件中,假设你在master分支运行git branch dev
创建了一个名字为dev
的分支,那么git所做的实际操作是:
- 在
.git\refs\heads
文件夹下新建一个文件名为dev
(没有扩展名)的文本文件。 - 将HEAD指向的当前分支(当前为
master
)的40位SHA-1 校验和外加一个换行符写入dev
文件。 - 结束。
创建分支就是这么简单,那么切换分支呢?更简单:
- 修改
.git
文件下的HEAD
文件为ref: refs/heads/<分支名称>
。 - 按照分支指向的提交记录将工作区的文件恢复至一模一样。
- 结束。
记住,HEAD
文件指向当前分支的最后一次提交,同时,它也是以当前分支再次创建一个分支时,将要写入的内容。
分支合并
再来说一说合并,首先是Fast-forward,换句话说,如果顺着一个分支走下去可以到达另一个分支的话,那么 Git 在合并两者时,只会简单地把指针右移,因为这种单线的历史分支不存在任何需要解决的分歧,所以这种合并过程可以称为快进(Fast forward)。比如:注意箭头方向,因为每一次提交都有一个指向上一次提交的指针,所以箭头方向向左,更为合理
当在master
分支合并dev
分支时,因为他们在一条线上,这种单线的历史分支不存在任何需要解决的分歧,所以只需要master
分支指向dev
分支即可,所以非常快。
当分支出现分叉时,就有可能出现冲突,而这时Git就会要求你去解决冲突,比如像下面的历史:因为master
分支和dev
分支不在一条线上,即v7
不是v5
的直接祖先,Git 不得不进行一些额外处理。就此例而言,Git 会用两个分支的末端(v7
和 v5
)以及它们的共同祖先(v3
)进行一次简单的三方合并计算。合并之后会生成一个和并提交v8
:注意:和并提交有两个祖先(v7
和v5
)。
分支的变基rebase
把一个分支中的修改整合到另一个分支的办法有两种:merge
和 rebase
。首先merge
和 rebase
最终的结果是一样的,但 rebase
能产生一个更为整洁的提交历史。仍然以上图为例,如果简单的merge
,会生成一个提交对象v8
,现在我们尝试使用变基合并分支,切换到dev
:
$ git checkout dev
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
这段代码的意思是:回到两个分支最近的共同祖先v3
,根据当前分支(也就是要进行变基的分支 dev
)后续的历次提交对象(包括v4
,v5
),生成一系列文件补丁,然后以基底分支(也就是主干分支 master
)最后一个提交对象(v7
)为新的出发点,逐个应用之前准备好的补丁文件,最后会生成两个新的合并提交对象(v4'
,v5'
),从而改写 dev
的提交历史,使它成为 master 分支的直接下游,如下图:现在,就可以回到master
分支进行快速合并Fast-forward了,因为master
分支和dev
分支在一条线上:
$ git checkout master
$ git merge dev
现在的v5'
对应的快照,其实和普通的三方合并,即上个例子中的 v8
对应的快照内容一模一样。虽然最后整合得到的结果没有任何区别,但变基能产生一个更为整洁的提交历史。如果视察一个变基过的分支的历史记录,看起来会更清楚:仿佛所有修改都是在一根线上先后进行的,尽管实际上它们原本是同时并行发生的。