17

Introduction

Git can trigger custom scripts when certain important actions occur, among which the more commonly used are: pre-commit , commit-msg , pre-push and other hooks. We can pre-commit code format validation trigger, the commit-msg a commit message and submitting user authentication trigger, the pre-push unit testing, testing E2E trigger operation.

When Git executes git init for initialization, it will generate a series of hook scripts .git/hooks

image.png

From the figure above, you can see that the suffix of each script .sample . At this time, the script will not be executed automatically. We need to remove the suffix before it will take effect, that is, pre-commit.sample become pre-commit take effect.

This article mainly wants to introduce how to write git hooks scripts, and will write two pre-commit and commit-msg scripts as examples to help everyone better understand git hooks scripts. Of course, it is recommended to use the ready-made, open source solution husky .

text

There are no restrictions on the scripting language used to write git hooks. You can use nodejs , shell , python , ruby and other scripting languages, which are very flexible and convenient.

Below I will use shell language to demonstrate how to write pre-commit and commit-msg scripts. Also note that when executing these scripts, if you exit the program with a non-zero value, it will interrupt the git submission/push process. So when the verification message/code fails in the hooks script, you can exit with a non-zero value and interrupt the git process.

exit 1

pre-commit

pre-commit to do in the 060f585731eae4 hook is very simple, only checking the format of the code to be submitted, so the script code is relatively small:

#!/bin/sh
npm run lint

# 获取上面脚本的退出码
exitCode="$?"
exit $exitCode

Since I have configured the relevant eslint configuration and npm script in the project, it is pre-commit to execute the relevant lint command in 060f585731eb15, and judge whether it exits normally.

// 在 package.json 文件中已配置好 lint 命令
"scripts": {
    "lint": "eslint --ext .js src/"
 },

Let's look at an animated picture below. When the code format is incorrect, an error will be reported when commit is made:

demo.gif

Submit after modifying the code format, then no error will be reported:

demo2.gif

As can be seen from the moving picture, this commit has been submitted normally.

commit-msg

In commit-msg hooks, we need to verify the commit message and the user.

#!/bin/sh

# 用 `` 可以将命令的输出结果赋值给变量
# 获取当前提交的 commit msg
commit_msg=`cat $1`

# 获取用户 email
email=`git config user.email`
msg_re="^(feat|fix|docs|style|refactor|perf|test|workflow|build|ci|chore|release|workflow)(\(.+\))?: .{1,100}"

if [[ ! $commit_msg =~ $msg_re ]]
then
    echo "\n不合法的 commit 消息提交格式,请使用正确的格式:\
    \nfeat: add comments\
    \nfix: handle events on blur (close #28)\
    \n详情请查看 git commit 提交规范:https://github.com/woai3c/Front-end-articles/blob/master/git%20commit%20style.md"

    # 异常退出
    exit 1
fi

When the commit-msg hook is triggered, the corresponding script will receive a parameter. This parameter is the commit message, which is obtained through cat $1 and assigned to the commit_msg variable.

It is relatively simple to verify the regularity of the commit message, just look at the code. If you are interested in commit submission specifications, you can check out my other article .

It is relatively simple to judge user permissions, just check the user's mailbox or user name (assuming that only employees of the abc company have the permission to submit codes).

email_re="@abc\.com"
if [[ ! $email =~ $email_re ]]
then
    echo "此用户没有权限,具有权限的用户为: xxx@abc.com"

    # 异常退出
    exit 1
fi

The following two animations are used to demonstrate the process of verifying commit messages and judging user permissions:

demo3.gif

demo4.gif

Set the default location of git hooks

The normal execution of the script is only the first step, and there is another problem that must be solved, that is, how to share the git hooks configuration with other developers in the same project. Because the .git/hooks directory will not be pushed to the remote warehouse along with the submission. There are two solutions to this problem: the first is to imitate husky to make an npm plug-in, and automatically .git/hooks directory during installation; the second is to write the hooks script separately in a certain directory in the project, and then When the project installs dependencies, this directory is automatically set to the hooks directory of git.

Next, talk about the implementation process of the second method in detail:

  1. After the npm install completed, the git config core.hooksPath hooks command is automatically executed.
  2. git config core.hooksPath hooks command sets the git hooks directory to the hooks directory under the project root directory.

    "scripts": {
     "lint": "eslint --ext .js src/",
     "postinstall": "git config core.hooksPath hooks"
    },

    Step on the pit

    The demo source code can run normally on windows, but it won’t work after changing to a mac, and an error is reported when submitting:

    hint: The 'hooks/pre-commit' hook was ignored because it's not set as executable.

    The reason is that the hooks script is not executable by default, so you need to make it executable:

    chmod 700 hooks/*

    In order to avoid having to modify every time the project is cloned, it is best to add this command to the npm script:

    "scripts": {
     "lint": "eslint --ext .js src/",
     "postinstall": "git config core.hooksPath hooks && chmod 700 hooks/*"
    },

    Of course, if it is windows, there is no need to add the second half of the code.

nodejs hooks script

In order to help front-end students better understand the git hooks script, I used nodejs to rewrite another version.

pre-commit

#!/usr/bin/env node
const childProcess = require('child_process');

try {
  childProcess.execSync('npm run lint');
} catch (error) {
  console.log(error.stdout.toString());
  process.exit(1);
}

commit-msg

#!/usr/bin/env node
const childProcess = require('child_process');
const fs = require('fs');

const email = childProcess.execSync('git config user.email').toString().trim();
const msg = fs.readFileSync(process.argv[2], 'utf-8').trim(); // 索引 2 对应的 commit 消息文件
const commitRE = /^(feat|fix|docs|style|refactor|perf|test|workflow|build|ci|chore|release|workflow)(\(.+\))?: .{1,100}/;

if (!commitRE.test(msg)) {
  console.log();
  console.error('不合法的 commit 消息格式,请使用正确的提交格式:');
  console.error('feat: add \'comments\' option');
  console.error('fix: handle events on blur (close #28)');
  console.error('详情请查看 git commit 提交规范:https://github.com/woai3c/Front-end-articles/blob/master/git%20commit%20style.md。');
  process.exit(1);
}

if (!/@qq\.com$/.test(email)) {
  console.error('此用户没有权限,具有权限的用户为: xxx@qq.com');
  process.exit(1);
}

to sum up

In fact, the scope of application of this article is not limited to the front-end, but to all projects that use git as version control. For example, Android, ios, Java, etc. It's just that this article chose the front-end project as an example.

Recently attached project source code: https://github.com/woai3c/git-hooks-demo

Reference


谭光志
6.9k 声望13.1k 粉丝