Git commit message 规范


Git commit message 规范

参考 AngularJS

Commit message 的作用

格式化的Commit message,有几个好处。

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

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

git log   HEAD --pretty=format:%s

git log --pretty=format:"%h - %an, %ar : %

可以过滤某些commit(比如文档改动),便于快速查找信息。

比如,下面的命令仅仅显示本次发布新增加的功能。

$ git log HEAD --grep feature

可以直接从commit生成Change log。

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

Commit message 格式

<type>(<scope>): <subject>
<空行>
<body>
<空行>
<footer>

上面是一次Commit后Message格式规范,分成标题,内容详情,结尾三个部分,各有各的用处,没有多余项。

头部即首行,是可以直接在页面中预览的部分,入上面图中所示,一共有三个部分,含义分别如下

Type

  • feat:新功能(feature)

  • fix:修补bug

  • docs:文档(documentation)

  • style: 不影响功能的代码变化(空格,格式化,丢失分号等)

  • refactor:重构(即不是新增功能,也不是修改bug的代码变动)

  • test:增加测试

  • chore:构建过程或辅助工具的变动

Scope 用来说明本次Commit影响的范围,即简要说明修改会涉及的部分。这个本来是选填项,但从AngularJS实际项目中可以看出基本上也成了必填项了。

Subject 用来简要描述本次改动,概述就好了,因为后面还会在Body里给出具体信息。并且最好遵循下面三条:

以动词开头,使用第一人称现在时,比如change,而不是changed或changes 首字母不要大写 结尾不用句号(.)。

Body body 里的内容是对上面subject里内容的展开,在此做更加详尽的描述,内容里应该包含修改动机和修改前后的对比。

Footer footer里的主要放置不兼容变更和Issue关闭的信息

Closes #23

也可以一次关闭多个 issue

Closes #56, #25, #95

Revert 此外如果需要撤销之前的Commit,那么本次Commit Message中必须以revert:开头,后面紧跟前面描述的Header部分,格式不变。并且,Body部分的格式也是固定的,必须要记录撤销前Commit的SHA值。

使用Commitizen

安装

为了让我们能把这些规范应用到实际使用中,我们要借助于Commitizen这个Node工具,它会在我们Commit的过程中更具规范的内容来引导我们如何一步一步实施规范。当然,规范这种东西就没有唯一的,各家有各家的不同,这一点当然也被该工具想到了,你也可以自定义一份自己的规范,以插件的形式让Commitizen来根据自家规范提醒你。

npm install -g commitizen

配置

如果项目中没有 package.json, 需要新建一个文件

nano package.json

文件内容如下:

{
  "devDependencies": {
    "cz-conventional-changelog": "^1.1.6"
  }
}

上一步我们在全局范围内安装了commitizen,之后我们就可以在Git仓库中配置我们的Commit规范了。打开项目执行如下命令:

commitizen init cz-conventional-changelog –save –save-exact

上面的cz-conventional-changelog就是AngularJS的规范,其它的规范你可以自行到官网上找找看,不行就自己花时间拟定一份吧。 此命令帮你完成了下载cz-conventional-changelog规范,配置package.json(添加依赖和配置应用规范),想看具体改动打开package.json即可。

使用

以后,凡是用到git commit命令,一律改为使用

git cz 这时,就会出现选项,用来生成符合格式的 Commit message。

Validate-commit-msg

validate-commit-msg 用于检查 Node 项目的 Commit message 是否符合格式。

它的安装是手动的。首先,拷贝下面这个JS文件,放入你的代码库。文件名可以取为validate-commit-msg.js。

接着,把这个脚本加入 Git 的 hook。下面是在package.json里面使用 ghooks,把这个脚本加为commit-msg时运行。

  "config": {
    "ghooks": {
      "commit-msg": "./validate-commit-msg.js"
    }
  }

然后,每次git commit的时候,这个脚本就会自动检查 Commit message 是否合格。如果不合格,就会报错。

生成 Change log

如果你的所有 Commit 都符合 Angular 格式,那么发布新版本时, Change log 就可以用脚本自动生成(例1,例2,例3)。

生成的文档包括以下三个部分。

New features Bug fixes Breaking changes. 每个部分都会罗列相关的 commit ,并且有指向这些 commit 的链接。当然,生成的文档允许手动修改,所以发布前,你还可以添加其他内容。

conventional-changelog 就是生成 Change log 的工具,运行下面的命令即可。

npm install -g conventional-changelog cd my-project conventional-changelog -p angular -i CHANGELOG.md -w 上面命令不会覆盖以前的 Change log,只会在CHANGELOG.md的头部加上自从上次发布以来的变动。

如果你想生成所有发布的 Change log,要改为运行下面的命令。

conventional-changelog -p angular -i CHANGELOG.md -w -r 0 为了方便使用,可以将其写入package.json的scripts字段。

{
  "scripts": {
    "changelog": "conventional-changelog -p angular -i CHANGELOG.md -w -r 0"
  }
}

以后,直接运行下面的命令即可。

npm run changelog

node_modules/cz-conventional-changelog/index.js


"format cjs";

var wrap = require('word-wrap');

// This can be any kind of SystemJS compatible module.
// We use Commonjs here, but ES6 or AMD would do just
// fine.
module.exports = {

  // When a user runs `git cz`, prompter will
  // be executed. We pass you cz, which currently
  // is just an instance of inquirer.js. Using
  // this you can ask questions and get answers.
  //
  // The commit callback should be executed when
  // you're ready to send back a commit template
  // to git.
  //
  // By default, we'll de-indent your commit
  // template and will keep empty lines.
  prompter: function(cz, commit) {
    console.log('\nLine 1 will be cropped at 100 characters. All other lines will be wrapped after 100 characters.\n');

    // Let's ask some questions of the user
    // so that we can populate our commit
    // template.
    //
    // See inquirer.js docs for specifics.
    // You can also opt to use another input
    // collection library if you prefer.
    cz.prompt([
      {
        type: 'list',
        name: 'type',
        message: '选择您正在提交的内容:',
        choices: [
        {
          name: 'Feature: 新功能添加',
          value: 'feat'
        }, {
          name: 'Fix bug: 修补bug',
          value: 'fix'
        }, {
          name: 'Documentation: 文档添加及修改',
          value: 'docs'
        }, {
          name: 'Style: 不影响功能的代码变化(空格,格式化,丢失分号等)',
          value: 'style'
        }, {
          name: 'Refactor:    代码重构',
          value: 'refactor'
        }, {
          name: 'Performance improves:    提高性能的代码更改',
          value: 'perf'
        }, {
          name: 'Add test:    添加测试代码,或功能测试代码添加',
          value: 'test'
        }, {
          name: 'Auxiliary tools:    改变构建过程或辅助工具和库,如文档生成',
          value: 'chore'
        }]
      }, {
        type: 'input',
        name: 'scope',
        message: '用来说明本次Commit影响的范围,即简要说明修改会涉及的部分。:\n'
      }, {
        type: 'input',
        name: 'subject',
        message: '用来简要描述本次改动,概述就好了,因为后面还会在Body里给出具体信息:\n'
      }, {
        type: 'input',
        name: 'body',
        message: '对上面subject里内容的展开,在此做更加详尽的描述,内容里应该包含修改动机和修改前后的对比:\n'
      }, {
        type: 'input',
        name: 'footer',
        message: '不兼容变更和Issue关闭的信息:\n,如:  Closes #56, #25'
      }
    ]).then(function(answers) {

      var maxLineWidth = 100;

      var wrapOptions = {
        trim: true,
        newline: '\n',
        indent:'',
        width: maxLineWidth
      };

      // parentheses are only needed when a scope is present
      var scope = answers.scope.trim();
      scope = scope ? '(' + answers.scope.trim() + ')' : '';

      // Hard limit this line
      var head = (answers.type + scope + ': ' + answers.subject.trim()).slice(0, maxLineWidth);

      // Wrap these lines at 100 characters
      var body = wrap(answers.body, wrapOptions);
      var footer = wrap(answers.footer, wrapOptions);

      commit(head + '\n\n' + body + '\n\n' + footer);
    });
  }
}