💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
### 2 Git 分支 - 分支的新建与合并 #### 分支的新建与合并 现在让我们来看一个简单的分支与合并的例子,实际工作中大体也会用到这样的工作流程: 1. 开发某个网站。 2. 为实现某个新的需求,创建一个分支。 3. 在这个分支上开展工作。 假设此时,你突然接到一个电话说有个很严重的问题需要紧急修补,那么可以按照下面的方式处理: 1. 返回到原先已经发布到生产服务器上的分支。 2. 为这次紧急修补建立一个新分支,并在其中修复问题。 3. 通过测试后,回到生产服务器所在的分支,将修补分支合并进来,然后再推送到生产服务器上。 4. 切换到之前实现新需求的分支,继续工作。 [分支的新建与切换](https://git-scm.com/book/zh/v1/Git-%E5%88%86%E6%94%AF-%E5%88%86%E6%94%AF%E7%9A%84%E6%96%B0%E5%BB%BA%E4%B8%8E%E5%90%88%E5%B9%B6#分支的新建与切换) 首先,我们假设你正在项目中愉快地工作,并且已经提交了几次更新(见图 3-10)。 ![](https://box.kancloud.cn/1662e7476b52eb70d32af03c4fde08de_229x115.png) 图 3-10. 一个简短的提交历史 现在,你决定要修补问题追踪系统上的 `#53` 问题。顺带说明下,Git 并不同任何特定的问题追踪系统打交道。这里为了说明要解决的问题,才把新建的分支取名为 `iss53`。要新建并切换到该分支,运行 `git checkout` 并加上 `-b` 参数: ~~~ $ git checkout -b iss53 Switched to a new branch 'iss53' ~~~ 这相当于执行下面这两条命令: ~~~ $ git branch iss53 $ git checkout iss53 ~~~ 图 3-11 示意该命令的执行结果。 ![](https://box.kancloud.cn/53ab8a390272f9f485274531287c1db4_229x186.png) 图 3-11. 创建了一个新分支的指针 接着你开始尝试修复问题,在提交了若干次更新后,`iss53` 分支的指针也会随着向前推进,因为它就是当前分支(换句话说,当前的 `HEAD` 指针正指向 `iss53`,见图 `3-12`): ~~~ $ vim index.html $ git commit -a -m 'added a new footer [issue 53]' ~~~ ![](https://box.kancloud.cn/310f7709a6dedf09df88edd8ba44e654_309x187.png) 图 3-12. iss53 分支随工作进展向前推进 现在你就接到了那个网站问题的紧急电话,需要马上修补。有了 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(-) ~~~ ![](https://box.kancloud.cn/a9838749bd1d0dd4d47e4b968da644e1_311x253.png) 图 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)。 ![](https://box.kancloud.cn/a2b64d93bcf6602a77be797ff54ff682_310x305.png) 图 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(+) ~~~ ![](https://box.kancloud.cn/27fb719d638b8a252244988fa75909bb_388x246.png) 图 3-15. iss53 分支可以不受影响继续推进。 值得注意的是之前 `hotfix` 分支的修改内容尚未包含到 `iss53` 中来。如果需要纳入此次修补,可以用 `git merge master` 把 `master` 分支合并到 `iss53`;或者等` iss53` 完成之后,再将 `iss53` 分支中的更新并入 `master`。 [分支的合并](https://git-scm.com/book/zh/v1/Git-%E5%88%86%E6%94%AF-%E5%88%86%E6%94%AF%E7%9A%84%E6%96%B0%E5%BB%BA%E4%B8%8E%E5%90%88%E5%B9%B6#分支的合并) 在问题 `#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 用于合并的三个提交对象: ![](https://box.kancloud.cn/4c0ba144a87b572249525c6ff909c443_500x431.png) 图 3-16. Git 为分支合并自动识别出最佳的同源合并点。 这次,Git 没有简单地把分支指针右移,而是对三方合并后的结果重新做一个新的快照,并自动创建一个指向它的提交对象`(C6)`(见图 3-17)。这个提交对象比较特殊,它有两个祖先(`C4` 和 `C5`)。 值得一提的是 `Git` 可以自己裁决哪个共同祖先才是最佳合并基础;这和 `CVS` 或 `Subversion`(1.5 以后的版本)不同,它们需要开发者手工指定合并基础。所以此特性让 `Git` 的合并操作比其他系统都要简单不少。 ![](https://box.kancloud.cn/0aaa8c8f182d224b37e86a21617008e4_441x234.png) 图 3-17. Git 自动创建了一个包含了合并结果的提交对象。 既然之前的工作成果已经合并到 master 了,那么 iss53 也就没用了。你可以就此删除它,并在问题追踪系统里关闭该问题。 ~~~ $ git branch -d iss53 ~~~ [遇到冲突时的分支合并](https://git-scm.com/book/zh/v1/Git-%E5%88%86%E6%94%AF-%E5%88%86%E6%94%AF%E7%9A%84%E6%96%B0%E5%BB%BA%E4%B8%8E%E5%90%88%E5%B9%B6#遇到冲突时的分支合并) 有时候合并操作并不会如此顺利。如果在不同的分支中都修改了同一个文件的同一部分,Git 就无法干净地把两者合到一起(译注:逻辑上说,这种问题只能由人来裁决。)。