Git Hooks及规范提交

git hooks文档:https://git-scm.com/docs/githooks
husky: https://github.com/typicode/husky
lint-staged: https://github.com/okonet/lint-staged

2020.9.18 星期五 :

做一些hooks相关的工作。比如:commit前 检查/格式化代码;规范提交信息/提交信息模版;
CI/CD

git hooks

在项目根目录的 .git/hooks 下面配置,配置文件的名称是固定的,使用shell语法编写。

Git Hooks 介绍

.git/hooks文件下,保存了一些 shell 脚本,然后在对应的钩子中执行这些脚本就行了。
一个还没有配置 Git Hooks 的仓库,默认会有很多.sample结尾的文件,这些都是示例文件
这个脚本默认是不生效的,如果要生效,把文件名后缀去掉就可以了

not set as executable

问题: hint: The ‘.git/hooks/pre-commit’ hook was ignored because it’s not set as executable.

The sample files from a git init are all executable; if it’s copied or renamed to a non-sample file, it will retain the original file’s x flag.
New files will be created with current defaults. In your case, view those defaults with umask:
默认情况下,u+x除非明确设置为新文件,否则不会。
$ umask #0022
解决
chmod +x

自动化脚本

就像附加答案一样,这里是函数,你可以用来初始化一个git存储库,它自动生成钩子可执行文件;
您应该将它放入.bashrc或在启动终端时从源文件中找到它。故事在下面:)

1
2
3
4
5
6
7
8
9
  ginit () {
git init
gitpath=`git rev-parse --show-superproject-working-tree --show-toplevel | head -1`
chmod u+x "$gitpath"/.git/hooks/*
for submodule in "$gitpath"/.git/modules/*; do
chmod u+x "$submodule"/hooks/*
done
}
}

我和你一样生气。我不想记住每次初始化存储库时都必须创建所有挂钩可执行文件。
另外,当你使用子模块时,它们的钩子不在.git/hooks,
但是在.git/modules/NameOfSubmodule/hooks,并且这些钩子也应该是可执行的。

示例shell

模版目录

如果我们所有项目都需要一个通用的钩子,那么我们需要在所有的项目中都放置钩子文件。挨个复制显然不是一个可行的方案。
在 git init 或者 git clone时,如果指定有模板目录,会使用拷贝模板目录下的文件到 .git/ 目录下。

1
2
$ git init --template "path-to-template-dir"
$ git clone --template "path-to-template-dir"

全局配置

1.创建/usr/local/.git_template/.git_template目录
2.将第一步中创建的commit_msg文件拷贝至上方目录
3.使用该命令将git全局配置模版重定向到第一步中创建到目录
git config --global init.templatedit /usr/local/.git_template/.git_template
4.如上三步完成后即可在创建新的使用git管理的项目的时候自动将全局模版拷贝至项目根目录/.git/目录下,
如果完成如上三步后需要对已经存在对git项目使用该模版,可移动至目标项目根目录并执行git init即可

1
2
3
4
5
6
7
8
9
10
11
12
13
# 定义模板目录,模板目录下的钩子目录
$ template_dir=$HOME/.git-templates
$ tempalte_hooks_dir=$template_dir/hooks

# 拷贝全局钩子文件目录到模板目录下
$ mkdir -p $template_dir
$ cp -rf $root_dir/sample/git-template/hooks/ $template_dir/

# 修改模板目录下钩子目录权限
$ chmod -R a+x $tempalte_hooks_dir

# 设置全局模板目录
$ git config --global init.templatedir $template_dir

why husky

.git文件夹不会提交到git,这就导致一个问题,我们在本地配置好 Git Hook 后,怎么分享给其他小伙伴儿呢?copy 吗?
那未免太 low 了,都用 Git 了,还 copy,也太不优雅了。这时候,就轮到

scripts

package.json中也可以设置

1
2
3
4
5
6
7
"scripts": {
"dev": "wxxxx build --dev",
"preinstall": "node build/bin/reset-hooks",
"preparecommitmsg": "node build/bin/custom-lint -e $GIT_PARAMS",
"postcommit": "node build/bin/post-commit",
"prepush": "node build/bin/pre-push -e $GIT_PARAMS",
}

husky

husky: https://typicode.github.io/husky/#/?id=add-a-hook
github: https://github.com/typicode/husky
Husky 是一个让配置 Git 钩子变得更简单的工具(题外话:Husky 是哈士奇的意思,我猜可能是作者养了条二哈)
下面这些流行的项目都在使用 Husky,可见它确实是一个非常好用的工具:webpack, babel, create-react-app

1
2
3
4
5
npm install husky --save-dev
npx husky install
npm pkg set scripts.prepare "husky install"

git config --get core.hooksPath

PS: 原理分析。执行npx husky install的时候,把git hooks 的路径指向.husky。

package.json

1
2
3
4
5
6
7
8
9
"scripts": {
"prepare": "npx husky install",
"lint": "eslint src"
},
"husky": {
"hooks": {
"pre-commit": "npm run lint"
}
},

原理分析

通过查看源码可以看到,在安装 husky 的时候,husky会根据 package.json里的配置,
在.git/hooks 目录生成所有的 hook 脚本(如果你已经自定义了一个hook脚本,husky不会覆盖它)

具体步骤

1、husky 使用了自定义的安装过程:node lib/installer/bin install(在node_modules/husky/package.json里)。
执行的时会在项目的.git/hooks 目录生成所有 hook 的脚本

2、每个hook脚本都是一样的

1
2
3
4
5
#!/bin/sh
# husky
# v1.0.0-rc.1 darwin
export HUSKY_GIT_PARAMS="$*"
node_modules/run-node/run-node ./node_modules/husky/lib/runner/bin `basename "$0"`

关键的部分是 bashname "$0",这样可以拿到当前的 hook名,如pre-commit、pre-push 。

3、最后根据package.json 的配置,执行我们定义相对应的hook脚本。

lint-stage

lint-staged: https://github.com/okonet/lint-staged

用于实现每次提交只检查本次提交所修改的文件。

yorkie

yorkie fork 自 husky 并且与后者不兼容。
Git hooks made easy
This is a fork of husky with a few changes:

vue-cli-serve

在安装之后,@vue/cli-service 也会安装 yorkie ,它会让你在 package.json 的 gitHooks 字段中方便地指定 Git hook:

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
38
39
40
41
42
43
44
{
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"lintcss": "stylelint src/**/*.{html,vue,css,less,scss} --fix",
"preparecommitmsg": "node build/bin/custom-lint -e $GIT_PARAMS",
"postcommit": "node build/bin/post-commit",
"analyz": "ANALYZ=true vue-cli-service build",
"project": "node ./scripts/build/projectInit.js"
},
"gitHooks": {
"pre-commit": "lint-staged",
"commit-msg": "node scripts/git/verify-commit-msg.js"
},
"lint-staged": {
"src/**/*.{js,jsx,vue,ts,tsx}": [
"vue-cli-service lint",
"git add"
],
"src/**/*.{vue,less,scss}": [
"npm run lintcss",
"git add"
]
},
"eslintIgnore": [
"/scripts/git/verify-commit-msg.js",
"/scripts/build/projectInit.js",
"/dist",
"/build",
"/vue.config.js",
"/tsconfig.json"
],
"stylelint": {
"extends": "@xes/stylelint-config-xes",
"rules": {
"value-list-comma-newline-after": "always-multi-line"
}
},
"stylelintIgnore": [
"/src/layout/index.html",
"src/assets/less/utils"
],
}

commitlint

commitlint: https://commitlint.js.org/#/guides-local-setup

1
2
3
# Add hook
npx husky add .husky/pre-commit "npm test"
npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"

1
2
3
4
5
6
7
8
9
10
11
npm install --save-dev husky
npm install --save-dev @commitlint/config-conventional @commitlint/cli

# 生成配置文件commitlint.config.js,当然也可以是 .commitlintrc.js
echo "module.exports = {extends: ['@commitlint/config-conventional']};" > commitlint.config.js

## 提交
git commit -m <type>[optional scope]: <description>

# 测试
echo 'foo: bar' | commitlint
1
2
3
4
5
"husky": {
"hooks": {
"commit-msg": "commitlint -e $HUSKY_GIT_PARAMS"
}
},

————其他————-

husky + lint-staged + commitlint

  1. 安装 husky,lint-staged,@commitlint/cli,@commitlint/config-conventional 依赖
    lint-staged: 用于实现每次提交只检查本次提交所修改的文件。
  2. 创建 .huskyrc

    1
    2
    3
    4
    5
    6
    {
    "hooks": {
    "pre-commit": "lint-staged",
    "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
    }
  3. 创建 .lintstagedrc

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
      {
    "src/**/*.js": "eslint"
    }
    // 设置 fix 可以自动修复错误:
    {
    "src/**/*.js": ["eslint --fix", "git add"]
    },
    // 或者使用下面的配置,自动格式化代码(谨慎使用)
    {
    "src/**/*.js": ["prettier --write", "git add"]
    }
  4. 创建 commitlint.config.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    module.exports = {
    extends: ['@commitlint/config-conventional'],
    rules: {
    'type-enum': [
    2,
    'always',
    [
    'feat', // 新功能(feature)
    'fix', // 修补bug
    'docs', // 文档(documentation)
    'style', // 格式(不影响代码运行的变动)
    'refactor', // 重构(即不是新增功能,也不是修改bug的代码变动)
    'test', // 增加测试
    'revert', // 回滚
    'config', // 构建过程或辅助工具的变动
    'chore', // 其他改动
    ],
    ],
    'type-empty': [2, 'never'], // 提交不符合规范时,也可以提交,但是会有警告
    'subject-empty': [2, 'never'], // 提交不符合规范时,也可以提交,但是会有警告
    'subject-full-stop': [0, 'never'],
    'subject-case': [0, 'never'],
    }
    }

eslint + prettier + husky

.eslintrc.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module.exports = {
root: true,
parserOptions: {
parser: 'babel-eslint'
},
extends: [
'plugin:vue/essential',
'@xes/eslint-config-xes',
'plugin:prettier/recommended'
],
plugins: ['html'],
rules: {
'arrow-parens': 0,
'generator-star-spacing': 0,
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
camelcase: 0
}
}

package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"scripts": {
"lint": "prettier --ignore-path --write './src/**/*.{js,json,css,vue}' && eslint --ext .js,.vue src"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"src/**/*.{js,json,css,vue}": [
"npm run lint",
"git add"
]
}
}

knowledge is no pay,reward is kindness
0%