引入 Git 的过程不会一帆风顺,中间会有不少困扰

背景

团队版本节奏两周一个版本,一周半的时间开发,半周时间测试发布

软件工程典型的瀑布式流程, 需求 -> 设计 -> 开发 -> 测试 -> 发布,然后周而复始,过程中几乎没有重叠;伴随着瀑布式的流程,代码也只有一份,“开发 -> 改 bug-> 发布”,周而复始

但需要以并不确定的研发周期支持各种运营活动(快速写快速发)

一方面是运营活动带来大量客户,与客户相关的流程也陷入频繁修改发布的过程

支持各种终端需求也集中爆发,需要 web 侧快速跟进上线

瀑布流程中两周发布一次的 “主版本”。当这么多版本交叠在一起时,发现必须要引入分支来管理研发过程

概念和特性

Git 的概念繁多,很多命令并不直观。尤其一些和 svn 很像的命令,需要一段时间去理解

svn commit会推送到服务器,而 Git 不会,导致解释 Git 的push命令并不是那么容易

Git 会在操作可能导致修改丢失时拒绝操作

当前分支上件 A,当前状态为 A1,将它做一点修改,并没提交,此时状态为 A2

切换到另一分支的状态 B1,如果 A1 和 B1 不一致,就需要覆盖当前文件,从 A2 切到 B1,此时就会导致状态 A1 到 A2 的改动丢失,Git 会拒绝操作

类似的场景很多,只要 Git 发现有改动会丢失就会拒绝操作,改动不会丢失,才允许操作。这种是否允许操作的不一致性也会困扰团队成员

解决的方案是引入 stash 操作,或者鼓励成员多提交

分支思维

从单线开发转入多线开发的思维转变

切 Git 之前,代码都是只有一个主线的(或其实有另外一份代码,但是是以 Copy 文件夹的形式存在,更多的意义在于 “备份”)

切换 Git 之后,一方面概念繁多,一方面还要时刻去关注代码所处的分支状态

甚至会有很多成员在一开始很怀疑分支的有效性,总会担心自己辛苦写的代码一不小心切完就再也找不回来。这个问题也同样需要一段时间来适应

日常操作的困扰

对比 svn,Git 的日常代码提交增加了 add 和 push 的过程,略显繁琐,但繁琐并不是很大的问题

真正的问题在于 Git 对版本和文件完整性的要求导致不允许对未提交的文件进行合并

直观的表现就是本机修改了文件无法直接与远程合并,必须要先提交,再合并远程,最后再推送。如果本机有部分文件无法提交的话,还需要增加 stash 相关的动作,整个流程就变为 “add->commit->stash->pull(merge 或 rebase)->push->stash pop”,而 svn 的则是 “update->commit”,光看看路径的长度就懂了

这个问题并没有什么好的解决办法,只能是让成员不断练习、练习、实践、实践,然后习惯

部分 Git 客户端会简化这个操作,如SmartGit 提供了一个操作叫sync,就是把上面列的这一长串流程放到一起了

Git-Flow

在没有分支管理经验的时候,全盘引入别人的成功经验是可取的,虽并不是 100% 完美,但很大程度上避免了前期刚切入 Git 时的混乱期

Git-Flow 并没有对测试介绍做出指导(何时在哪个分支做测试),可能导致唯一的测试服务器上经常出现版本混乱

尝试加入 tests 分支,但事实证明这个分支并不能很好地与其它分支协作

其实测试服务器上出的问题并不是 Git-Flow 带来的。测试应该是在 release 分支拉出后再做

如果是单独测试特性,则直接在 feature 分支做

如果需要同时测多个东西,则需要多台测试服务器

不采用 Git-Flow,结果就会经常为拉分支和合并分支的事情而困扰

release 分支

release 分支不是是一个存活期非常短的分支,只是拉出来打打版本号,然后立马就消失了

release 分支其实相当于对 develop 分支的一个冻结副本,release 拉出来的时候就意味着上面的需求都已经冻结,在 release 上唯一可以继续做的改动只有这些需求的 bug 修复

release 一旦拉出后,develop 上就可以继续执行新功能开发,这样新功能开发和版本测试发布可以并行,所以测试的介入的理想节点是 release 版本

如果是瀑布式模型,测试和新功能开发不重合,则可以不需要 release 版本

develop 分支

理想情况下,一个版本开始的时候 develop 和 master 是完全一样的

此时开始在 develop 上做开发,相当于这是一个大版本,上面拉出来的各种 feature 分支都是在做这个大版本开发的一部分(它们最后也合回 develop,和直接在 develop 上开发的效果是一样的)

而如果一个大版本正在开发,则时需要再来一个并行的大版本(比如碰到了长线需求),也即正在开发的独立大版本不止一个,则理论上需要并行的多个 develop 分支。这是 Git-Flow 的各种介绍文章中均没有提到的问题

所以,当碰到有多个并行大版本的需求时,如果要准备开发第二个大版本的需求(也可以简化为 “独立发布的需求”),则必须不能使用 Git-Flow 从 develop 拉出 feature 分支。此时应该使用 hotfix 分支(相当于另一个 develop 分支)

hotfix

在看过上面一段后,应该能明白,hotfix 的地位和 develop 其实是一样的,只是 develop 存在的时间更久一些(“大” 版本嘛),还会拉出功能分支,接受功能分支合并

简单说,他们的区别就是 develop 更 “重” 一些,hotfix 更轻量一些,本质上都是从 master 来,回 master 去

master

有紧急 bug 的时候,Git-Flow 要求拉出一个 hotfix 分支来处理,而不能直接改动 master

但从实践的经验来看,master 上并不是不能做修改,有时候为简单起见,也可以在 master 上直接改,只是做完修改需要合并回 develop,合并完之后和拉 hotfix 分支再完成等效

一些习惯

rebase 和 merge

一、 建议本机和远程分支(相同分支)同步必须用 rebase,因为不用 rebase 就会使用 merge,这样会导致(逻辑上的)同一分支在版本记录中变成分叉的多个分支(然后这些分支合并的消息全是merge xxx branch of http://remote.server/xxx.git),不利于追踪版本记录

但从实践的经验来看,rebase 在发生冲突时解决方案并不是那么友好

首先是此时的 “my version” 和 “theirs version” 并不和想象的一样。(详情可参考 rebase 文档,很可能 “theirs” 其实是自己的代码。)然后,在极端情况下 rebase 会导致代码丢失(目前原因未知)

上面说的是本机和远程的相同分支使用 rebase。不同分支合并必须用 merge,否则历史记录不可读,再也找不到合并之前各个分支到底是在哪里

二、 两个分支 A 和 B,本机 A 的状态为 A1,远程的比 A1 新,为 A2,要将 B 合并到 A

按照上方所说,本机和远程的 A 分支同步使用 rebase,B 合并到 A 使用 merge

如果先将 B merge 到 A,则本机的状态为 A1,然后是 B。此时再拉远程的 A2,会导致 B 合并过来的记录丢失(在分支图中看不到合并的痕迹)

因此碰到这种情况,既需要和远程同步,又需要合并其它分支的情况,一定要先 rebase 再 merge,否则会丢失 merge 记录

commit 和 stash

如前文所述,svn 的习惯(update->commit)行不通,因此需要先 stash 或者 commit

鼓励团队成员多 commit,即使没写完,也可以在下次提交时使用–amend将两次提交进行合并

之所以鼓励 commit 而不是 stash,是因为 stash 并不在版本记录中,理解起来并不那么容易,对比和合并也不是特别方便

此外就是前文说的,Git sync 是个好功能,但是从实践的情况来看,sync 时并不一定会选择 rebase,而 merge 会导致版本记录可读性变差

tags

需要从 Git 中提取代码的情况,通过 tags 操作非常好,可以很方便地追踪代码版本情况

不打 tags,也可以使用提交的 hash 值,也可以追踪,只是人眼看起来没有那么直观了

使用分支名,技术上也可行,但是会导致追踪困难。(能很轻易地找到前天晚上 master 在哪里么?)

如果有发布系统来记录每一次发布的情况,则可以不用 tags,因为发布系统可以记录当时提交的 hash 值,追踪起来也很方便

小结

坑都是要自己踩的,认真踩下来,结果还不错