分支的本质
分支:指向一条commit对象链或一条工作记录线的指针;
快照A~D分别表示四次提交(commit),注意提交的顺序为:A -> B -> C -> D:
从图中可以看到每一次提交的对象内都会保存上一次提交的commit id,由此可以从后往前把所有的提交(commit)串起来形成一条链(类似单向链表),这条链就组成了一条完整的分支信息:
- 当版本库中只有一条分支:该分支的最新提交就包含了整条分支的所有内容,代表版本库的当前状态。如上图的快照
D,里面包含了快照A~C中的所有内容,此时快照D中的内容就是整个版本库中的内容:
- 当版本库中有多条分支:每条分支上的最新提交包含了所处分支的全部内容,将各个分支的最新提交进行合并。合并的节点就包含了所有分支的内容,也就是现阶段的版本库本身;如下图中的
d1、m2、t3分别包含了dev、master、test分支上的所有内容:
1.分支 == 指针
情景一:
从图中可以看到:
HEAD为一个指针:指向当前分支;master也为一指针:指向提交;
情景二:
上图中,dev为master分支上创建的新分支,可知:
git在创建新分支时,文件本身不变化,只需要创建一个代表新分支,并指向当前分支的指针;如图中的dev与master指向同一个提交,文件没有发生任何变化;HEAD指向dev分支,表示当前所处分支为dev,相当于执行了:git checkout -b dev后的状态;
相信你已经发现:
HEAD是一个始终指向当前分支的指针
2.HEAD标识符
HEAD文件是一个指向当前所在分支的引用标识符,也可以理解为一个指针,它与分支之间的关系是这样的:
查看HEAD
HEAD文件中并不包含SHA1值(每次提交的commit ID),而是包含一个指向另外一个引用的指针。我们可以查看.git目录下的HEAD文件:
可见HEAD指向的是当前所在的master分支;
当我们通过git checkout -b dev 创建并切换到dev分支后,再次进入.git文件夹查看HEAD,会发现此时HEAD指向了dev:
由此证明了HEAD始终指向当前分支;
当执行git commit命令时,git会创建一个commit对象(比如下图D)。并且将这个commit对象的parent指针指向HEAD所指向的引用(master)指向的提交(也就是C),这样就能形成一条提交链:
我们对于HEAD修改的任何操作,都会被git reflog完整记录下来:
但是手动地修改HEAD文件,这些信息就不会被记录下来,所以十分不建议手动修改git相关的配置文件,而是应该尽量采用命令行的方式来修改。
修改HEAD
实际上,我们可以通过git的底层命令symbolic-ref来实现对HEAD文件内容的修改;
git中的命令可分为两类:高级命令和底层命令;之前介绍的git add等都是高级命令;
要注意格式:refs/heads/develop;
查看ORIG_HEAD文件:
可以发现,ORIG_HEAD里面的SHA1值就是最新一次提交的SHA1值。
查看FETCH_HEAD文件:
里面有两个信息,一个是最新提交的commit ID,另一个是提交信息。
所以,对于git而言commit ID是十分重要的信息,通过这个SHA1值可以回溯或查找需要的提交。
3.git merge原理
过程图解
- 在新分支上进行提交操作
上图表示在dev分支上进行了一次提交,此时:
- 分支master的提交记录由:
A、B和C组成; -
而分支dev的提交历史则由:
A、B、C和D组成; -
对两分支进行合并操作
在master分支上执行:git merge dev将dev分支的内容合并到了master分支上;这种合并方式叫做:Fast-forward,没有冲突,改变的只是master指针的指向;
详细过程
在执行合并操作前:
- 在
master分支上查看该分支的提交记录:
- 在
dev分支上查看该分支的提交记录:
可以看到dev分支只是比master分支多进行了一次提交(dev1),两分支状态如下图所示:
执行合并操作:
先切换到master分支,然后执行git merge dev合并dev分支:
可以看到使用了Fast-forward方式进行合并,合并后两分支状态如下图所示:
合并后,HEAD同时指向了master和dev分支;并且master和dev分支的提交历史完全一致;这就说明了:使用Fast-forward(快进合并)方式进行分支合并,只会改变master分支指针的指向;
4.Fast-forward
- 默认情况下,合并分支时
git会使用Fast-forward模式; - 在这种模式下,删除分支会丢弃分支信息;
- 进行分支合并操作时加上'--no-ff'参数会禁止使用
Fast-forward方式,这样会多出一次提交记录;
ff表示Fast-forward
具体演示如下:
使用Fast-forward
首先,查看master分支上最新的3次提交:
此时两分支的状态为:
切换回master分支,通过git merge dev合并dev分支,此时默认采用Fast-forward方式:
可以看到合并后,master直接指向了dev的最新提交,并没有产生新的提交。合并后两分支的状态如下所示:
由此验证了Fast-forward方式只会改变分支指针的指向。
禁用Fast-forward
合并时可以通过:
git merge --no-ff dev
禁用Fast-forward模式。
- 继续在
dev分支新增一次提交:dev3。然后查看dev分支上最新的3次提交:
- 不修改
master分支,查看其最新的3次提交:
此时两个分支的状态为:
- 然后,在
master分支上不使用Fast-forward方式合并dev分支。合并命令采用:
git merge --no-ff dev
执行后进入如下的vim编辑器界面,要求我们填写提交注释:
这说明不使用Fast-forward方式合并分支,会触发了一次提交。填写提交注释后完成提交操作,合并完成后,查看master分支的提交记录:
可以发现禁用了Fast-forward模式的合并会比dev分支多产生一次提交:Merge branch 'dev',即使合并后的内容是一样的。此时两分支的状态为:
由此验证了,禁用Fast-forward方式合并,会多出一个表示合并的提交记录。
5.合并冲突¶
合并的两分支只有一条分支发生了改变,并且其中一分支是基于另一分支创建的。比如上述的master与dev分支,两分支没有分岔,此时不会出现合并冲突;git会通过Fast-forward方式自动完成合并操作;
但是,当合并的两分支都发生改变时,即分支出现分岔,如下图所示。此时就需要解决冲突后手动合并分支了:
具体演示如下:
合并前
首先,分别对两分支上的test.txt文件进行修改,并分别将修改提交到各自的分支;
- 在master分支上进行新的提交:
mas3,然后查看文件test.txt内容和分支提交记录:
- 在
dev分支上进行新的提交:dev1,然后查看文件test.txt内容和分支提交记录:
可见两分支的提交记录只有最新一次提交不一样:
合并后
在master分支上,通过git merge dev合并dev分支时,会在共同修改的test.txt文件中出现合并冲突,如下图所示:
出现冲突的原因为:两个分支都对同一个文件test.txt进行了修改,git合并时并不知道以哪个分支的修改为标准。所以不能采用Fast-forward方式自动合并,需要解决冲突,手动合并。
手动合并过程
手动合并操作需要分如下三步进行:
- 第一步:选择需要保留的内容,手动解决合并冲突
此时进入发生合并冲突的test.txt文件:
会出现典型的冲突呈现方式(此时HEAD指向的是master分支),其中:
<<<HEAD与>>>dev之间的内容表示:两分支上test.txt文件的不同之处;<<<HEAD与===之间的内容表示:当前分支master对test.txt文件的修改;===与>>>dev之间的内容表示:dev分支对test.txt文件的修改;
此时只需要在test.txt中保留想要的内容即可,例如:将两分支对test.txt的修改都进行保存,删除3、5、7行多余的内容:
除此之外,还可以通过git mergetool指令,调用vimdiff工具进入vim编辑器,来解决test.txt文件的冲突:
在实际开发中,我们很少手动进行合并,而是借助于一些工具来实现。
- 第二步:使用
git add test.txt将手动解决冲突时对test.txt的修改提交到暂存区;
编辑完毕后,可以看到此时仍然处于合并过程中(MERGING)。通过git status 查看状态,发现手动解决冲突时对test.txt文件的修改操作还在工作区中,需要通过git add test.txt将这一修改纳入暂存区,继续进行合并:
- 第三步:通过
git commit -m合并注释将手动解决冲突时对test.txt的修改进行提交,完成合并操作;
如果需要填写较多的合并注释,可以通过git commit进入vim编辑器编辑。提交后,完成整个手动合并过程。
完成手动合并分支后,查看两分支的提交历史:
master分支上的提交历史:
dev分支上的提交历史:
可以发现此时两分支转变为了可以通过Fast forward方式合并的形式了,如图所示:
手动解决冲突前:
手动解决冲突后:
同步两分支
若想dev和master分支上的内容进行同步,只需要在dev分支中通过git merge master 合并master分支即可。此时就可以使用Fast-forward方式进行合并了,合并结果如下图所示:
验证:
合并后,查看dev分支的提交历史:
可以看到HEAD同时指向dev与master,即三个指针都指向了最新的一次提交,符合上述分析得出的结论;
经过上面的讨论,不难看出:合并分支的实质就是不同提交的合并,以及HEAD和分支指针的移动;
以上就是今天介绍的本地分支重要操作,相信看到这里的你已经对git本地分支的操作了如指掌了。在下一讲中将介绍git最神奇的功能:版本回退。俗话说得好:世上没有后悔药。但是在git中,就存在所谓的"后悔药"! 那么我们下一节再见。

















































