作为一名程序员,代码托管大多数用的都是git。在个人的学习或工作中,都会提交代码至git远程仓库中。随着项目增大,开发人员增多,每个人在提交代码时携带的message也是层出不穷,五花八门。

有这样的:

commit提交反面例子 

这样的:

commit提交反面例子

随着时间推移,项目历史久远,开发人员回溯历史的时候,看到张三\李四在之前提交的代码记录全部都是abcd 1234。心中瞬间一万只草泥马奔腾~ 。由此可见,规范正式的commit提交对开发者而言是多么重要

那优雅规范的commit提交是什么样子的呢?

commit提交规范后的样子 

从图中可以清晰知道何人何时做了什么事情。 如果我们也想使用这种格式来规范自己或团队的commit提交,该如何做呢? 我们前期需要准备以下三样东西:

  1. husky (Github传送门)
  2. commitizen/cz-cli  (Github传送门)
  3. commitlint  (Github传送门)

1. husky v8(8.0.2)

一个简单易用的Hooks工具,可以让我们快速的使用Git Hooks。

1.1 🤔Git Hooks是什么?

Git Hook是Git在工作流的生命周期内调用外部函数的Hook函数。以Git commit 举例,在commit过程中,git为开发者准备了4个Hooks。按照生命周期从前往后依次是:

  1. pre-commit

提交之前 (我们可以在这里做一些代码格式化的操作)

  1. prepare-commit-msg

准备提交 (我们提交信息要用到的一些规范化日志的工具准备工作)

  1. commit-msg

开始提交 (验证我们提交的信息是否符合规范)

  1. post-commit

提交成功 (已经将代码提交了。即 git commit之后调用的Hooks,这个我们一般用的比较少)

1.2 😀安装 Husky(单个项目安装)
npm install husky -d
or 
yarn add husky --dev
把husky安装到dev依赖中‌

安装好husky后,在package.json script 中添加一条prepare的husky安装指令。 (prepare是npm的生命周期执行命令,详情可见:Life Cycle Scripts,这里不做详细说明)

安装好husky后,在package.json script 中添加一条prepare的husky安装指令。(prepare是npm的生命周期执行命令,详情可见:Life Cycle Scripts,这里不做详细说明)

添加husky安装指令如下:

"scripts": {
  "prepare": "husky install"
}

添加完上述命令之后,运行npm install 或 yarn install,npm会在当前install执行完之后,运行prepare脚本,会把husky安装一次,安装成功之后,控制台会输出husky - Git hooks installed的字样,同时项目根目录里会出现.husky的文件夹。

1.3 😀添加Git Hooks

通过husky我们可以添加Git hooks,我们先尝试添加pre-commit的hook。在scripts中,我们添加两条命令:

"scripts":{
	"husky": "node node_modules/husky/lib/bin",//快速执行husky
	"add-pre-commit": "npm run husky add .husky/pre-commit \"npm run pre-commit\"",
	"pre-commit": "console.log('pre-commit')"
}

add-pre-commit命令是通过运行husky,添加一条git 出于pre-commit周期时执行的命令。

pre-commit命令是我们模拟开始格式化文件的相关操作

在上面代码中,我们告诉husky,当git处于pre-commit周期时,会同步运行我的pre-commit命令去做一些“格式化代码”的事情。

提交代码时,会执行我们的脚本命令

同理接下来我们再补全其他git提交工作流的hooks,并关联到我们在1.2中提到npm prepare环节中,从而实现clone项目到install安装依赖的环节就会把我们的husky以及对应的git hooks安装好:

"scripts": {
	"prepare": "husky install & npm run add-pre-commit & npm run add-prepare-commit-msg & npm run add-commit-msg & npm run add-post-commit",
	"husky": "node node_modules/husky/lib/bin",
	"add-pre-commit": "npm run husky add .husky/pre-commit \"npm run pre-commit\"",
	"add-prepare-commit-msg": "npm run husky add .husky/prepare-commit-msg \"npm run prepare-commit-msg\"",
	"add-commit-msg": "npm run husky add .husky/commit-msg \"npm run commit-msg\"",
	"add-post-commit": "npm run husky add .husky/post-commit \"npm run post-commit\"",
	"pre-commit": "console.log('pre-commit')",
	"prepare-commit-msg": "console.log('prepare-commit-msg')",
	"commit-msg": "console.log('commit-msg')",
	"post-commit": "console.log('post-commit')"
}
补全Git提交工作流中剩余生命周期的Hooks
1.4 🤔hooks重复添加的问题

当在package.json新增加一个依赖,重新npm install 或yarn install。依旧会执行我们的“prepare” 指令,会导致我们的Hooks一直在新增,从而出现hooks重复的问题。所以在每次install前应该先删除本地的Hooks,重新添加。

先添加删除代码

var fs = require('fs'); // 引入fs模块



function deleteall(path) {

    var files = [];

    if (fs.existsSync(path)) {

        files = fs.readdirSync(path);

        files.forEach(function (file, index) {

            var curPath = path + "/" + file;

            if (fs.statSync(curPath).isDirectory()) { // recurse

                deleteall(curPath);

            } else { // delete file

                fs.unlinkSync(curPath);

            }

        });

        fs.rmdirSync(path);
    }
    throw Error(`${path}文件夹不存在`)

};
const path = '.husky';
try {
    console.log(`开始删除${path}文件夹`);
    deleteall('.husky');
    console.log(`删除${path}文件夹成功`);
} catch (error) {
    console.log(`删除${path}文件夹失败,可能该文件夹不存在`);
}

删除husky hooks代码的deletejs 

再给“prepare” 指令中,每次运行前执行一次delete。husky就安装完成了。

完整package.json如下:

{
  "scripts": {
    "prepare": "npm run delete-husky ./husky && husky install & npm run add-pre-commit & npm run add-prepare-commit-msg & npm run add-commit-msg & npm run add-post-commit",
    "husky": "node node_modules/husky/lib/bin",
    "delete-husky": "node ./script/delete.js",
    "add-pre-commit": "npm run husky add .husky/pre-commit \"npm run pre-commit\"",
    "add-prepare-commit-msg": "npm run husky add .husky/prepare-commit-msg \"npm run prepare-commit-msg\"",
    "add-commit-msg": "npm run husky add .husky/commit-msg \"npm run commit-msg\"",
    "add-post-commit": "npm run husky add .husky/post-commit \"npm run post-commit\"",
    "pre-commit": "console.log('pre-commit')",
    "prepare-commit-msg": "console.log('prepare-commit-msg')",
    "commit-msg": "console.log('commit-msg')",
    "post-commit": "console.log('post-commit')"
  },
  "devDependencies": {
    "husky": "^8.0.2"
  }
}

2. 😋commitizen/cz-cli

commitizen/cz-cli是一个命令行工具,作用于提交代码时的一些规则选项,通过这个命令行我们可以按照期望规则自主有序的选择message内容。如下图:
cz-cli提交示例

2.1 😀安装和配置

先安装cz-cli

npm install commitizen -D
or
yarn add commitizen --dev

安装好cz-cli之后,我们还需要安装一个规则适配器,这个规则适配器会告诉cz-cli以哪种规则作为基准的一个规范。这里我们以AngularJS的提交规则作为规范,并且为了直观美化,我们使用国人修改的汉化版:cz-conventional-changelog-zh也还可以选择其他规则适配器

npm install cz-conventional-changelog-zh -D
or
yarn add cz-conventional-changelog-zh --dev

再把cz-conventional-changelog-zh通过配置文件的方式和cz-cli关联起来。为了更加美观,我们尝试把emoji添加到commit type中(即上图中feat、fix这种类型)。配置方式有配置文件或package.json里配置两种,具体可参考官方配置说明。在这里我选择使用配置文件的方式来进行配置。在当前项目根目录里新建 .czrc 文件。文件内容完成如下:

{
  "path": "cz-conventional-changelog-zh",
  "disableScopeLowerCase": false,
  "disableSubjectLowerCase": false,
  "maxHeaderWidth": 100,
  "maxLineWidth": 100,
  "defaultType": "",
  "defaultScope": "",
  "defaultSubject": "",
  "defaultBody": "",
  "defaultIssues": "",
  "types": {
    "feat": {
      "description": "✨ 开发了一个新功能或新模块",
      "title": "Features | 新功能",
      "emoji": "✨"
    },
    "fix": {
      "description": "🐛 解决了一些bug",
      "title": "Bug Fixes | Bug 修复",
      "emoji": "🐛"
    },
    "docs": {
      "description": "📚 文档相关的更改",
      "title": "Documentation | 文档",
      "emoji": "📚"
    },
    "style": {
      "description": "🎨 不影响功能的代码格式化修改。例如: 删除空格",
      "title": "Styles | 风格",
      "emoji": "🎨"
    },
    "refactor": {
      "description": "📦 不修复错误、不新加功能的代码重构",
      "title": "Code Refactoring | 代码重构",
      "emoji": "📦"
    },
    "perf": {
      "description": "⚡️ 性能优化、调优",
      "title": "Performance Improvements | 性能优化",
      "emoji": "⚡️"
    },
    "test": {
      "description": "✅ 添加测试或修改测试",
      "title": "Tests | 测试",
      "emoji": "✅"
    },
    "chore": {
      "description": "♻️ 其他更新",
      "title": "Chores | 其他更新",
      "emoji": "♻️"
    },
    "revert": {
      "description": "⏪ 回退到某个版本",
      "title": "Reverts | 回退",
      "emoji": "⏪"
    },
    "conflict": {
      "description": "🔀 解决冲突",
      "title": "Conflict | 冲突",
      "emoji": "🔀"
    },
    "delete":{
      "description": "🔥删除代码或者文件",
      "title": "Delete | 删除",
      "emoji": "🔥"
    }
  }
}

当前安装方式是当前项目安装,非全局安装。即官方的git-cz命令无法使用,我们可以在script里加一条命令commit:"git-cz",再使用npm或者yarn来运行commit命令查看效果:
commitizen-demo
可以看到已经配置成功,但对大多数开发者而言不是很友好。为什么呢?因为当要运行提交代码命令时,不再是传统的git commit的命令,而是运行自己写的commit:"git-cz"命令。🙁🙁🙁 那么如何关联到git commit命令呢?

2.2 🧐 通过Husky关联Git Hooks

可以通过上文提及的Husky来和git commit这类git工作代码进行关联。按照cz-cli的推荐方式,给我们的prepare-commit-msg命令增加如下内容:

#!/bin/bash
exec < /dev/tty && node_modules/.bin/cz --hook || true

这是一段bash命令,用于告诉git来运行cz-cli。我使用的是windows环境,bash命令默认是用不了的。因此还需要添加两个windows可执行的脚本代码。(exec-git-cz.cmd和parepare-commit-msg.cmd
参考链接:https://github.com/commitizen/cz-cli/issues/627)

@ECHO off
SETLOCAL

CALL :find_dp0
set "_prog=%dp0%node_modules\.bin\git-cz.cmd"
IF NOT EXIST "%_prog%" (
    ECHO "%_prog% does not exist, please install commitizen"
    ENDLOCAL
    EXIT /b 1
)

call "%_prog%" %*

ENDLOCAL
EXIT %errorlevel%

:find_dp0
SET dp0=%~dp0
EXIT /b
exec-git-cz.cmd
@ECHO off
SETLOCAL

CALL :find_dp0
set "_prog=%dp0%exec-git-cz.cmd"
IF NOT EXIST "%_prog%" (
    ECHO "%_prog% does not exist"
    ENDLOCAL
    EXIT /b 1
)
start /wait call "%_prog%" --hook %*)

:find_dp0
SET dp0=%~dp0
EXIT /b
parepare-commit-msg.cmd

然后将我们script-->prepare-commit-msg的内容替换为parepare-commit-msg.cmd。接着尝试运行git commit命令,windows会新弹出一个CMD窗口来选择此次提交的信息选项。
windows-git-commit

3.😋commitlint

和cz-cli一样也是一个命令行,只不过cz-cli是用于提交commit message,而commitlint用于检测commit message是否符合规范的。因为之前在配置commitizen/cz-cli阶段中是以AngularJS的提交规则作为规范。所以我们再配置commitlint也用此规范的适配器做为校验。

3.1 😀安装 @commitlint/config-conventional @commitlint/cli
npm install @commitlint/config-conventional @commitlint/cli -d
or
yarn add @commitlint/config-conventional @commitlint/cli --dev
3.2 👨‍💻配置

在当前项目根目录新建commitlint.config.js,添加如下内容

module.exports = {
    extends: ["@commitlint/config-conventional"], 
};

配置内容是告诉commitlint,我们使用哪种适规则配置。(查看更多适配器可点击这里)添加完之后,当commit message为错误的信息的时候则会被commitlint打回,无法提交也更加无法push到仓库中。
commitlint_error_commit_message

3.3 💪关联husky

当我们输入完commit message时,需要告诉husky,给我们执行commit-msg hook函数,从而在我们在执行git commit时就验证我们的提交的内容是是否规范。因此,我们需要把commit-msg内容修一下。官方推荐如下:

npx --no -- commitlint --edit ${1}

乍眼一看这段命令没有什么问题,通俗解释出来就是让npm运行commitlint去打开${1}这份文件。问题来了:${1}是shell变量,windows也不支持。所以我们需要将其修改为如下命令才可以适配,把${1}修改为.git/COMMIT_EDITMSG(COMMIT_EDITMSG是我们此次提交输入的信息暂存区)。

node_modules/.bin/commitlint --edit .git/COMMIT_EDITMSG

添加完之后,运行git commit,就会自动校验内容是否合规了。