Git教程 · 发行版交付
- 1️⃣ 概述
- 2️⃣ 使用要求
- 3️⃣ 执行过程及其实现
- 3.1 预备阶段:创建 stable 分支
- 3.2 预备并创建发行版
- 3.3 创建补丁
- 4️⃣ 替代解决方案
对于每个项目或产品来说,发布版本的创建都需要一定的时间,其具体过程因各公司或组织的情况而异。
Git 无法用来指定项目进入发布阶段的时间。但我们可以利用标签和分支这两个强大的Git 工具来为发布进度设置一个很宽泛的时间区间。
在本章的工作流中,我们将以一个典型的 Web 项目为例,为你介绍版本发布的过程。在 我们的这个 Web 项目中,始终会存在一个面向产品发布的发行版和一个未来要发布的版本。 产品发行版中出现的主要bug 和安全隐患往往能很快得到解决(以补丁的形式)。而后面这个 新版本在正式发布之前,往往要经过一个持续多天的详尽测试期(我们称之为“代码冻结阶段”)。与此同时,下一个版本的开发也会继续进行。
本章的工作流主要演示是如何用Git 来实施项目的发布阶段,它包含以下内容。
- 产品发行版支持打补丁的功能。
- 在代码冻结阶段并行开发新版本的可能性。
- 确保在开发阶段能以补丁的形式修复所有错误,或者测试阶段的工作能回流到开发阶段。
- 发行版的历史以及补丁的历史记录要能很容易地被访问。
- 发行版与开发版之间的比对工作也要很容易进行。
1️⃣ 概述
在下图中,我们将会看到开发阶段和发布阶段各自所需要的分支。
如你所见,开发部分被放在了master 分支上。无论有没有设置feature 分支 ,master 分支都是决定将哪一些代码纳入发行版的唯一主角。
在预备发布阶段,我们会将启用一个独立的 codefreeze 分支,以便稳定住将要发布的版本。与此同时, master 分支上下一版本的开发工作将继续进行。
一旦稳定阶段的工作完成,我们就可以在 stable 分支上创建一个发行版提交,并同时生成一个相应的发行版标签。
如果产品发行版中出现了某个致命性的错误,我们就得为其新建一个热点修复分支。待排错工作完成之后,我们会在stable分支上创建一个相应的提交修复和发行版标签。
请注意, codefreeze 分支和 hotfix 分支只存在于项目的稳定阶段与排错阶段。另外,项目在稳定阶段和修复阶段所发生的修改始终会通过合并的方式返回到 master 分支上。
2️⃣ 使用要求
- 产品发行版只有一个:项目的产品发行版只能有一个。也就是说同一个项目或产品不会同时维护多个版本。虽然 Git 有处理多个版本的能力,但根据本章工作流的设计, 我们只能处理一个产品发行版。
- 开发的稳定性:开发分支需要经过良好的测试,并且在代码冻结阶段所可能出现的错误必须是可控的,以至于我们可以在短期内完成相关的工作。
- 发行版的完整性:我们在开发分支上新增的内容以及所做的修改要始终被纳入到下一发行版中。
3️⃣ 执行过程及其实现
该工作流会为项目创建一个发行版。然后用一个独立分支来放置该发行版在预备阶段的内容。而修复相关的当作可以在产品发行版上完成。
3.1 预备阶段:创建 stable 分支
在接下来的这部分内容中,我们将要介绍如何在版本库独立的预备阶段中进行一次产品发布。
在这部分工作流中,我们需要有一个名为 stable 的分支,该分支中将只包含新发行版或 补丁所需要的提交。stable 分支上的第一父级提交历史可以直接用来充当发行版的历史,我们可以用log
命令来显示它们。
> git checkout stable
> git log --first-parent -oneline590lec9 Hotfix-Release-2.0.1
b955c9c Release-2.0.0
5d0173d Release-1.0.0
3a05e26 init
在上述命令中,我们用到了以下参数。
--first-parent
: 只考虑第一父级提交。-oneline
: 令每条日志输出只打印一行。
在这里,我们的重点是要正确地定义的 stable 分支的起点。如果 stable 分支被建在了 首次发布的提交上,那么 master 分支上之前的提交也必然会被纳入其第一父级提交的历史 (见下图)。
stable 分支更好的起点应该是在 master 分支的首次提交上。这样一来,发行版的历史记录中就只有一个不必要的提交了(见下图中的首次提交)。
-
第1步:确定首次提交的位置
不幸的是 ,Git 中没有命令可用于找出某一分支上的首次提交。所以我们最好的办法是查看日志中的最后一项。> git log -oneline --first-parent | tail -1 3a05e26 init
在上述命令中,我们用到了以下参数。
-oneline
: 令输出内容只以单行形式打印。--first-parent
: 令其只返回各提交的第一父级提交,这可以加快其执行速度。| tail -1
: 只打印日志输出中的最后一行。
-
第2步:创建 stable 分支
在找到首次提交的位置之后,我们就可以创建stable 分支了。这里需要用到branch 命令,并且也可以额外指定分支的名称。> git branch stable 3a05e26
3.2 预备并创建发行版
在接下来这部分内容中,我们来介绍用Git 发布项目的步骤。
由于项目的开发过程被放在了 master 分支中,所以一些必要的单元测试和整合测试也要在这个分支上执行。
当开发工作完成,该项目也准备好被发布时,我们通常都会有越来越密集的测试要执行。 这就是我们所谓的“代码冻结”阶段。 一旦进入了这个阶段,项目代码中就只有会对发行版造成影响的 bug 修复及可能的回避措施才会被执行。这个阶段的持续时间很大程度上取决于项目的开发过程,我们可以从现有代码的质量和测试情况推导出大致的范围,结果可能是几小时,也可能是几个星期。
在代码冻结阶段,我们对于下一发行版的开发工作并不会停止,因为我们将要发表的内容会被稳定在一个独立的 codefreeze 分支中。该分支只存在到新的发行版稳定为止。待下一次要发布新的发行版时,我们又会再重新创建一个新的 codefreeze 分支。
-
第1步:创建 codefreeze 分支
codefreeze 分支是基于当前的 master 分支来创建的。我们可以用checkout
命令来创建这个新分支并激活它。> git checkout -b codefreeze master
-
第2步:稳定 codefreeze 分支
在 codefreeze 分支中,只有那些会影响发行版的错误才会被纠正。这部分的修复动作将遵循最小变更原则。如果我们没有简单的解决方案实现最小化的错误修改,在必要时我们也可以考虑实现某种规避错误的措施。
另外, codefreeze 分支上新增的提交必须要定期合并到 master 分支。这些一次性的修复措施也会从当前的开发工作中被清除出去。> git checkout master > git merge codefreeze
相关的规避措施应该也会被纳入到 codefreeze 分支中,因为这些内容将在 master 分支中被保留。这些规避措施可以在 master 分支中是可以被恢复的(例如通过
revert
命令), 并由此创建出一个更好的实现(见下图)。 -
第3步:创建发行版
待 codefreeze 分支上的测试成功完成之后,我们就可以创建发行版了。
同样地,来自 codefreeze 分支的合并操作也必须要基于stable 分支来进行。这对于 stable 分支中那些尚未在 codefreeze 分支中被测试过的提交来说非常重要。这些提交会导致我们的合并操作在尚未经过完全测试的 stable 分支上创建出一个主要发行版。
下面,我们就用log
命令来检查一下 stable 分支中是否存在 codefreeze 分支中缺失的提交。如果它没有任何输出,就说明 stable 分支上没有新增的提交。> git log codefreeze..stable --oneline
如果
log
命令返回了某种输出,我们就要stable 分支与 codefreeze 分支进行重新合并,并针对发行版再次对其进行必要的测试。
如果日志输出为空, codefreeze分支上的合并提交就可以在stable分支被执行了。通常,Git 会对这次合并采用快进式合并,因为我们已经确保了stable 分支上没有新的提交。但为了获取 stable 分支一份有意义的第一父级提交历史,我们还是应该对其使用
--no-ff
选项。另外,我们也应该使用注释明确标识一下这个发行版的新提交。> git checkout stable > git merge codefreeze --no-ff -m "Release-2.0.0"
这里的
--no-ff
选项主要用于指示merge
命令不地执行快进式合并。也就是说,该命令始终要新建一个提交对象。
除了提交之外,我们还需要为发行版创建一个新的标签。该标签主要用于快速访问该发 行版提交,例如我们将其配合diff
命令一起使用。> git tag -a release-2.0.0 -m "Release-2.0.0"
最后,我们要将 codefreeze 分支删除,因为该分支只存在于项目的稳定阶段。待下一轮发布时我们又会重建它。
> git branch -d codefreeze
-
第4步:更新 master 分支
既然发行版已被发布,我们也就确保了发行版中所有的修改也都被纳入到了 master 分支中。
尽管 codefreeze分支中所有的bug 修复提交都已经被合并到了master 分支提交中,但新 发行版的提交仍然还存在(见下图)。虽然该发行版提交不会改变任何文件,因此与 master 分支并无关系,但当我们查询“属于stable分支,但不属于master分支的提交”时它还是会不时冒出来。因此,我们需要将 stable分支合并到master分支中。> git checkout master > git merge stable -m "Nach Release-2.0.0"
到目前为止,从版本管理的角度来看,新发行版已经完全被创建了。
3.3 创建补丁
补丁所处理的是一种带有紧迫性的修改,它应该尽可能地快速,且独立于其他修改。补 丁通常都是直接实现在当前发行的版本中的。像 Web应用程序中那些不太重要的错误通常会 等到下一版本发布时再纠正。但如果有一个错误会得系统无法正常工作,或者会导致安全风险,那它就必须立即得到纠正。
-
第1步:创建
hotfix
分支并进行排错
这样的纠错任务通常需要在一个独立的 hotfix 分支中进行。为了能并行启用多个补丁,我们可以让每个人都设置一个属于自己的 hotfix 分支。
它的起点应该是 stable 分支,指向的是最后一个产品发行版。> git checkout -b hotfix-al stable
现在,我们可以对项目做一些必要的修改了。
-
第2步:验证补丁被并行化创建的可能性
如果我们已经完成了错误纠正和新发行版的创建,接下来就必须要检查以下提交历史, 看看在此期间是否还有另一个补丁在运行。为此,我们就需要用log
命令来查看该历史记录中是否还存在属于stable 分支,但不属于hotfix 分支的提交。> git log hotfix-al..stable --oneline
如果 stable 分支在补丁被安置之前的这段时间里发生了其他修改,我们就必须要检查一 下这些修改是否也将以补丁的形式工作。这样的话,我们的历史记录将会保持线性发展,这些补丁应该通过变基操作被放置在stable 分支的最新提交中(见下图)。
> git rebase stable
这样一来,该补丁提交就被建在了stable 分支的最后一次提交之上。
-
第3步:发布补丁
现在来正式发布这个补丁,为此我们需要对 hotfix 分支和stable 分支来一次合并操作。 自然,我们会再次不允许快进式合并,因为我们要创建一个新的提交。而且该合并提交的注释中也应该注明必要的发行版信息。> git checkout stable > git merge hotfix-al --no-ff -m "Hotfix-Release-2.0.1"
除了提交之外,我们还需要为发行版创建一个新的标签。
> git tag -a release-2.0.1 -m "Hotfix-Release 2.0.1"
最后,我们可以将hotfix分支删除。
-
第4步:在其他分支上接受补丁所做的修改
当然,我们在hotfix 分支中所修复的错误还必须被传送给其他活动分支。
在代码冻结阶段,补丁只能将其所做的修改传递给 codefreeze 分支。之后,codefreeze 分支会将这部分修改再传递给 master分支(见下图)。> git checkout codefreeze > git merge stable -m "Hotfix 2.0.1" > git checkout master > git merge codefreeze -m "Hotfix 2.0.1"
而在非代码冻结阶段,补丁中的修改可以直接被传递给 master 分支(见下图)。
> git checkout master > git merge stable -m "Hotfix 2.0.1"
4️⃣ 替代解决方案
-
只用标签
在本章工作流中,我们所描述的是stable 分支与标识版本的附加标签之间的搭配使用。 那么,如果只用标签行不行呢?
对于那些纯标记性的,并可据此重现的发行版来说,当然使用丰富的标签是足以应对实际需要了。但如果我们要谈及对于版本发布历史及补丁发布历史的理解的话,单独依靠标签就不切 实际了。这样的话,我们只能根据标记名称来猜测时间顺序快乐。而通过 stable 分支,我们 就可以用第一父级提交历史来做这件事。
-
不用标签
标签实际上就是我们为提交设置的符号名称。如果我们想比较以下当前开发版本与某一特定发行版之间的区别(使用diff
命令), 标签确实是比提交的散列值要更实用一些。> git diff release-1.0.0
如果消除掉了这些标签,我们就必须要先在 stable 分支找到相应的提交,然后再指定它的散列值了。
> git diff 5d0173d
-
用快进式合并
在Git 中,分支中能引用的只有一些提交。如果某一分支被激活(通过 checkout 命令), 那么该分支所引用的各个新提交都会被自动更新。提交在创建它的这个分支上并不会留下历史信息。因此分支上的第一父级提交历史是它唯一可选的、“启发式”的历史形式。对两个分支进行快进式合并的结果会指向同一个提交对象。如果我们想使用的是第一父级提交历史,这种做法就无法对这两个分支中的其中一个父级提交的创建动作进行跟踪了。
如果我们不允许执行快进式合并,那么合并操作就始终会去创建新的提交。这样一来,它的第一父级提交就是当前分支下的最后一次提交,而第二父级提交则是之前已添加的那次提交。 -
直接在 stable 分支上实现补丁
本章工作流所介绍的是如何纠正一个严重的错误,它应该建立一个独立的 hotfix 分支。
从原则上来说,直接在 stable分支上做这件事也是可以的。但在某些情况下,有些与发行版无关的提交也会出现在 stable 分支的第一父级提交的历史中。当这种情况发生时,要创建的补丁往往就不止一个了。
而且,这样做会使我们很难并行化地创建补丁。
《【Git教程】(十六)基于构建服务器的工作 — 概述及使用要求,执行过程及其实现,替代解决方案 ~》