Git工作流的使用

  最近好久都没有更新博客了。
  一方面是自己“三十 而未立”也当爸爸了,在幸福洋溢之余也还有很多的事情需要去做,很多时候下班和周末没有时间开电脑了;另外就是工作上面的事情比较繁琐,而且与此同时挤出零碎的时间完善了一个HTTP服务端框架tzhttp和一个简易的信息收集系统tzmonitor,关于这两个系统的细节我后面会单独描述,此处暂且不表了。不过在写这两个项目的时候,我使用了Git Workflow和Git Submodule特性,感觉确实很方便实用——如果说Git是一个强大的版本控制工具的话,那么Git Workflow就是在现实工作中活用这个工具的“术”了,Git Workflow就像是一个Git在团队实践项目中的工作模式,套用这个模式可以清晰完美解决工作中的版本控制、团队合作、迭代发布的需求,其本质提现的是有效的项目流程管理和各个参与者对于开发维护工作的协同约定。
  说到Git Workflow就不得不谈大名鼎鼎的A successful Git branching model了,这里祭出他的Workflow框图:
git-workflow

  首先不得不说的是,Git Workflow之所以能successful,完全得益于Git底层精巧的设计和高效的存储。在Git中维护了对象库索引两种类型的数据结构,他们被存储在项目的.git目录下面。
  Git使用对象库存储了块(blob)、目录树(tree)、提交(commit)和标签(tag):blob是用来存储具体的文件内容的,其内部数据会被忽略,比如代码中的源文件、图像等,其只存储文件内容而不包含文件元数据;tree解决了文件名和文件夹名的问题,其内容可以是blob文件名、路径名信息,形成一个类似UNIX文件系统的层次结构;commit包含了提交过程相关信息,提交commit可以表示提交时刻整个代码库的快照,除了包含提交者、提交摘要外,最主要是指定了一个tree对象,同时还会引用到上一个父提交对象;tag虽然和commit一样包含时间、作者、注释等信息外,最主要是tag指向的是一个commit(而不是一个tree),所以通常就是提交对象的一个别名信息,方便大家引用、描述而已。
  相对来说索引则是使用者操作Git时候一个动态的信息了,他是介于工作目录和版本库的中间层。使用过Git的都知道,一个提交的生成包含了暂存变更(stage)和提交变更(commit)两个步骤,通过索引就可以灵活的控制哪些变更是准备累计起来最终提交的,每次git commit只会检查索引而不是当前的目录来确认提交的内容的。
  然后在Git中,Branch实际就是一个指向提交对象的可变指针,在Git中有一个默认的特殊指针HEAD,他指向当前工作所在分支。这不是重点,重点是在Git中分支是十分轻量级的,他们底层的数据是高度压缩且没有冗余的,而且分支的合并相比其他版本控制系统也更为的强大,所以我们就可以通过维护特定的分支实现一个清晰、条理化的工作流程。

  在Git Workflow中,我们会涉及到如下几个分支:master、develope、release、hotfix。
(a) master branch
  master branch是每个git init之后默认生成的一个分支,虽然大多数人直接用他,但是他确实最重要的一个分支。master branch代表的是一个最稳定的、production-ready的分支,即任何时候该分支的最新状态都能编译出一个生产可用的系统。正是由于我们约定该分支的代码是如此的稳定可行,所以可以给该分支设定Git-hook,以便在该分支有变更操作的时候,都能自动编译产生稳定可靠的最新生产版本。
(b) develop
  该分支代表了下一个发布之前的相对比较稳定的开发状态,很多人将其称为“持续集成”的分支,也是很多nightly-build的代码分支。该分支切不可以直接与master branch交互。
(c) feature
  该分支在一个新特性被提出来的时候,可以从develop branch直接衍生出来,该分支在该特性开发过程中一直存在,直到该特性被接收时合并到develop branch,或者该功能被直接被丢弃掉。该分支通常都在开发者本地存在,一般不会被推动到生产仓库中去。

1
2
3
4
5
6
git checkout -b feature-0430 develop

git checkout develop
git merge --no-ff feature-0430

git branch -d feature-0430

(d) release
  当develop branch的代码的开发工作基本完成,代码比较稳定,而且在本次发布的feature branch都已经合并进来的时候,就可以从develop branch衍生出release branch了,而且此时develop branch应当处于封闭状态,其他需要在下一个版本发布的特性应该在本次release之后再合并到develop branch。
  该分支在正式发布之前会一直存在,其只允许小规模的bug修复、版本信息变更、文档生成等操作,不允许有大的变更操作。而且此时的bug fix只允许在release branch而不允许在develop branch进行,因为release branch会再度合并回develop branch的。

1
2
3
4
5
6
7
8
9
10
11
git checkout -b release-1.1.0 develop
# bug fix and bump version with commit

git checkout master
git merge --no-ff release-1.1.0
git tage -s v1.1.0

git checkout develop
git merge --no-ff release-1.1.0

git branch -d release-1.1.0

  在release branch合并回develop branch时候如果小概率的导致冲突,那么正常解决冲突然后再commit就可以了。
(e) hotfix
  该分支用于快速修复线上生产环境的bug而产生的,hotfix branch是从master branch检出线上运行版本tag产生的,可以修复后直接合并到master branch,而此时develop不必封闭可以继续开发。之所以从master branch开辟分支而不是develop branch,就是因为此时develop branch可能还不够稳定,走完发布流程的代价太高。hotfix branch是唯一可以直接从master branch衍生出来的分支。

1
2
3
4
5
6
7
8
9
10
11
12
git checkout -b hotfix-1.1.1 master
# bump version and commit
# bugfix and commit

git checkout master
git merge --no-ff hotfix-1.1.1
git tag -s v1.1.1

git checkout develop
git merge --no-ff hotfix-1.1.1

git branch -d hotfix-1.1.1

  hotfix branch的处理和release branch比较类似,所有的更新都需要合并回master branch和develop branch。当产生hotfix branch的时候如果存在release branch,那么hotfix的变更可以合并回release branch而不是develop branch,因为release branch最终也会合并到develop branch的,不过这个不是必须的,特别当develop branch紧急需要这个hotfix的时候,可以直接合并回develop branch。

  在上面的分支合并的时候,我们都使用了–no-ff符号,代表的是即使merge是fast-forward的,也会产生一条实质内容为空的commit对象。如果不使用它的话,被合并的提交会直接放在目标分支上;如果使用该选项的话,合并的源分支会完整的保留下来,然后在目的分支上创建一个commit对象,该对象有合并的源分支和目标分支的两个父提交指针,这样的操作和传统的版本控制集中式管理是完全不一样的,但是这种信息方便日后对历史纪录的追述和定位。

  比较可惜的是,目前还只是自己私底下使用,在公司工作中仍然是使用传统的SVN集中式的代码管理和人肉代码手动合并的方式进行,虽然我们开发的服务都是自己使用,所以流程需求相对比较简单,但是整个过程也是十分麻烦、苦不堪言。记得之前面试过一个小伙伴,问到他们公司的项目管理的时候,他们的确用的Git Workflow来进行的:当一个需求到来的时候,产品经理会给开发者弄一个feature branch;开发者完成代码开发后,团队的核心主程会将代码merge到devel branch;接着从devel branch衍生出release branch,同时develop关闭新功能提交窗口,只和测试部合作完成bug的修复,测试完毕后即可正式发布。
  现在公司在推Gitlab了,希望能够早日应用到实践工作中来。

参考