在项目的开发过程中,分支与合并是很常见的操作。以我曾经管理过的一个长期的项目为例,由于项目的总体需求规模比较庞大,全部开发完成需要很长的时间,而且随着业务的增长后续不断会有需求的边界也会逐步的扩张。为了规避风险因此我们采用敏捷的开发模式,同时将需求按优先级做迭代增量开发和交付,持续和尽早获得反馈。短期内(3个月)项目就上线了,随后每个月都会发布一个包含了新需求的版本。在这中间可能也会有修复紧急feature、issue的临时发布。
在上面提到的项目实例,假设新的需求开发、生产环境紧急的bug修复工作等都是直接在master分支(Git在新建一个仓库时,默认只有一个名为master的分支)内进行,会发生什么样的后果?
(注:图-1 带箭头实线表示的不是提交方向、时间方向,而是指针,指向当前提交的上一次提交(parent))
实例中最容易发生的后果之一,如图-1所示,master最近一次的提交C2是发布到生产环境的版本。随后开发人员提交了包含了新特性的代码C3以及C4开始做集成测试。此时在生产环境发现了一个bug需要紧急修复并发布。但是现在master内的代码已经包含了C3等不能发布到生产环境的代码提交。这种情况下要么只有等C3测试通过可以发布到生产环境时,和bugfix一起发布;又或者将版本回撤到C2,修复bug后将重新提交C3和C4。两种方案都是非常差的选择。
为了规避这类问题,就需要使用分支功能将新工作从开发的主线上分离开来,以免影响开发主线。例如生产环境单独使用master分支,其它的工作如新需求开发、紧急修复等分别使用不同的分支,在工作完成后需要发布时再和并回master。可以根据项目的实际情况来制定分支策略和发布管理的工作流程。
1. 分支的创建
在git中创建一个分支很简单:git branch
。在上面的例子中,使用分支的简单方案就是,在C2阶段,将开发C3等特性的工作放入新的分支去做。创建一个分支:
$ git branch feature-c3
检出分支:
git checkout feature-c3
检出分支时会将该分支的最后一次提交检出到暂存区和工作区。另外,也可以使用git checkout -b feature-c3
命令来创建并立即检出这个新分支,这条命令等效于先后执行上面的两条命令。
接下来我们来模仿在feature-c3分支中增加新的特性:
- 修改README.md文件,新增一行内容并保存:This line is demo for new feature c3.
- 新增一个featurec4.txt,文件内容保存一行文字:This line is demo for new feature c4.
然后分别使用add、commit将修改提交到版本库。
此时C2版本发布到生产环境的系统发现了严重的bug #07,需要紧急修复和发布。目前C3和C4都是在feature-c3上提交的,master仍然是c2的源码版本。所以我们需要使用master分支的版本来修复bug并发布。
首先运行git checkout master
命令检出master分支。
然后创建一个基于master的hotfix分支git checkout -b hotfix0.1_07
,然后修改README.md文件,在第6行保存段文字“This is a hotfix for v0.1.0”,然后add、commit文件。
2. 分支的合并
假设上面修改了README.md文件后就修复了bug #07并通过了验收测试,现在要将hotfix分支的修改合并回master并发布到生产环境。
首先要将分支切换回master:git checkout master
。现在如果查看README.md文件会发现内容仍然只有5行(C2时的版本)。然后运行merge命令git merge hotfix0.1
:
$ git merge hotfix0.1
Updating 4a567b9..fa7f53c
Fast-forward
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
打开gitsample工作区可以看到README.md文件已经发生了改变,hotfix分支的修改已经合并进来了。
现在hotfix分支已经完成了使命,我们可以使用git branch -d hotfix0.1
命令将该分支删除。
再来查看仓库里的分支信息:git branch
$ git branch
feature-c3
* master
从命令返回结果可以看到,仓库里只剩下feature-c3和master两个分支。master左侧有一个*
,表示这是当前检出的分支。
feature-c3分支的内容还没有被合并到master,此时我们来试着删除这个分支:
$ git branch -d feature-c3
error: The branch 'feature-c3' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature-c3'.
命令返回错误信息:c3分支的内容还没有完全合并,不能删除。现在假设feature分支的开发已经完成并通过测试验收,可以发布到生产环境了。让我们再次合并分支:
$ git merge feature-c3
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.
命令提示README.md文件的内容冲突,自动合并失败。需要人工干预重复然后再提交。
我们先用git log -1
看下最后一次的提交日志:
efrey@EKStudio MINGW64 ~/Desktop/gitsample (master|MERGING)
$ git log
commit fa7f53c462f5500d00f2968b7f7ab42d307cdbc0 (HEAD -> master)
Author: Efrey Kong
Date: Tue Aug 20 10:15:56 2019 +0800
hotfix 0.1.0
可以看到Git bash命令行提示符上方的显示内容由
efrey@EKStudio MINGW64 ~/Desktop/gitsample (master)
变成了
efrey@EKStudio MINGW64 ~/Desktop/gitsample (master|MERGING)
并且日志中最后一次的提交仍然是合并hotfix分支操作。
接下来我们再打开README.md看下文件冲突的内容:
# gitsample
This line is the first time modified.
This line is the second time modified.
<<<<<<< HEAD
This is a hotfix for v0.1.0
=======
This line is a demo for feature-c3.
>>>>>>> feature-c3
<<<<<<< HEAD
和=======
中间的内容是当前master分支的内容;
=======
>>>>>>> feature-c3
中间的内容是feature-c3分支的内容。
原来在hotfix分支中,我们在README.md文件的第6行增加了一段“This is a hotfix for v0.1.0”。
而在feature-c3分支中,同样的位置增加了一段“This line is a demo for feature-c3.”。我们来处理下这个冲突:
# gitsample
This line is the first time modified.
This line is the second time modified.
This is a hotfix for v0.1.0
This line is a demo for feature-c3.
保存修改,然后分别运行git add .
、git commit -m "feature-c3"
提交。此时再来查看最后一次提交日志:
efrey@EKStudio-ZBook MINGW64 ~/Desktop/gitsample (master)
$ git log -1
commit 23b2190e6821953f8bba6935faef56cf3532b694 (HEAD -> master)
Merge: fa7f53c 49418b5
Author: Efrey Kong
Date: Tue Aug 20 10:55:25 2019 +0800
feature-c3
合并成功了。此时删除feature-c3:
$ git branch -d feature-c3
Deleted branch feature-c3 (was 49418b5).
3. 分支工作区内容的暂存
4. 分支工作流程模型
4.1. develop-master模型
A successful Git branching model一文提到了一种比较实用的Git工作流程模型,也被称为develop-master模型。
在这种模型中,git中存在两个长期分支:master和develop。其它的分支如hotfix、feature、release等属于辅助分支,按需要创建,在工作完成后合并回长期分支并删除辅助分支。
master作为系统稳定版的主干,最近一次提交是永远和生产环境保持一致的。dev分支在项目的一开始就和master分离,作为持续开发的主干。
当生产环境系统发现了bug,则在master的主干上创建一个hotfix的分支用于修复bug。bug修复测试通过后,再将这个hotfix分支合并回master,同时dev也会吸收hotfix分支中的修复。合并后删除hotfix分支。
在开发下一次发布的主要需求功能,或者要开发新的特性时,则从dev分支创建新的分支,开发完成后合并回dev分支,并删除这个新的分支。当dev分支中的可以发布时,从dev创建release分支,release分支只接受bug fix的提交(bug fix的提交也需要持续的合并回dev分支)。测试完成后,将release分支合并到master发布版本到生产环境。合并后删除release分支。