阿里云主机折上折
  • 微信号
Current Site:Index > Use Git hooks to translate this sentence into English.

Use Git hooks to translate this sentence into English.

Author:Chuan Chen 阅读数:63617人阅读 分类: 开发工具

Basic Concepts of Git Hooks

Git hooks are scripts that run automatically when specific Git events occur. They are stored in the .git/hooks directory. When certain Git operations are performed, such as committing code or pushing changes, Git checks if corresponding hook scripts exist in this directory and executes them if found. Hooks are divided into two categories: client-side hooks and server-side hooks. Client-side hooks are triggered by local operations, while server-side hooks run on the server in response to network operations.

Each hook script must be an executable file. Git identifies them by their filenames. By default, the .git/hooks directory contains some sample scripts with a .sample extension. To enable them, remove the .sample suffix.

Client-Side Hooks

Client-side hooks affect the local workflow of developers and can be categorized into commit workflow hooks, email workflow hooks, and other client-side hooks.

Pre-Commit Hook

The pre-commit hook runs before the commit message is entered and is used to inspect the upcoming snapshot. For example, it can be used to run code style checks:

#!/bin/sh

# Run ESLint check
npm run lint

# If lint check fails, abort the commit
if [ $? -ne 0 ]; then
  echo "Lint check failed. Please fix errors before committing."
  exit 1
fi

Prepare-Commit-Msg Hook

This hook runs before the commit message editor is launched, allowing us to automatically generate commit message templates or modify the default message. For example:

#!/bin/sh

# Get the current branch name
BRANCH_NAME=$(git symbolic-ref --short HEAD)

# If the branch name contains a JIRA ID, add it to the commit message
if [[ $BRANCH_NAME =~ (PROJ-[0-9]+) ]]; then
  echo "[${BASH_REMATCH[1]}] $(cat $1)" > $1
fi

Commit-Msg Hook

The commit-msg hook takes one parameter, which is the path to a temporary file containing the commit message. It can be used to validate the commit message format:

#!/usr/bin/env node

const fs = require('fs');
const msg = fs.readFileSync(process.argv[2], 'utf8').trim();

const commitRegex = /^(revert: )?(feat|fix|docs|style|refactor|perf|test|chore)\(.+\): .{1,50}/;

if (!commitRegex.test(msg)) {
  console.error(`Invalid commit message format: "${msg}"`);
  console.error('Commit messages should follow: type(scope): description');
  console.error('Example: feat(login): add login validation');
  process.exit(1);
}

Server-Side Hooks

Server-side hooks execute when changes are pushed to the server and can be used to enforce project policies. These hooks run on the server, not on the developer's machine.

Pre-Receive Hook

The pre-receive hook runs first when processing a push from a client and can be used to reject pushes that violate certain policies. For example, checking if the committer's email is a company email:

#!/bin/sh

while read oldrev newrev refname; do
  # Get all commits in the push
  commits=$(git rev-list $oldrev..$newrev)
  
  for commit in $commits; do
    email=$(git show -s --format='%ae' $commit)
    
    if [[ ! "$email" =~ @company\.com$ ]]; then
      echo "Error: Commit ${commit} uses non-company email ${email}"
      exit 1
    fi
  done
done

Update Hook

The update hook is similar to pre-receive but runs once for each branch being updated. It can be used to ensure the main branch can only be updated via merge requests:

#!/bin/sh

refname="$1"
oldrev="$2"
newrev="$3"

# If it's the main branch and not a fast-forward update
if [ "$refname" = "refs/heads/main" ] && [ "$(git merge-base $oldrev $newrev)" != "$oldrev" ]; then
  echo "Error: The main branch can only be updated via merge requests"
  exit 1
fi

Practical Hook Examples

Automatically Run Tests

Run tests automatically before each commit to ensure no failing code is committed:

#!/bin/sh

# Run tests
npm test

if [ $? -ne 0 ]; then
  echo "Tests failed. Please fix them before committing."
  exit 1
fi

Prevent Committing Large Files

Prevent accidentally committing large files to the repository:

#!/usr/bin/env node

const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
const execSync = require('child_process').execSync;

const files = execSync('git diff --cached --name-only --diff-filter=ACM')
  .toString()
  .split('\n')
  .filter(Boolean);

for (const file of files) {
  const size = parseInt(execSync(`git cat-file -s ":${file}"`).toString(), 10);
  if (size > MAX_FILE_SIZE) {
    console.error(`Error: File ${file} is too large (${size} bytes)`);
    console.error('Please use Git LFS or remove the file from the commit');
    process.exit(1);
  }
}

Automatically Increment Version Number

Automatically increment the version number in package.json after each commit to the main branch:

#!/bin/sh

branch=$(git symbolic-ref --short HEAD)

if [ "$branch" = "main" ]; then
  # Increment the patch version
  npm version patch --no-git-tag-version
  
  # Add the version change to the current commit
  git add package.json
  git commit --amend --no-edit
fi

Sharing and Managing Hooks

By default, hooks are stored in the .git/hooks directory, which is not tracked by Git. To share hooks within a team, consider the following methods:

  1. Store hooks in the project: Create a hooks directory in the project and document how to manually copy them to .git/hooks in the README.

  2. Use tools like husky: Husky allows defining Git hooks in package.json:

{
  "husky": {
    "hooks": {
      "pre-commit": "npm run lint",
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  }
}
  1. Use the pre-commit framework: Create a .pre-commit-config.yaml file to define hooks:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
  rev: v3.2.0
  hooks:
    - id: trailing-whitespace
    - id: end-of-file-fixer
    - id: check-yaml

Debugging Hooks

Debugging Git hooks can be challenging because they run during Git command execution. Here are some debugging tips:

  1. Log output: Add logging to hook scripts:
#!/bin/sh

echo "pre-commit hook started" >> /tmp/git-hooks.log
date >> /tmp/git-hooks.log
  1. Run hooks manually: Test hooks by executing them directly:
./.git/hooks/pre-commit
  1. Temporarily disable hooks: Rename hook files while debugging:
mv .git/hooks/pre-commit .git/hooks/pre-commit.disabled
  1. Use set -x: Add set -x to bash scripts to show executed commands:
#!/bin/sh

set -x  # Start debug output
npm run lint
set +x  # End debug output

Performance Considerations

Hook scripts can increase the time taken for Git operations, especially if they run complex checks or tests. To minimize impact on the development workflow:

  1. Only check staged files: Use git diff --cached to get staged files instead of the entire project.

  2. Run tasks in parallel: Use tools to run checks in parallel if possible.

  3. Cache results: Consider caching results for time-consuming operations.

  4. Provide skip options: Allow skipping certain checks in emergencies:

#!/bin/sh

if [ -n "$SKIP_HOOKS" ]; then
  echo "Skipping hook checks"
  exit 0
fi

Usage example:

SKIP_HOOKS=1 git commit -m "Emergency fix"

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.