Git is more than modify-add-commit

2016-03-09

Git 作为一个常见的版本控制软件,基本是每个开发者都熟悉并且日常中在用的。基本的操作流程就是:修改文件 -> git add -> git commit。 但是 git 不止这些。

Git 是什么

借用 esr 的话 [1] 来说,是这样的:

A version control system (VCS) is a tool for managing a collection of program code that provides you with three important capabilities: reversibility, concurrency, and annotation. ...

The most basic capability you get from a VCS is reversibility, the ability to back up to a saved, known-good state when you discover that some modification you did was a mistake or a bad idea.

VCSes also support concurrency, the ability to have many people modifying the same collection of code or documents knowing that conflicting modifications can be detected and resolved.

Finally, VCSes give you the capability to annotate your data, attaching explanatory comments about the intention behind each change to it and a record of who was responsible for each change. Even for a programmer working solo, change histories are an important aid to memory; for a multi-person project they become a vitally important form of communication among developers.

如何正确地使用 git

Commit message

  • 内容的自我表达性
  • 格式

以上两点基本指出了要点所在。“内容的自我表达性”即是说, commit message 要能够基本表达该 commit 的意图,本次修改到底做了什么; 而 “格式” 就是 commit message 的格式啦,比如说太长的 commit message 不利于阅读。没有切身感受?请打开 这个网址 感受一下。

有些项目,比如说 Angular,有自己的 git-commit-guidelines [2],有更为详细的说明,还可以利用 git log 来生成 changlog。 我个人没有用过这个方式所以也不好说,个人看法来说,commit message 可读就好,然后最好详细说明 commit 原因,链接其他 issue 或者 PR 来提供更为详细和有用的说明。以后来看的时候就知道当时这样修改的原因是什么。

我的 wiki 中有相关的 write-good-commit-messages 的说明,你可以去看看。

Commit 的更改内容

  • 相关性
  • 细粒度

关于 commit 还有一个十分重要的是 commit 的内容。一个好的 commit,不仅仅应当有详细说明的 commit message,其内容也应当是和 commit message 高度相关的。或者说,commit 的内容决定了这个 commit message。举个例子说,一个 commit 既修改了某个文档中 typo,又把某个挂了的测试给修复了,还在代码中增加了一个新功能,这样一个 commit 包含了三个方面的修改,个人来说,不太适合放在一个 commit 里,最好是分开来放,使每个 commit 的内容有相关性。当然有的情况下,虽然内容可以不是很一致,但是 commit message 说清了也是可以。最可怕的就是,只是把 git 当作一个备份工具,感觉修改了很多,然后一下子全部提交一下,commit message 又是 "Update code on 20160309" 这样子的。

之前的例子有人可能会想,那么多 commit 然后没有什么问题么?这时候应该是时候讨论一下 commit 粒度的时候了。简单的讲,就是一次 commit 的内容多少合适。就个人而言,我是偏向细粒度的,也就是说,一次性的修改不要太多。一个 commit 只有一个 commit message, 但是如果我分成三个,就有更为详细的说明,分成各自相关的修改来提交。但是如果太碎了,这时候各个 commit 有时候已经联系不起来了。 所以呢,这个还是要看实际情况,只是说有一个抽象的概念是细还是粗,一次性修改解决了一个跑挂的测试虽然改了许多,但都是同一目的, 所以一起提交也未尝不可。

我个人的想法和疑惑

Log history

一次 commit 的单个的,但是一百个 commit 就是整体了,有了 commit 历史。这时候问题来了,单个 commit 时,我们都是认真抉择, 仔细维护的,那么整个提交历史怎么办呢?举个例子,每次 merge 时,如果你是 no-ff merge 的,那么会新产生一个 merge commit, 次数一多以后呢,你的提交历史基本就是一个五线谱了。你可以使用 rebase, cherry-pick, ff merge 来适当地避免这个问题。 但是,如果不是这样做呢,提交历史虽然是好看了许多,但是你失去了你的工作记录。

我对此也并不是特别理解,个人目前的做法是,加入一个新的功能而且 commit 比较多的时候,就使用 no-ff merge,保留我的开发过程; 但如果是别人向我提的 PR 或者是 hot-fix 之类的,我可能就会使用 rebase, cherry-pick, ff merge 来。

Branch 的使用

  • work-flow
  • merge

git 中还有一个难点个人感觉是开 branch,并不是说怎么开(git checkout -b my-branch),而是说如何开。挺著名的 git workflow 中用到 branch 很多,不同 branch 有不同的职责,有不同的生成时间,有不同的 merge 对象等等。还有 github workflow 也用了很多 branch,它的功能更改都是通过新开 branch,然后在新开的 branch 上修改。

新开了 branch 就肯定要 ’merge‘ 回来,方法有很多,比如 ff merge, no-ff merge, rebase, cherry-pick 等等。 那么什么时候选择哪个合适的方法也是问题。

我目前的开发方法

  • 长期生存的 branch: master, dev, 平时开发在 dev 上,有新的 release 的时候,master ff merge 一下 dev,保持更新;
  • 新的功能或相关修改在 dev 上开相应的 branch,待主要功能完成后,较少的 commit 下,信息失真较少的情况下, ff merge;反之则使用 no-ff merge;merge 完成后删去该分支;
  • rebase 主要用于本地的 commit log 修改,比如说 squash commits 等;
  • 别人提的 PR,少的情况下,避免产生新的 merge commit;反之就需要有,来保留开发过程和相关信息;
[1]http://www.catb.org/esr/writings/version-control/version-control.html
[2]https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#-git-commit-guidelines

Til next time,

lord63 at 10:03