如今让咱们来看一个简单的分支与合并的例子,实际工做中大致也会用到这样的工做流程:html
假设此时,你忽然接到一个电话说有个很严重的问题须要紧急修补,那么能够按照下面的方式处理:git
首先,咱们假设你正在项目中愉快地工做,而且已经提交了几回更新(见图 3-10)。github
图 3-10. 一个简短的提交历史vim
如今,你决定要修补问题追踪系统上的 #53 问题。顺带说明下,Git 并不一样任何特定的问题追踪系统打交道。这里为了说明要解决的问题,才把新建的分支取名为 iss53。要新建并切换到该分支,运行 git checkout
并加上 -b
参数:服务器
$ git checkout -b iss53 Switched to a new branch 'iss53'
这至关于执行下面这两条命令:工具
$ git branch iss53 $ git checkout iss53
图 3-11 示意该命令的执行结果。测试
图 3-11. 建立了一个新分支的指针网站
接着你开始尝试修复问题,在提交了若干次更新后,iss53
分支的指针也会随着向前推动,由于它就是当前分支(换句话说,当前的 HEAD
指针正指向 iss53
,见图 3-12):this
$ vim index.html $ git commit -a -m 'added a new footer [issue 53]'
图 3-12. iss53 分支随工做进展向前推动spa
如今你就接到了那个网站问题的紧急电话,须要立刻修补。有了 Git ,咱们就不须要同时发布这个补丁和iss53
里做出的修改,也不须要在建立和发布该补丁到服务器以前花费大力气来复原这些修改。惟一须要的仅仅是切换回 master
分支。
不过在此以前,留心你的暂存区或者工做目录里,那些尚未提交的修改,它会和你即将检出的分支产生冲突从而阻止 Git 为你切换分支。切换分支的时候最好保持一个清洁的工做区域。稍后会介绍几个绕过这种问题的办法(分别叫作 stashing 和 commit amending)。目前已经提交了全部的修改,因此接下来能够正常转换到 master
分支:
$ git checkout master Switched to branch 'master'
此时工做目录中的内容和你在解决问题 #53 以前如出一辙,你能够集中精力进行紧急修补。这一点值得牢记:Git 会把工做目录的内容恢复为检出某分支时它所指向的那个提交对象的快照。它会自动添加、删除和修改文件以确保目录的内容和你当时提交时彻底同样。
接下来,你得进行紧急修补。咱们建立一个紧急修补分支 hotfix
来开展工做,直到搞定(见图 3-13):
$ git checkout -b hotfix Switched to a new branch 'hotfix' $ vim index.html $ git commit -a -m 'fixed the broken email address' [hotfix 3a0874c] fixed the broken email address 1 files changed, 1 deletion(-)
图 3-13. hotfix 分支是从 master 分支所在点分化出来的
有必要做些测试,确保修补是成功的,而后回到 master
分支并把它合并进来,而后发布到生产服务器。用 git merge
命令来进行合并:
$ git checkout master $ git merge hotfix Updating f42c576..3a0874c Fast-forward README | 1 - 1 file changed, 1 deletion(-)
请注意,合并时出现了“Fast forward”的提示。因为当前 master
分支所在的提交对象是要并入的 hotfix
分支的直接上游,Git 只需把 master
分支指针直接右移。换句话说,若是顺着一个分支走下去能够到达另外一个分支的话,那么 Git 在合并二者时,只会简单地把指针右移,由于这种单线的历史分支不存在任何须要解决的分歧,因此这种合并过程能够称为快进(Fast forward)。
如今最新的修改已经在当前 master
分支所指向的提交对象中了,能够部署到生产服务器上去了(见图 3-14)。
图 3-14. 合并以后,master 分支和 hotfix 分支指向同一位置。
在那个超级重要的修补发布之后,你想要回到被打扰以前的工做。因为当前 hotfix
分支和 master
都指向相同的提交对象,因此 hotfix
已经完成了历史使命,能够删掉了。使用 git branch
的 -d
选项执行删除操做:
$ git branch -d hotfix Deleted branch hotfix (was 3a0874c).
如今回到以前未完成的 #53 问题修复分支上继续工做(图 3-15):
$ git checkout iss53 Switched to branch 'iss53' $ vim index.html $ git commit -a -m 'finished the new footer [issue 53]' [iss53 ad82d7a] finished the new footer [issue 53] 1 file changed, 1 insertion(+)
图 3-15. iss53 分支能够不受影响继续推动。
值得注意的是以前 hotfix
分支的修改内容还没有包含到 iss53
中来。若是须要归入这次修补,能够用 git merge master
把 master 分支合并到 iss53
;或者等 iss53
完成以后,再将 iss53
分支中的更新并入master
。
在问题 #53 相关的工做完成以后,能够合并回 master
分支。实际操做同前面合并 hotfix
分支差很少,只需回到 master
分支,运行 git merge
命令指定要合并进来的分支:
$ git checkout master $ git merge iss53 Auto-merging README Merge made by the 'recursive' strategy. README | 1 + 1 file changed, 1 insertion(+)
请注意,此次合并操做的底层实现,并不一样于以前 hotfix
的并入方式。由于此次你的开发历史是从更早的地方开始分叉的。因为当前 master
分支所指向的提交对象(C4)并非 iss53
分支的直接祖先,Git 不得不进行一些额外处理。就此例而言,Git 会用两个分支的末端(C4 和 C5)以及它们的共同祖先(C2)进行一次简单的三方合并计算。图 3-16 用红框标出了 Git 用于合并的三个提交对象:
图 3-16. Git 为分支合并自动识别出最佳的同源合并点。
此次,Git 没有简单地把分支指针右移,而是对三方合并后的结果从新作一个新的快照,并自动建立一个指向它的提交对象(C6)(见图 3-17)。这个提交对象比较特殊,它有两个祖先(C4 和 C5)。
值得一提的是 Git 能够本身裁决哪一个共同祖先才是最佳合并基础;这和 CVS 或 Subversion(1.5 之后的版本)不一样,它们须要开发者手工指定合并基础。因此此特性让 Git 的合并操做比其余系统都要简单很多。
图 3-17. Git 自动建立了一个包含了合并结果的提交对象。
既然以前的工做成果已经合并到 master
了,那么 iss53
也就没用了。你能够就此删除它,并在问题追踪系统里关闭该问题。
$ git branch -d iss53
有时候合并操做并不会如此顺利。若是在不一样的分支中都修改了同一个文件的同一部分,Git 就没法干净地把二者合到一块儿(译注:逻辑上说,这种问题只能由人来裁决。)。若是你在解决问题 #53 的过程当中修改了 hotfix
中修改的部分,将获得相似下面的结果:
$ git merge iss53 Auto-merging index.html CONFLICT (content): Merge conflict in index.html Automatic merge failed; fix conflicts and then commit the result.
Git 做了合并,但没有提交,它会停下来等你解决冲突。要看看哪些文件在合并时发生冲突,能够用 git status
查阅:
$ git status On branch master You have unmerged paths. (fix conflicts and run "git commit") Unmerged paths: (use "git add <file>..." to mark resolution) both modified: index.html no changes added to commit (use "git add" and/or "git commit -a")
任何包含未解决冲突的文件都会以未合并(unmerged)的状态列出。Git 会在有冲突的文件里加入标准的冲突解决标记,能够经过它们来手工定位并解决这些冲突。能够看到此文件包含相似下面这样的部分:
<<<<<<< HEAD <div id="footer">contact : email.support@github.com</div> ======= <div id="footer"> please contact us at support@github.com </div> >>>>>>> iss53
能够看到 =======
隔开的上半部分,是 HEAD
(即 master
分支,在运行 merge
命令时所切换到的分支)中的内容,下半部分是在 iss53
分支中的内容。解决冲突的办法无非是两者选其一或者由你亲自整合到一块儿。好比你能够经过把这段内容替换为下面这样来解决:
<div id="footer"> please contact us at email.support@github.com </div>
这个解决方案各采纳了两个分支中的一部份内容,并且我还删除了 <<<<<<<
,=======
和 >>>>>>>
这些行。在解决了全部文件里的全部冲突后,运行 git add
将把它们标记为已解决状态(译注:实际上就是来一次快照保存到暂存区域。)。由于一旦暂存,就表示冲突已经解决。若是你想用一个有图形界面的工具来解决这些问题,不妨运行 git mergetool
,它会调用一个可视化的合并工具并引导你解决全部冲突:
$ git mergetool This message is displayed because 'merge.tool' is not configured. See 'git mergetool --tool-help' or 'git help config' for more details. 'git mergetool' will now attempt to use one of the following tools: opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare vimdiff emerge Merging: index.html Normal merge conflict for 'index.html': {local}: modified file {remote}: modified file Hit return to start merge resolution tool (opendiff):
若是不想用默认的合并工具(Git 为我默认选择了 opendiff
,由于我在 Mac 上运行了该命令),你能够在上方"merge tool candidates"里找到可用的合并工具列表,输入你想用的工具名。咱们将在第七章讨论怎样改变环境中的默认值。
退出合并工具之后,Git 会询问你合并是否成功。若是回答是,它会为你把相关文件暂存起来,以代表状态为已解决。
再运行一次 git status
来确认全部冲突都已解决:
$ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: index.html
若是以为满意了,而且确认全部冲突都已解决,也就是进入了暂存区,就能够用 git commit
来完成此次合并提交。提交的记录差很少是这样:
Merge branch 'iss53' Conflicts: index.html # # It looks like you may be committing a merge. # If this is not correct, please remove the file # .git/MERGE_HEAD # and try again. #
若是想给未来看此次合并的人一些方便,能够修改该信息,提供更多合并细节。好比你都做了哪些改动,以及这么作的缘由。有时候裁决冲突的理由并不直接或明显,有必要略加注解。