接上节,此时的dev分支与master分支的进度就不同了,因此须要将dev分支与master分支同步。这里须要的就是合并分支的操做,你们应该都知道用git merge
或者git rebase
。git
merge,即「合并」。算法
当出现咱们上面图中的那种状况时,时间线只有一条,dev分支只不过是落后master分支而已。此时咱们在dev分支上执行git merge maseter
时,git就仅仅会把dev分支指针移动到master分支所在的位置,伪装合并了,就变成了这样:segmentfault
这种merge的方式叫作「fast-forward」,也是git默认的merge方式。app
若是状况改变了,举个例子:咱们在开发过程当中,一直使用的是master分支,这时出了一个很严重的bug,咱们就须要创建一个叫topic的分支来处理这个bug,但主要的功能工期又不能拖,因此master分支与topic分支就同时向前推动,此时时间线如图所示:工具
(此图出自git本身的帮助文件,使用命令git help merge
便可看到。想看其余命令的帮助就git help <command>
便可):测试
这时候,topic上的bug修改完毕,须要合并回master分支,须要的操做为:切换到master分支git checkout master
,合并devgit merge dev
。spa
注意,这时候的这两条分支是真正的「分支」了,他们在时间线上岔开了,各自分支都有本身独有的东西。翻译
由于此时的topic分支的末端并不在master分支的父端,须要把不一样的修改同步起来,单纯的指针移动不能完成这一步,fast-forward方式也就不可能实现了。设计
这时,git便会将两个分支不一样的地方取出,合并成一个commit,而后把master指针指向这个新的commit(就是在master上生成了一次commit)。这样,topic分支上的修改就同步到master分支上了。此时分支状况如图:3d
BTW,可以进行fast-forward的merge状况下,也能够经过增长--no-ff
命令来强制不使用fast-forward模式。假如仍是回到咱们master-dev两个分支的例子,master领先于dev分支:
这时咱们不用fast-forward,在落后的dev分支上执行git merge master --no-ff
,git会在dev上强行建立一个commit,把master分支上不一样于dev的修改加进去,分支线就会变成这种诡异的样子:
(本手残渣画图实在是很差看,就直接用GUI工具source tree上的状况截图了)
底层上,git会把将要合并的两个分支的各个commit快照进行差别比较,求出它们之间的最长公共子序列,并把公共子序列从中去掉,得出各自存在两个分支中的不一样修改,并将其合并成一个commit放在当前分支的顶端。
这里仅仅说明一点原理,具体实现方式与算法本人也只是懂一点皮毛,只要明白fast-forward与不使用的状况下merge,分支会产生什么样的状况,用来工做就没有任何问题了。
除了--no-ff
,merge还有另外一种合并的方式:--squash
。这种方法在符合fast-forward的状况下依然会执行fast-forward方式,不会有任何改变。但当遇到以下状况时:
假设咱们要将topic合并到master上来,squash方式会集中topic的「A、B、C」三次commit中的修改合并,并添加到暂存区中。
这时master分支与topic分支不会有任何的变更,只不过暂存区中会被添加topic上修改的集合(暂存区=A+B+C)。
这时咱们就能够查看暂存区中的内容是否是符合一次提交,以后commit就能够了。git help merge
里是这么描述的:「create a single commit instead of doing a merge」,结合上面的讲解就能够理解squash的意思了吧。
fast-forward中是没有冲突的(不明白为啥没冲突的面壁思过)。而在其余状况时,若是两个分支同时有对同一个文件(行)的修改,就会产生冲突。这时git会在产生冲突的文件里写一堆这样的东西:
上面的「<<<<<<<< HEAD」直到「========」的部分,就是当前分支的修改(看到HEAD就知道是指向当前分支的指针了)。而「========」到下面的「>>>>>>> dev」的部分天然就是dev分支合并过来的修改啦。
这时须要你仔细对比冲突,若是跟同事合做的话就要商量好,而后把「<<<<< HEAD ===== >>>>> dev」之类git给你加上的东西和不须要的修改部分删掉。接下来git status
就会看到下面的提示:
上面绿的的东西天然就在暂存区了,这些表明dev分支上并不冲突的部分。下面的红色文件就表明你冲突的文件,当你修改以后须要走一遍add -> commit的流程(这个commit能够不指定commit message),也能够直接执行git commit -a
,就完成merge建立新commit的过程了。
涉及操做:git merge <branch>
, git merge <branch> --no-ff
, git merge <branch> --squash
, git checkout <branch>
, git help <command>
除了merge,git还有一种分支合并的方式,叫作git rebase。
rebase,就是「re」与「base」结合,官方译名「变基」(咖喱gaygayʕ •ᴥ•ʔ)。这个「变基」的含义从字面上确实不是很好理解,先来看一下rebase示例:
回到咱们master-dev两个分支的例子,master领先于dev分支:
这时候咱们在dev分支上执行git rebase master
,master便与dev合并了,如图所示:
此时你心里OS:这不是跟fast-forward模式下的merge同样么?莫急莫急,咱们再看一下出现这样状况下的分支(做者偷懒拿前面图糊弄了嘿嘿嘿):
不着急解释原理,咱们先看看在topic上执行git rebase master
的结果(就是将master合并到topic上):
看图得知:master上的「F」「G」两次提交,变成了topic分支的父节点,整个分支又从新合成为一条时间线。在本例中,你能够想象「biu」的一下把topic分支拔下来,而后「pu」的一下把它插到了master的顶端(大雾)。
固然,git确定不是像上面那样「biu」「pu」地操做分支的。
git help rebase中是这样描述git rebase
的:
git-rebase - Reapply commits on top of another base tip
翻译一下,就是「将你的commit们在另外一个基准点上从新应用」。注意这里的「reapply」,git并不会直接移动commit自己,而是会为须要rebase的分支上的commit分别建立一个patch「补丁」,而后将patch在基准点上依次应用,重建出一条时间线。
能够参照上面的两张图梳理一下流程:
当咱们在topic分支上执行git rebase master
时,表明了咱们要将咱们当前的分支(topic)应用到指定的master分支上。
此时topic与master的共同父节点是「E」,topic的特有commit是「A」「B」「C」,git就会按照时间点,分别建立「A」「B」「C」的patch「A'」「B'」「C'」,而后将topic分支的基准点设置为master分支的顶点「G」(「变基」了!),依次将「A'」「B'」「C'」Apply到「G」上。
如今是否是理解「rebase变基」是什么意思了!
rebase产生的冲突与merge实际上是相同的。但因为rebase操做会按照patch一个个打补丁上去,每打一个都有可能会产生冲突,跟merge的产生一个commit这种一次性操做不同,解决冲突以后也就不是提交commit,而是git add <file>
以后执行git rebase --continue
。
也就是「打一个补丁,解决一次冲突,而后继续下一个补丁」的过程。
若是你不耐烦了,也能够git rebase --abort
直接不进行rebase了。
涉及操做:git rebase <branch>
, git rebase <branch> --onto <commit id>
, git rebase --continue
, git rebase --abort
上面讲了好多关于分支的东西,可能会让人困惑:分支涉及到的东西这么多,自己又复杂,多分支处理也复杂,应该怎样利用分支才好?分支合并的策略选哪种呢?这里我说一下我的的看法:
首先,git保存的是修改这一点,能够很清楚的让咱们知道代码发生了哪些改变。在这样的状况下,咱们利用commit时间线就能够明确地区分哪一个人在什么时间作了什么事情,也就是给了你「查看历史」与「修改历史」的权力,这对一个软件项目来讲是相当重要的。
有关git多个分支的设计,实际上是很是巧妙的。多个分支解决了以代码自己不一样版本、不一样功能或不一样目的的开发方向(好比开发新功能或改bug,又暂时不想修改主要版本)开发时的代码版本管理问题,可以很方便地管理工做区的文件内容。
因此,我对多分支系统利用的理解是这样的:
有关git代码合并策略的选择,虽然git提供了很是丰富的方法,但一个team使用的方法应该大致固定成同一个,这样能避免不少混乱,而后在适当的时机使用不一样的策略。在《Pro Git》这本书中总结的就很好,我摘下来总结下:
选择merge仍是rebase取决于你对 commit历史时间线的定义。有两种观点:第一种认为,commit历史应该显示的是何时具体发生了什么事,好比分支的建立与合并过程,有哪些分支,分别合并在了什么地方等等。另外一种认为,commit历史应该显示的是这个项目经历过的状态,而不考虑具体的分支构建过程。
每个团队,每个人都是不一样的。git做为一个如此强大的工具提供给了你解决任何问题的思路,你就要考虑清楚你的团队到底须要什么。
一个一箭双鵰的方法就是:rebase你本地的修改,push到多人环境中时用merge。
乱糟糟的时间线&&完整的分支结构 vs 清爽的一条线&&舍弃修改过程,看你团队取舍咯。