Git Commit (提交) 规范与注意事项
请先阅读:
- How to Write a Git Commit Message
- Commit messages – Good practices
- angular.js 的命名规范
怎么样算是一个好的 commit?
- 一个 commit 应该只包含一种变更逻辑,多个变更逻辑应该拆分多个 commit
- 容易 code review (多个小 commit 比一个大 commit 容易 review)
- 一次变更在 200 行代码内,或是 4 小时内的工作量 (超过则要考虑拆分 commit)
- 提交历史容易阅读,可以理解一个分支是如何完成的
- 将来容易 revert
- 不确定怎么样算是一个合理的 commit?=> 问 code reviewer 的建议
- 若是多个 commit 完成同一件任务,则应该考虑合并 (squash/amend)
- 宁愿先拆分成多个 commit,之后再合并 (操作简单);也不要提交一个大 commit,之后再拆分 (操作困难)
- reviewer 有权力退回并要求重新拆分或是合并 commits。因为大 commit 会非常难以审查。
Commit Message 规范
一个好的 commit message 应该一眼就能理解:
- 操作的目的 (subject)
- 改变的范围或模块 (scope)
- 操作的类型 (type)
而这些讯息应该都放在 commit message 的第一行 (header) 中。commit message 允许输入多行讯息,因此详细的内容应该放在后面的 body 和 footer 中。
本 commit message 规范,主要参考 angular.js 的命名规范。提交一个 commit 时,message 必须符合以下三个部分:
- header (或称 title;必要):一行就能理解 commit 的摘要
- body (非必要):详细说明内容,给 reviewer 看的讯息。
- footer (或称 note;非必要):说明解决了哪个 ticket。
<type>(<scope>): <subject> <BLANK LINE> <body> <BLANK LINE> <footer>
Commit Message 格式注意事项
- header 的 scope 括号使用英文半型,且与 type 紧连 (看起来像是个函式);冒号使用英文半型,且之后必须要有一个空白。
- header 尽量控制在 50 字之内,太长则应该考虑精简叙述
- 每行应该不超过 72 字距,太长则应该自行排版换行
- header/body/footer 之间,应该空一行。
- header 有严格的格式规范,必须用一行说明一个 commit 的目的 (见后面说明)。
- body 内要敘述「做了什麼 (What)」和「為什麼做 (Why)」,而不要寫「如何做 (How)」
- footer 说明完成了哪些 ticket
- commit 本身应该就提供足够的资讯给 reviewer,不该在 merge request 内做额外说明。
范例
分支的多个提交历史:
// 分支为 `feat/JIRA-388-new-API-to-update-user-info` // 含有以下的提交 (只显示 header) * test(单元测试): 新增 WebAPI 测试
* update(Pods): 更新第三方套件 AFNetworking
* feat(API): 新增 update user 的 API,并实现
* feat(使用者 UI): 新增编辑使用者资讯的页面
* feat(缓存): 新增 APICache 类,并实现 cache 功能
* feat(默认图片): 无自定图片图片时,内定的头像图片
* chore(编译设定): info.plist 编译设定
* docs(README): 修改 README 文件叙述,完成 API 模组
或是参考 conventional-changelog 的提交历史
<type> 格式说明
跟 branch names 一样,采用以下的分类 (angular.js 的命名规范定义):
- feat:变更功能、新开发需求、第三方库引入
- fix:修复 bug (无论是在哪个主分支上修复)
- add:增加文件 fi
- refactor:重构、调整架构,不新增功能、不修复 bug (但可能会增加测试代码)
- perf:增进效能,优化
- test:增加测试为主 (可能伴随著重构)
以及新增以下允许的 types:
- update (从原本的 feat 抽出):变更已存在的需求、第三方库更新
- deprecated (从原本的 feat 抽出):废弃功能、第三方库。通常不直接删除,而是使用条件编译,或是将代码、资源搬至一个废弃资料夹,以备将来需要回复使用。
<scope> 格式说明
这个部分表明了变更范围。但好的 scope 应该要指出此改变会影响的范围,让代码审查者了解,让测试人员知道要测试什么,如动了缓存,则写缓存,改了支付模块,就写支付等,能迅速看到影响范围或者模块。
以下是一些建议:
- 以简单的一个单字来叙述,越简洁越好
- 可以用简写或是缩写来定义,只要大家知道就好
- 同样一个 scope 应该大家都会用一样的 scope 叙述,大小写也应该一致 (不要用多个单字来解释同一个 scope)。
- scope 的叙述不要含空白,若是英文单字,则使用骆驼式命名法 (Camel-Case)
- 能够让其他开发者猜出你可能会修改的范围 (某个档案、或是某个资料夹 … 等等)
- 能够让测试者比较有概念知道你修改代码影响的部份
- 改了不同范围的东西,或许拆开成不同的 commit 来叙述会比较好。
- 如果真的想不到一个专有名词,可以使用一个通称,例如 fix(err)、fix(bug)
- 如果改的东西实在太杂,则可以写 (misc) (miscellaneous)。(不推荐)
- 如果真的没有什么可以叙述改动范围的,那就不写 scope。(不推荐)
例如:
- type 若是 test,scope 可能就是 UnitTest, UITest, BehaviorTest…
- type 若是 perf,scope 可能就是 LoadingTime, MemoryFootprint…
<subject> 格式说明
- 以动词和命令语气为开头 (add、delete、update、modify),而不要使用过去式、或动名词 (added、adding)
- 开头不必大小;除了专有名词,尽量全小写
- 句尾不必加上句号
注意事项
一个 commit 只会有一种代码集合
一个提交(commit)中应该包含有所有发布需要的代码和资源。
现在很多开发都会使用到第三方套件,并用自动化工具来管理。这些自动化工具可以设定第三方库依赖的最低版本,并提供了自动更新的机制。
若每次从一同个提交签出 (checkout) 时代码都不同 (例如依赖的远端库已经更新版本),那么执行结果可能就会和之前打包的有所不同,这样会增加厘清问题上的困难和额外的成本。
因此,在提交一个 commit 时,当下使用的第三方库的版本应该要固定,以确保任何时间签出编译的结果,永远会一致,得到一样的结果。
不该使用代码注释来控制打包
许多项目都会使用条件编译来控制编译条件 (例如 C macros)。例如在开发、测试以及上线版时,设定不同的访问伺服器的 URL、API keys 等。
但使用「注释 / 反注释 (comment/uncomment)」来决定打包时要采用哪些代码、变数设定,这个行为是不被允许的。因为这会带来一些问题:
- 每次发布时,要检查所有的变数是否有正确的注释 / 反注释。这样人为花费时间长,容易出错。
- 万一出错的时候,找问题的成本高,因为不确定当初编译时的注释是否正确。
- 无法达到自动化打包 (自动化工具不会更改代码)
因此,应该使用 scheme,而不该使用 comment/uncomment,来控制编译 / 打包的条件参数。
另外,若有部分代码需暂时移除,也应该使用条件编译,而非注解的方式移除。
不要任意变更共同开发的分支历史
- 共同使用的分支 (尤其是主要分支),不应该任意变更历史,这样会发生同步后的开发分歧。
- 所有主要分支,将变更推送到远端之前,请再三确认没有问题,因为推送之后,就变成了共同开发的远端分支,到时候任意变更容易带来麻烦。我们会强制设定一般开发者不能推送主分支,只能藉由 merge request 来合并进入主分支。
- 如果要取消过去的某个 commit,使用 revert
- 若非得修改共同使用的分支,应该在操作前和大家提醒,并先将代码合并。
- 拉取远端代码时,永远使用 pull –rebase,而不要单纯使用 pull (采用 merge),避免拉取时因与远端分歧而产生意外的合并。
合并分支时采用 always rebase 和 always non-fast-forward merge
关于是否要使用 always rebase,还是单纯的使用 always merge,一直以来有两派的支持者,也各有优缺点。更多的议题讨论,可以参考这篇 “Git team workflows: merge or rebase?” 或是 “Getting solid at Git rebase vs. merge”。
使用 rebase 可以带来清楚的提交历史,并免多人开发时导致的历史混乱。但使用 rebase 有些注意事项,可以参考这篇「The Golden Rule of Rebasing」。
至于 always non-fast-forward merge,可以弥补 rebase 加上 fast-forward merge 形成单一线条的一个缺点:无法追踪一个开发的起始和结束。这也是 Git Flow 的标准。
不要使用 git add .,提交代码之前,应该自行 review
- 不建议使用命令列使用 add/commit,建议使用图形化工具来执行 (例如 SourceTree)
- 不要使用 git add . 操作,除非你已经自我 review 完所有需要的提交的内容
- 尽量不要使用 git commit -m “…” 操作,commit message 应该要符合规范 (可能会有多行)
- 所有的 add/commit 之前,都必须自我 review,包括:
- review 档案:是否正确含有不该提交、或是忘记提交的档案 (包括图片等资源)。
- review 代码:是否有不该提交、或是忘记修改的代码。
- 绝大多数的错误,都是在自我 review 时就可以发现。
改变 git 提交历史的操作,必须在每步骤操作之后进行确认结果
- 每次操作会改变提交历史的指令,都应该在操作后立刻检查是否操作成功
- 尤其是初学者操作 add、commit、rebase、merge、pull,一定要在操作后检查。有问题就立刻排除。
- 建议使用图形化工具检查,或是 git log –oneline –graph –decorate –all 检查。