
10.2 工作流二:使用多个分支完成所有需求的开发
在这种开发工作流下,每个需求都拥有独立的分支。同样的,与单分支实现提交原子性的方式一样,这些分支都是本地分支,并非主代码仓上的功能分支。
需要注意的是,在下面的分析中,我只描述每个分支上只有一个提交的简单形式,至于每个分支上使用多个提交的情况,操作流程与单分支提交链中的描述一样,这里不再累述。
多分支工作流的具体步骤,大致包括以下4步。
1)切换到某一个分支对某需求进行开发,产生提交。
2)提交完成后,将其发送到Phabricator上进行质量检查。在等待质量检查结果的同时,切换到其他分支,继续其他需求的开发。
3)如果第2步发送到Phabricator的提交没有通过质量检查,则切换回这个提交所在分支,对提交进行修改,修改之后返回第2步。
4)如果第2步发送到Phabricator的提交通过了质量检查,则切换回这个提交所在分支,把这个提交推送到远端代码仓中,然后回到第1步进行其他需求的开发。
下面,我们看一个开发C和D两个需求的场景。在这个场景中,我首先开发需求C,并把它的提交C1发送到Phabricator;然后开发需求D,等到C1通过质量检查之后,立即将其推送到远程共享代码仓中去。
阶段1:开发需求C
需求C是一个简单的重构,把index.js中所有的var都改成const。首先,使用git checkout -b feature-c origin/master产生本地分支feature-c,并跟踪origin/master。
> git checkout -b feature-c origin/master Branch 'feature-c' set up to track remote branch 'master' from 'origin'. Switched to a new branch 'feature-c'
然后,开发需求C,生成提交C1,并把提交发送到Phabricator进行检查。
## 修改代码,产生提交 > vim index.js > git diff diff --git a/index.js b/index.js index cc92a42..e5908f0 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ -var port = 3000 -var express = require('express') -var app = express() +const port = 3000 +const express = require('express') +const app = express() app.get('/timestamp', function (req, res) { res.send('' + Date.now()) 20:54:10 (feature-c) jasonge@Juns-MacBook-Pro-2.local:~/jksj-repo/git-atomic-demo > git add . 20:54:16 (feature-c) jasonge@Juns-MacBook-Pro-2.local:~/jksj-repo/git-atomic-demo > git commit ## 填写详细提交说明 Refactor to use const instead of var Summary: const provides more info about a variable. Use it when possible.Test: ran `node index.js` and verified it by visiting localhost:3000. Endpoints still work. ## 以下是Commit Message保存并退出后,git commit的输出结果 [feature-c 2122faa] Refactor to use const instead of var 1 file changed, 3 insertions(+), 3 deletions(-) ## 使用arc命令把当前提交发送给Phabricator进行检查 > arc diff ## 查看提交链 * 2122faa (HEAD -> feature-c, multi-branch-step-1) Refactor to use const instead of var * 5055c14 (origin/master) Add documentation for getRandom endpoint ...
这时,origin/master上只有feature-c一个分支,上面有C1一个提交,如图10-15所示。

图10-15 生成C1提交(多分支工作流状态1)
阶段2:开发需求D
将C1发到Phabricator进行质量检查后,开始开发需求D。需求D是在README.md中添加所有endpoint的文档。首先,使用git checkout -b命令产生一个分支feature-d并跟踪origin/master。
> git checkout -b feature-d origin/master Branch 'feature-d' set up to track remote branch 'master' from 'origin'. Switched to a new branch 'feature-d' Your branch is up to date with 'origin/master'.
然后,开发需求D,生成提交D1,并把D1发送到Phabricator进行检查。
## 进行修改 > vim README.md ## 添加,产生修改,输入提交说明 > git add README.md > git commit ## 查看修改 > git show commit 97047a33071420dce3b95b89f6d516e5c5b59ec9 (HEAD -> feature-d, multi-branch-step-2) Author: Jason Ge <gejun_1978@yahoo.com> Date: Tue Oct 15 21:12:54 2019 Add spec for all endpoints Summary: We are missing the spec for the endpoints. Adding them. Test: none diff --git a/README.md b/README.md index 983cb1e..cbefdc3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,8 @@ # This project is for demoing atomic commit in git-You can visit endpoint getRandom to get a random real number. -The end endpoint is `/getRandom`. +## endpoints + +* /getRandom: get a random real number. +* /timestamp: get the current timestamp. +* /: get a "hello world" message. ## 将提交发送到Phabricator进行检查 > arc diff ## 查看提交历史 > git log --oneline --graph feature-c feature-d * 97047a3 (HEAD -> feature-d Add spec for all endpoints | * 2122faa (feature-c) Refactor to use const instead of var |/ * 5055c14 (origin/master) Add documentation for getRandom endpoint
这时,origin/master上有feature-c和feature-d两个分支,分别有C1和D1两个提交,如图10-16所示。

图10-16 分支上有C1、D1两个提交(多分支工作流状态2)
阶段3:推送提交C1到远端代码仓共享分支
这时,我收到Phabricator发来的C1通过检查的通知,可以推送C1了!首先,使用git checkout把分支切换回分支feature-c:
> git checkout feature-c Switched to branch 'feature-c' Your branch is ahead of 'origin/master' by 1 commit. (use "git push" to publish your local commits)
然后,运行git fetch; git rebase origin/master,确保分支上有最新的远程共享分支代码:
> git fetch > git rebase origin/master Current branch feature-c is up to date.
接下来,运行git push推送C1:
> git push Enumerating objects: 5, done. Counting objects: 100% (5/5), done. Delta compression using up to 8 threads Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 460 bytes | 460.00 KiB/s, done. Total 3 (delta 2), reused 0 (delta 0) remote: Resolving deltas: 100% (2/2), completed with 2 local objects. To github.com:jungejason/git-atomic-demo.git 5055c14..2122faa feature-c -> master ## 查看提交状态 * 97047a3 (feature-d) Add spec for all endpoints | * 2122faa (HEAD -> feature-c, origin/master, multi-branch-step-1) Refactor to use const instead of var |/ * 5055c14 Add documentation for getRandom endpoint ...
这时,origin/master指向C1。分支feature-d从origin/master的父提交上分叉,上面只有D1一个提交,如图10-17所示。

图10-17 推送C1到远程代码仓(多分支工作流状态3)
阶段4:继续开发D1
完成C1的推送后,继续开发D1。首先,用git checkout命令切换回分支feature-d;然后,运行git fetch和git rebase,确保当前代码D1包含了远程代码仓的最新代码,以减少将来合并代码产生冲突的可能性。
> git checkout feature-d Switched to branch 'feature-d' Your branch and 'origin/master' have diverged, and have 1 and 1 different commits each, respectively. (use "git pull" to merge the remote branch into yours) 21:38:22 (feature-d) jasonge@Juns-MacBook-Pro-2.local:~/jksj-repo/git-atomic-demo> git fetch > git rebase origin/master First, rewinding head to replay your work on top of it... Applying: Add spec for all endpoints## 查看提交状态 > git log --oneline --graph feature-c feature-d * a8f92f5 (HEAD -> feature-d) Add spec for all endpoints * 2122faa (origin/master,) Refactor to use const instead of var ...
这时,当前分支为feature-d,上面有唯一一个提交D1',而且D1'已经变基到了origin/master上,如图10-18所示。

图10-18 继续开发D1(多分支工作流状态4)
需要注意的是,因为使用的是git rebase,没有使用git merge产生合并提交,所以提交历史是线性的。在后面的章节中我们会详细讨论线性的提交历史对CI自动化的重大意义。
至此,我们完成了在两个分支上同时开发C、D两个需求,并尽早把完成的提交推送到远端代码仓中的全过程。
虽然在这个例子中,我简化了这两个需求开发的过程,每个需求只有一个提交并且一次就通过了质量检查,但结合在一个分支上完成所有需求开发的流程,相信你也可以推导出每个需求有多个提交,以及质量检查没有通过时的处理方法。