Git分支管理新建与合并

489 阅读9分钟

一、分支的新建与合并

  让我们来看一个简单的分支新建与分支合并的例子,实际工作中你可能会用到类似的工作流。将经历如下步骤:

  1. 开发某个网站。
  2. 为实现某个新的需求,创建一个分支。
  3. 在这个分支上开展工作。

  正在此时,你突然接到一个电话说有个很严重的问题需要紧急修补。 你将按照如下方式来处理:

  1. 切换到你的线上分支(production branch)。
  2. 为这个紧急任务新建一个分支,并在其中修复它。
  3. 在测试通过之后,切换回线上分支,然后合并这个修补分支,最后将改动推送到线上分支。
  4. 切换回你最初工作的分支上,继续工作。

二、新建分支

  首先,我们假设你正在你的项目上工作,并且已经有一些提交。

一个简单提交历史

  现在,你已经决定要解决你的公司使用的问题追踪系统中的名为"#53"问题。 想要新建一个分支并同时切换到那个分支上,你可以运行一个带有-b参数的"git checkout" 命令:

  它是下面两条命令的简写:

$ git branch iss53
$ git checkout iss53

创建一个新分支指针

  继续在#53问题上工作,并且做了一些提交。在此过程中,iss53 分支在不断的向前推进,因为你已经检出到该分支(也就是说,你的HEAD指针指向了iss53 分支)

分支随着工作的进展向前推进

  现在有个紧急问题等待你来解决。有了Git 的帮助,你不必把这个紧急问题和iss53的修改混在一起,你也不需要花大力气来还原关于 #53问题的修改,然后再添加关于这个紧急问题的修改,最后将这个修改提交到线上分支。 你所要做的仅仅是切换回master分支。

  但是,在这么做之前,要留意你的工作目录和暂存区里那些还没有被提交的修改,它可能会和你即将检出的分支产生冲突从而阻止Git切换到该分支。 最好的方法是,在你切换分支之前,保持好一个干净的状态。 有一些方法可以绕过这个问题(即,保存进度(stashing)和修补提交(commit amending),我们会在储藏与清理中看到关于这两个命令的介绍。 现在,我们假设你已经把你的修改全部提交了,这时你可以切换回master分支了:

  这个时候,你的工作目录和你在开始#53 问题之前一模一样,现在你可以专心修复紧急问题了。请牢记:当你切换分支的时候,Git 会重置你的工作目录,使其看起来像回到了你在那个分支上最后一次提交的样子。Git 会自动添加、删除、修改文件以确保此时你的工作目录和这个分支最后一次提交时的样子一模一样。

  接下来,你要修复这个紧急问题。 让我们建立一个针对该紧急问题的分支(hotfix branch),在该分支上工作直到问题解决:

基于master分支的紧急问题分支hotfix branch

  你可以运行你的测试,确保你的修改是正确的,然后将其合并回你的master 分支来部署到线上。你可以使用"git merge"命令来达到上述目的:

  在合并的时候,你应该注意到了"快进(fast-forward)"这个词。由于当前 master分支所指向的提交是你当前提交(有关hotfix的提交)的直接上游,所以Git 只是简单的将指针向前移动。   换句话说,当你试图合并两个分支时,如果顺着一个分支走下去能够到达另一个分支,那么Git在合并两者的时候,只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 “快进(fast-forward)”。

现在,最新的修改已经在master分支所指向的提交快照中,你可以着手发布该修复了。

master被快进到hotfix

  关于这个紧急问题的解决方案发布之后,你准备回到被打断之前时的工作中。 然而,你应该先删除hotfix分支,因为你已经不再需要它了——master 分支已经指向了同一个位置。你可以使用带-d选项的"git branch"命令来删除分支:

  现在你可以切换回你正在工作的分支继续你的工作,也就是针对 #53 问题的那个分支(iss53分支)。

继续在iss53分支上的工作

  在hotfix分支上所做的工作并没有包含到iss53分支中。如果你需要拉取 hotfix所做的修改,你可以使用"git merge master"命令将master分支合并入 iss53 分支,或者你也可以等到iss53分支完成其使命,再将其合并回master分支。

三、分支的合并

  假设你已经修正了#53问题,并且打算将你的工作合并入master分支。 为此,你需要合并iss53分支到master分支,这和之前你合并hotfix 分支所做的工作差不多。只需要检出到你想合并入的分支,然后运行"git merge"命令:

  这和之前合并hotfix分支的时候看起来有一点不一样。 在这种情况下,你的开发历史从一个更早的地方开始分叉开来(diverged)。 因为,master分支所在提交并不是iss53分支所在提交的直接祖先,Git 不得不做一些额外的工作。 出现这种情况的时候,Git 会使用两个分支的末端所指的快照(C4和 C5)以及这两个分支的工作祖先(C2),做一个简单的三方合并。

一次典型合并中所用到的三个快照

  和之前将分支指针向前推进所不同的是,Git 将此次三方合并的结果做了一个新的快照并且自动创建一个新的提交指向它。 这个被称作一次合并提交,它的特别之处在于他有不止一个父提交。

一个合并提交

  需要指出的是,Git 会自行决定选取哪一个提交作为最优的共同祖先,并以此作为合并的基础;这和更加古老的 CVS系统或者 Subversion (1.5 版本之前)不同,在这些古老的版本管理系统中,用户需要自己选择最佳的合并基础。 Git 的这个优势使其在合并操作上比其他系统要简单很多。

  既然修改已经合并进来了,你已经不再需要 iss53 分支了。 现在你可以在任务追踪系统中关闭此项任务,并删除这个分支。

四、遇到冲突时的分支合并

  有时候合并操作不会如此顺利。 如果你在两个不同的分支中,对同一个文件的同一个部分进行了不同的修改,Git 就没法干净的合并它们。如果你对#53问题的修改和有关hotfix 的修改都涉及到同一个文件的同一处,在合并它们的时候就会产生合并冲突,我们再把iss53分支和hotfix创建回来,在这两个分支上修改同一个文件同一个位置:

  有时候合并操作不会如此顺利。 如果你在两个不同的分支中,对同一个文件的同一个部分进行了不同的修改,Git 就没法干净的合并它们。 如果你对#53问题的修改和有关hotfix 的修改都涉及到同一个文件的同一处,在合并它们的时候就会产生合并冲突:

  任何因包含合并冲突而有待解决的文件,都会以未合并状态标识出来。 Git 会在有冲突的文件中加入标准的冲突解决标记,这样你可以打开这些包含冲突的文件然后手动解决冲突。出现冲突的文件会包含一些特殊区段,看起来像下面这个样子:

  这表示HEAD所指示的版本(也就是你的master 分支所在的位置,因为你在运行merge 命令的时候已经检出到了这个分支)在这个区段的上半部分(======= 的上半部分),而 iss53 分支所指示的版本在 ======= 的下半部分。 为了解决冲突,你必须选择使用由 ======= 分割的两部分中的一个,或者你也可以自行合并这些内容。 例如,你可以通过把这段内容换成下面的样子来解决冲突:

aaa
eee
111
222

  上述的冲突解决方案仅保留了其中一个分支的修改,并且 <<<<<<< , ======= , 和 >>>>>>> 这些行被完全删除了。在你解决了所有文件里的冲突之后,对每个文件使用 git add命令来将其标记为冲突已解决。一旦暂存这些原本有冲突的文件,Git 就会将它们标记为冲突已解决。

五、分支管理

  现在已经创建、合并、删除了一些分支,让我们看看一些常用的分支管理工具。

  "git branch"命令不只是可以创建与删除分支。 如果不加任何参数运行它,会得到当前所有分支的一个列表:

  注意hotfix分支前的* 字符:它代表现在检出的那一个分支(也就是说,当前 HEAD 指针所指向的分支)。这意味着如果在这时候提交,hotfix 分支将会随着新的工作向前移动。如果需要查看每一个分支的最后一次提交,可以运行"git branch -v"命令:

  --merged与--no-merged 这两个有用的选项可以过滤这个列表中已经合并或尚未合并到当前分支的分支。 如果要查看哪些分支已经合并到当前分支,可以运行git branch --merged:

  因为之前已经合并了hotfix分支,所以现在看到它在列表中。 在这个列表中分支名字前没有*号的分支通常可以使用"git branch -d" 删除掉;你已经将它们的工作整合到了另一个分支,所以并不会失去任何东西。

  查看所有包含未合并工作的分支,可以运行"git branch --no-merged":

  这里显示了其他分支。 因为它包含了还未合并的工作,尝试使用"git branch -d"命令删除它时会失败:

  如果真的想要删除分支并丢掉那些工作,如同帮助信息里所指出的,可以使用 -D选项强制删除它。