我们正在开发某个项目,为了实现某个项目,我们创建了分支task_113,正在这个分支上开展工作。这个时候收到反馈说有个很严重的bug需要立即处理。这个时候,你应该立即切换到线上分支,为这个bug建立新的分支issue_005,并在这个分支上修复它。在测试通过后,切换到线上分支来合并issue_005,最后将改动推到线上。然后切换回task_113上继续做新的需求。

merge 合并

enter image description here

开始在task 113上工作:

在没开始做任务前,我们仓库的结构如上。现在我们开始创建task_113分支:

$ git checkout -b task_113
Switched to a new branch 'task_113'

这个时候我们的git仓库结构如下: enter image description here

这个时候我们在当前分支上做了些工作,并且提交到了版本库后,我们的git结构如下: enter image description here

收到紧急问题issue 005

这个时候我们收到反馈的bug后,我们立即切到master分支上,从master分支创建新的issue_005分支开始解决线上的bug:

在切换到master之前,需要确认所有修改已经提交到版本库中。

# 切换到master分支
git checkout master
Switched to branch 'master'

# 建立issue_005分支处理bug
git checkout -b issue_005
Switched to a new branch 'issue_005'

下面的图是我们现在git仓库的状态: enter image description here 当issue_005分支上的修改测试完成后,我们需要到master上将其合并。

git checkout master
Switched to branch 'master'

git merge issue_005
Updating 04132ad..86e4899
Fast-forward
 about.html | 1 +
 1 file changed, 1 insertion(+)

Fast-forward: 表示的是在合并两个分支的时候,如果顺着一个分支走下去能够到达另一个分支,那么Git在合并两者的时候,只会简单的将指针向前推进(指针右移),因为这种情况没有需要解决的分歧,这就叫做快进。

这个紧急问题处理完成后,我们就可以将issue_005这个分支删除,因为我们再也用不上它了。删除完成后,我们继续回到task_113分支上继续我们的工作:

git branch -d issue_005
Deleted branch issue_005 (was 86e4899).

git checkout task_113
Switched to branch 'task_113'

git commit -a -v -m 'update index.html[task 113]'
[task_113 76e0e88] update index.html[task 113]
 1 file changed, 1 insertion(+)

这个时候我们再来看看我们的仓库结构: enter image description here

合并分支

task_113的需求我们已经完成了,并且测试通过,这个时候我们应该开始将它合并到master上准本发布到线上环境了。

git checkout master
Switched to branch 'master'

git merge task_113
Merge made by the 'recursive' strategy.
 index.html | 2 ++
 1 file changed, 2 insertions(+)

这次的合并并没有像上次合并那样,出现Fast-forward。因为master分支所在提交并不是task_113分支所提交的直接祖先,在这种情况下,Git会使用两个分支末端所指的快照(c4和c5)以及这两个分支的共同祖先c2来一个简单的三方合并。合并过后的仓库结构为: enter image description here

这个时候我们再把task_113分支删除了就好了。我们再看看git log --graph的结果: enter image description here

从上图可以看出,和我们画的结构图完全一致,接下来我们再介绍下另外一种方法变基

变基

enter image description here 上图是我们当前项目结构,如果这个时候用合并的方式来整合分支的话,最终结构为: enter image description here 整合分支最容易的方法就是合并分支。它会把两个分支的最新快照以及二者之间共同祖先(如果能过快进的话,则直接快进)进行三方合并来生成新的快照。

而变基则是提取出C4中引入的补丁和修改,在C3的基础上应用一次。可以使用rebase命令将提交的某一分支上的所有修改都移至另一分支上。

$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command

这个时候我们回到master上,进行一次快进合并:

git merge work
Updating 3462092..ece0ba5
Fast-forward
 work.html | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 work.html

合并过后,我们的仓库结构为: enter image description here

还是用git log --graph来看看最后的结构: enter image description here

其实变基和合并一样,最终都生成了一次新的提交,但是使用变基我们让提交历史变得更加简介,你可以看到经过变基的分支的历史记录中,尽管开发工作是并行的,但是它们看起来就好像是穿行一样,提交历史是一条直线没有分叉。在简单的项目中,可能有点分叉也没什么,但是在一个多人维护的大型项目中,分叉多了项目的维护工作就会变得越来越复杂。

一般情况下我们用变基也就是为了确保向远程分支推送时能够保持提交历史的简洁:当我们在github上修复别人项目的bug时,我们首先在自己的分支里开发,当开发完成后,你需要将你的代码变基到origin/master上,然后再向主项目提交修改。这样的话,该项目的维护者就不再需要进行整合工作,而是快速合并即可。

变基的风险

使用变基的时候一定要记住一条规则:不要对在你的仓库外有副本的分支执行变基。 变基操作本质上是丢弃一些现有的提交,然后相应的建一些内容一样但实际不同的提交。如果你将提交推送到远程仓库上,而其他人也从该仓库拉取提交并基于你的分支创建了新的分支做后续工作,这个时候你用git rebase命令重新整理了提交再此推送,其他人不得不再次将它们手头的工作与你的提交进行整合,如果他们还要拉去并整合他们修改过的提交,整个开发工作就会变得异常的混乱。想想去年美国因为同事频繁使用git push -f推送到远程分支而被同事枪击的新闻吧,这样你就能记住这条规则了。