一、Git钩子
Git 能在特定的重要动作发生时触发自定义脚本,它能完成下列一些很常用的场景:
1.多人开发代码语法、规范强制统一
2.commit message 格式化、是否符合某种规范
3.如果有需要,测试用例的检测
4.服务器代码有新的更新的时候通知所有开发成员
5.代码提交后的项目自动打包(git receive之后) 等等...
每一个使用了 git 的工程下面都有一个隐藏的 .git 文件夹。
挂钩都被存储在 .git 目录下的 hooks 子目录中,即大部分项目中的 .git/hooks。 如下图:
Git 默认会放置一些脚本样本在这个目录中,除了可以作为挂钩使用,这些样本本身是可以独立使用的。所有的样本都是shell脚本,其中一些还包含了Perl的脚本。不过,任何正确命名的可执行脚本都可以正常使用 ,也可以用Ruby或Python,或其他脚本语言。
上图是git 初始化的时候生成的默认钩子,已包含了大部分可以使用的钩子,但是 .sample 拓展名防止它们默认被执行。为了安装一个钩子,你只需要去掉 .sample 拓展名。或者你要写一个新的脚本,你只需添加一个文件名和上述匹配的新文件,去掉.sample拓展名。把一个正确命名且可执行的文件放入 Git 目录下的 hooks子目录中,可以激活该挂钩脚本,之后他一直会被 Git 调用。
钩子分为客户端和服务端的,客户端的常用的钩子如下:
pre-commit
钩子在键入提交信息前运行。 它用于检查即将提交的快照,例如,检查是否有所遗漏,确保测试运行,以及核查代码。 如果该钩子以非零值退出,Git 将放弃此次提交,不过你可以用git commit --no-verify
来绕过这个环节。 你可以利用该钩子,来检查代码风格是否一致(运行类似lint
的程序)、尾随空白字符是否存在(自带的钩子就是这么做的),或新方法的文档是否适当。prepare-commit-msg
钩子在启动提交信息编辑器之前,默认信息被创建之后运行。 它允许你编辑提交者所看到的默认信息。 该钩子接收一些选项:存有当前提交信息的文件的路径、提交类型和修补提交的提交的 SHA-1 校验。commit-msg
钩子接收一个参数,此参数即上文提到的,存有当前提交信息的临时文件的路径。 如果该钩子脚本以非零值退出,Git 将放弃提交,因此,可以用来在提交通过前验证项目状态或提交信息。 在本章的最后一节,我们将展示如何使用该钩子来核对提交信息是否遵循指定的模板。post-commit
钩子在整个提交过程完成后运行。 它不接收任何参数 该钩子一般用于通知之类的事情。
对于任何Git仓库来说钩子都是本地的,而且它不会随着git clone
一起复制到新的仓库。而且,因为钩子是本地的,任何能接触得到仓库的人都可以修改。在开发团队中维护钩子是比较复杂的,因为.git/hooks
目录不随你的项目一起拷贝,也不受版本控制影响。一个简单的解决办法是把你的钩子存在项目的实际目录中(在.git外)。这样你就可以像其他文件一样进行版本控制。作为备选方案,Git同样提供了一个模板目录机制来更简单地自动安装钩子。每次你使用git init
或git clone
时,模板目录文件夹下的所有文件和目录都会被复制到.git文件夹。
二、node项目下的git hook
husky 是一个 Git Hook 工具。husky 其实就是一个为 git 客户端增加 hook 的工具。将其安装到所在仓库的过程中它会自动在.git/
目录下增加相应的钩子实现在pre-commit
阶段就执行一系列流程保证每一个commit
的正确性。部分 cd
在 commit stage
执行的命令可以挪动到本地执行,比如 lint 检查、比如单元测试。当然,pre-commit
阶段执行的命令当然要保证其速度不要太慢,每次 commit
都等很久也不是什么好的体验。
下面使用husky来实现在提交前检查是否有测试快照,提交信息是否符合书写规范的例子
npm i husky happy-git-commit-message-checker -D
package.json中增加如下代码(husky版本大于0.14的写法):
"husky": { "hooks": { "pre-commit": "node ./scripts/pre-commit.js", "commit-msg": "node ./scripts/commit-msg.js", } }
./scripts/pre-commit.js(检查是否有测试快照用的)
const childProcess = require('child_process'); const fs = require('fs'); const path = require('path'); const fileMaxSize = 6 * 1024 * 1024; // 最大6MB childProcess.exec('git status --short -u', (error, stdout) => { console.log('检测.snap快照文件体积'); if (error) { console.log(error); process.exitCode = 1; // 禁止提交 } else { const paths = stdout.split(/s/); for (let i = 0; i < paths.length; i++) { const pt = path.resolve(process.cwd(), paths[i]); if (/.snap$/.test(pt)) { // 是快照文件 let stats; try { stats = fs.statSync(pt); if (stats.size > fileMaxSize) { console.log(`x ${paths[i]} ${stats.size}Byte (单个快照文件体积最大6MB)`); process.exitCode = 1; // 禁止提交 return; } console.log(`√ ${paths[i]} ${stats.size}Byte`); } catch (e) { // } } } } });
./scripts/commit-msg.js(提交信息规范检查)
我们要求的提交信息的规则如下:
-
一律以
【xx页】描述
或【xx模块】描述
的形式书写代码注释 -
优先使用
【xx页】描述
-
描述
需尽可能详细 -
涉及多个页面,每个页面一行,分开描述
const fs = require('fs'); const runner = require('happy-git-commit-message-checker').runner; console.log('检测message书写规范'); try { let message = fs.readFileSync('./.git/COMMIT_EDITMSG', 'utf-8'); const lines = message.split(' '); if (!lines[lines.length - 1]) { lines.pop(); } message = lines.join(' '); runner(message, ['[bB]uild(.*?)']); } catch (e) { console.log('检测程序运行出错...', e); }
如何获取commit message?
需要调用者自己读取/.git/COMMIT_EDITMSG文件内容,在commit-msg钩子触发时,Git会自动将message写入COMMIT_EDITMSG文件,读取即可