如何优雅地提交Git Commit Message

2017/7/3 Git代码管理

How to submit Git Commit Message gracefully?

# commit规范

社区有多种 Commit message 的写法规范。本文介绍 Angular规范 (opens new window)(见上图),这是目前使用最广的写法,比较合理和系统化,并且有配套的工具。

# 格式化Commit message好处

  1. 提供更多的历史信息,方便快速浏览。

    比如,下面的命令显示上次发布后的变动,每个commit占据一行。你只看行首,就知道某次 commit 的目的。

    $ git log <last tag> HEAD --pretty=format:%s
    
    1

  2. 可以过滤某些commit(比如文档改动),便于快速查找信息。 比如,下面的命令仅仅显示本次发布新增加的功能。

    $ git log <last release> HEAD --grep feature
    
    1
  3. 可以直接从commit生成Change log。

    Change Log 是发布新版本时,用来说明与上一个版本差异的文档,详见后文。

# Angular规范

每次提交,Commit message 都包括三个部分:Header,Body 和 Footer。

<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>

<type>(<scope>): <subject>
// 空一行
<body>
// 空一行
<footer>
1
2
3
4
5
6
7
8
9
10
11

其中,Header 是必需的,Body 和 Footer 可以省略。

不管是哪一个部分,任何一行都不得超过72个字符(或100个字符)。这是为了避免自动换行影响美观。

我们通过 git commit 命令带出的 vim 界面填写的最终结果应该类似如上这个结构, 大致分为三个部分(使用空行分割):

  • 标题行(必须): 必填, 描述主要修改类型和内容
  • 主题内容(非必须): 具体修改内容, 描述为什么修改, 做了什么样的修改, 以及开发的思路等等,可以分为多行, 建议符合 50/72 formatting (opens new window)
  • 页脚注释(非必须): 一些备注,通常放 Breaking Changes 或 Closed Issues 或修复的 bug 的链接.

Header部分只有一行,包括三个字段:type(必需)、scope(可选)和subject(必需)。

  • type: type用于说明 commit 的类别,只允许使用下面7个标识。
    • build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm,webpack)
    • ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs,GitHub Actions)
    • docs:Documentation only changes. 文档修改(documentation)
    • feat:A new feature. 新功能
    • fix:A bug fix. 修补bug
    • perf : A code change that improves performance. 性能优化.
    • refactor:A code change that neither fixes a bug nor adds a feature. 重构(即不是新增功能,也不是修改bug的代码变动)
    • style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc). 代码格式修改(不影响代码运行的变动)
    • test:Adding missing tests or correcting existing tests. 增加测试
    • chore:琐事
    • revert commit:回退版本
  • scope:commit 影响的范围, 比如: route, component, utils, build, $location, $browser, $compile, $rootScope, ngHref, ngClick, ngView, etc...
  • subject: commit的简单描述,不能超过50个字符,建议符合 50/72 formatting
    • use the imperative, present tense: "change" not "changed" nor "changes"
    • don't capitalize first letter. 不要大写
    • no dot (.) at the end. 末尾不要句号

# Body

commit的具体描述,可以是多行的

  1. Breaking Changes should start with the word BREAKING CHANGE: with a space or two newlines. The rest of the commit message is then used for this. 不兼容变动(BREAKING CHANGE),如果是上个版本不兼容的改动,用BREAKING CHANGE作为开头
  2. 关闭 Issue,例如 Closes #234

# 例子

feat

feat($browser): onUrlChange event (popstate/hashchange/polling)

Added new event to $browser:
- forward popstate event if available
- forward hashchange event if popstate not available
- do polling when neither popstate nor hashchange available

Breaks $browser.onHashChange, which was removed (use onUrlChange instead)
1
2
3
4
5
6
7
8

fix

fix($compile): couple of unit tests for IE9

Older IEs serialize html uppercased, but IE9 does not...
Would be better to expect case insensitive, unfortunately jasmine does
not allow to user regexps for throw expectations.

Closes #392
Breaks foo.bar api, foo.baz should be used instead
1
2
3
4
5
6
7
8

docs

docs(guide): updated fixed docs from Google Docs

Couple of typos fixed:
- indentation
- batchLogbatchLog -> batchLog
- start periodic checking
- missing brace
1
2
3
4
5
6
7

feat with BREAKING CHANGE

feat($compile): simplify isolate scope bindings

Changed the isolate scope binding options to:
  - @attr - attribute binding (including interpolation)
  - =model - by-directional model binding
  - &expr - expression execution binding

This change simplifies the terminology as well as
number of choices available to the developer. It
also supports local name aliasing from the parent.

BREAKING CHANGE: isolate scope bindings definition has changed and
the inject option for the directive controller injection was removed.

To migrate the code follow the example below:

Before:

scope: {
  myAttr: 'attribute',
  myBind: 'bind',
  myExpression: 'expression',
  myEval: 'evaluate',
  myAccessor: 'accessor'
}

After:

scope: {
  myAttr: '@',
  myBind: '@',
  myExpression: '&',
  // myEval - usually not useful, but in cases where the expression is assignable, you can use '='
  myAccessor: '=' // in directive's template change myAccessor() to myAccessor
}

The removed `inject` wasn't generaly useful for directives so there should be no code using it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

Revert:如果当前 commit 用于撤销以前的 commit,则必须以revert:开头,后面跟着被撤销 Commit 的 Header。Body部分的格式是固定的,必须写成This reverts commit &lt;hash>.,其中的hash是被撤销 commit 的 SHA 标识符。如果当前 commit 与被撤销的 commit,在同一个发布(release)里面,那么它们都不会出现在 Change log 里面。如果两者在不同的发布,那么当前 commit,会出现在 Change log 的Reverts小标题下面。

revert: feat(pencil): add 'graphiteWidth' option

This reverts commit 667ecc1654a317a13331b17617d973392f415f02.
1
2
3

# Commitizen: 替代你的 git commit

commitizen/cz-cli (opens new window), 我们需要借助它提供的 git cz 命令替代我们的 git commit 命令,使用Node开发, 帮助我们生成符合规范的 commit message.

我们还需要为 commitizen 指定一个 Adapter 比如: cz-conventional-changelog (opens new window) (一个符合 Angular团队规范的 preset). 使得 commitizen 按照我们指定的规范帮助我们生成 commit message.

# Install

global:全局模式下, 需要 ~/.czrc 配置文件, 为 commitizen 指定 Adapter.

npm install -g commitizen cz-conventional-changelog
echo '{ "path": "cz-conventional-changelog" }' > ~/.czrc
1
2

local:

npm install -D commitizen cz-conventional-changelog

// package.json中配置:
"script": {
    ...,
    "commit": "git-cz",
},
 "config": {
    "commitizen": {
      "path": "node_modules/cz-conventional-changelog"
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12

# commit

git cz or npm run commit

# Commitlint: 校验你的 message

conventional-changelog/commitlint (opens new window)

可以帮助我们 lint commit messages, 如果我们提交的不符合指向的规范, 直接拒绝提交, 比较狠.

同样的, 它也需要一份校验的配置, 这里推荐 @commitlint/config-conventional (opens new window) (符合 Angular团队规范).

Install

npm install -g @commitlint/cli @commitlint/config-conventional
1

Configure

echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js
1

To lint commits before they are created you can use Husky's 'commit-msg' hook: 如果想在commit的时候触发运行,可以使用Husky

typicode/husky (opens new window)

// install
npm install husky --save-dev

// package.json
{
  "husky": {
    "hooks": {
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11

# conventional-changelog-cli 自动生成 CHANGELOG

conventional-changelog-cli 默认推荐的 commit 标准是来自angular项目,除了 angular 标准以外,目前集成了包括 atom, codemirror, ember, eslint, express, jquery 等项目的标准,具体可以根据自己口味来选用。

下面案例按照AngularJS的规范,如果所有的提交记录都符合AngularJS的规范,使用命令来自动生成changelog文件。

Install

// 必须安装conventional-changelog-cli的依赖
npm i -D conventional-changelog-cli

// package.json中配置:
{
  "scripts": {
    "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s"
  }
}
1
2
3
4
5
6
7
8
9

生成的文档只会收集type为feat、fix还有Breaking changes这三种类型的提交记录。

  1. Make changes
  2. Commit those changes
  3. Make sure Travis turns green
  4. Bump version in package.json
  5. conventionalChangelog
  6. Commit package.json and CHANGELOG.md files
  7. Tag
  8. Push

The reason why you should commit and tag after conventionalChangelog is that the CHANGELOG should be included in the new release, hence gitRawCommitsOpts.from defaults to the latest semver tag.

Last Updated: 2022/1/8 04:00:18