Git使用心得

2015.06.04/2015.06.05发布于笔记暂无评论/目录

总结一些(目前我认为)比较靠谱的git实践,包括工作流和使用习惯等方面。

使用git很久了,积累了不少知识,也写过不少总结(比如这个这个),自以为多少也算是git熟手了,直到前几天差点捅篓子,才发现自己的开发习惯有很大的问题。无奈之下,果断需要再总结一番。

风险预警:以下内容不保证100%正确,欢迎一起讨论。

残酷的开发现实

开发过程是复杂而残酷的,以下便是我亲身经历过的现实:

  1. 需求人员不停的催你加新feature:boss的一个眼神就能让某些人脑暴出N个idea。
  2. feature啥时候发布完全没谱:最后会发现很多feature这辈子都没法上线和用户见面。
  3. bug不停的刷存在感:客服抱怨说又被用户骂了,boss怒斥说服务器怎么又挂了。

这意味着:

  1. 有时需要并行开发多个feature。
  2. 一部分feature会在代码仓库里当一辈子钉子户。
  3. 需要时不时的修bug并尽快测试和发布。

工作流

没有最好的工作流,只有最合适的工作流。

反面典型

之前我的git工作流大致可以总结如下:

  1. 所有的代码都在master分支上提交。
  2. 需要并行开发新的feature时,不得已才开一个新的分支,写完后合并到master分支上。
  3. 发布的时候开一个新的分支,然后用cherry-pick挑捡出需要的commit,编译发布。

前几天差点捅篓子,因为挑捡commit的时候把一个比较重要的bugfix commit给遗漏了。悲剧的是一开始测试的时候没有出问题[1],直到后来无意间看代码的时候才发现。。。惊出一身冷汗。

如果当时不是闲的蛋疼看代码,我估计发布之后第二天,各种客户端都要挂得一塌糊涂,被boss请去喝茶那是必须的了。

出了这个岔子之后,发现表面上问题出在cherry-pick这个命令上,因为cherry-pick每次都会生成新的commit,然后用master..upgrade比较分支的commit历史就不好用了[2],因此才遗漏了某个关键的commit。

但是细想之下,发现问题的根本症结还是在工作流上面,上面的工作流会导致代码管理起来很混乱,不管cherry-pick的时候多仔细,出错是迟早的事情。

改进措施

出了问题之后,阅读了下man gitworkflows,在网上也浏览了一些前辈们分享的经验,大致总结了以下几点:

  1. master分支只保存即将上线发布的commit。
  2. 新的feature和bugfix都从master上创建单独的新分支。[3]
  3. 需要上线的feature和bugfix要测试充分后再merge到master分支上发布。
  4. 在master分支上tag发布的commit并写详细的发布说明。

使用习惯

命名习惯

分支(branch)和标签(tag)的命名应该遵循一定的惯例,比如新的功能分支应该以feature-开头,而bug修复分支应该以bugfix-开头。

例如:

$ git branch
master
* feature-xxoo
bugfix-ooxx

当branch和tag多了之后,可以使用for-each-ref命令来查看详情。

# 按作者时间倒序列出所有的本地branch
$ git for-each-ref --sort=-authordate \
    --format="%(authordate:iso8601) %(objectname:short) %(refname:short)" \
    refs/heads/

输出大致如下:

2015-06-04 17:44:21 +0800 ea79ffb feature-icomoon
2015-04-29 11:32:01 +0800 014270d feature-customizable-sidebar
2015-01-23 10:59:53 +0800 c5854f6 master

如果要查看tag的详情

# 按作者时间倒序列出所有的tag
$ git for-each-ref --sort=-*authordate \
    --format="%(*authordate:iso8601) %(objectname:short) %(refname:short)" \
    refs/tags/

输出大致如下:

2015-06-04 17:44:21 +0800 548ad4a v1.1
2014-05-19 13:49:36 +0800 3c770ee v1.0

更多使用方法可参考man git-for-each-ref

commit

commit的粒度应该尽量的细,在功能上应当保持一定的原子性。

每次提交的时候,建议使用git commit -v,并编写精确的说明。

rebase + merge

对于自己的本地私有分支,在push到远程仓库之前[4],应当尽量先使用rebase,作用主要有两个:

  1. git rebase -i HEAD~N 整理分支的commit历史,比如修改commit说明,调整commit的顺序或合并commits等。
  2. git rebase master 当master分支在之后合并过其他的分支,最好将本分支在master分支上rebase一下,在master分支上merge之前就解决掉可能存在的冲突[5]

然后在master上merge新分支时,可以考虑使用merge --no-ff,这样会额外生成一个空的merge commit,虽然没有fast forward的commit历史直观,但是在revert的时候会比较方便。

    master: A -- B -- C -- D -- E
                       \
    feature-a:          F -- G


                 | (git rebase master feature-a)
                 V


    master: A -- B -- C -- D -- E
                                 \ 
    feature-a:                    F' -- G'


                 | (git merge --no-ff feature-a)
                 V


    master: A -- B -- C -- D -- E -------- M
                                 \        /
    feature-a:                    F' -- G'

需要注意的是:

  1. F'G'都是新的commit,和之前的FG有相同的authordate,但是committerdate和hash不同。
  2. 使用merge --no-ff会产生非线性的commit历史。

参考资料

  • man gitworkflows

  1. 其实测试流程也有不小的问题,但与本文主题无关,不多讨论。

  2. 如果不得不用cherry-pick,此时可以用cherry命令来比较patch历史。

  3. 有时也可能需要从feature和bugfix分支上创建新分支。

  4. 已经推到远程仓库的分支,绝对不能再用rebase修改commit历史。

  5. 自己的问题自己解决,因为有merge权限的人很可能和分支的作者不是同一个人。

#git#总结

评论