User Tools

Site Tools


git_hooks

Overview

Overview of hooks, concentrating on client-side hooks related to running git commit.

The two types of hooks

Hooks come in two flavours:

  • client-side
  • server-side

Note that hooks can get their arguments from any combination of:

  • the environment
  • command-line arguments
  • stdin

Default hook installation for a new repository

Unless overridden (explained later), a new repository created via git init will have its new .git/hooks/ directory populated verbatim from the contents of /usr/share/git-core/templates/hooks/:

  • applypatch-msg.sample*
  • commit-msg.sample*
  • fsmonitor-watchman.sample*
  • post-update.sample*
  • pre-applypatch.sample*
  • pre-commit.sample*
  • prepare-commit-msg.sample*
  • pre-push.sample*
  • pre-rebase.sample*
  • pre-receive.sample*
  • update.sample*

These are sample scripts supplied by Git that you can use as is, or customize to taste. In order to activate such scripts once they're copied to the new repository:

  • Make sure they are marked as executable.
  • Remove the .sample suffix (exact spelling is important).

Where hooks are effectively run

From man githooks:

Before Git invokes a hook, it changes its working directory to
either $GIT_DIR in a bare repository or the root of the working
tree in a non-bare repository. An exception are hooks triggered
during a push (pre-receive, update, post-receive, post-update,
push-to-checkout) which are always executed in $GIT_DIR.

pre-commit

From man githooks:

This hook is invoked by git commit(1), and can be bypassed with
the --no-verify option. It takes no parameters, and is invoked
before obtaining the proposed commit log message and making a
commit. Exiting with a non-zero status from this script causes
the git commit command to abort before creating a commit.

Really, it's just the last line that matters, which fails on whitespace errors:

#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git commit" with no arguments.  The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-commit".

if git rev-parse --verify HEAD >/dev/null 2>&1
then
        against=HEAD
else
        # Initial commit: diff against an empty tree object
        against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi

# If you want to allow non-ASCII filenames set this variable to true.
allownonascii=$(git config --bool hooks.allownonascii)

# Redirect output to stderr.
exec 1>&2

# Cross platform projects tend to avoid non-ASCII filenames; prevent
# them from being added to the repository. We exploit the fact that the
# printable range starts at the space character and ends with tilde.
if [ "$allownonascii" != "true" ] &&
        # Note that the use of brackets around a tr range is ok here, (it's
        # even required, for portability to Solaris 10's /usr/bin/tr), since
        # the square bracket bytes happen to fall in the designated range.
        test $(git diff --cached --name-only --diff-filter=A -z $against |
          LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
then
        cat <<\EOF
Error: Attempt to add a non-ASCII file name.

This can cause problems if you want to work with people on other platforms.

To be portable it is advisable to rename the file.

If you know what you are doing you can disable this check using:

  git config hooks.allownonascii true
EOF
        exit 1
fi

# If there are whitespace errors, print the offending file names and fail.

prepare-commit-msg

From man githooks:

This hook is invoked by git commit right after preparing the
default log message, and before the editor is started.

It takes one to three parameters. The first is the name of the
file that contains the commit log message. The second is the
source of the commit message, and can be: message (if a -m or -F
option was given); template (if a -t option was given or the
configuration option commit.template is set); merge (if the
commit is a merge or a .git/MERGE_MSG file exists); squash (if a
.git/SQUASH_MSG file exists); or commit, followed by a commit
SHA-1 (if a -c, -C or --amend option was given).

If the exit status is non-zero, git commit will abort.

The purpose of the hook is to edit the message file in place,
and it is not suppressed by the --no-verify option. A non-zero
exit means a failure of the hook and aborts the commit. It
should not be used as replacement for pre-commit hook.

The sample prepare-commit-msg hook that comes with Git removes
the help message found in the commented portion of the commit
template.

The sample hook:

#!/bin/sh
#
# An example hook script to prepare the commit log message.
# Called by "git commit" with the name of the file that has the
# commit message, followed by the description of the commit
# message's source.  The hook's purpose is to edit the commit
# message file.  If the hook fails with a non-zero status,
# the commit is aborted.
#
# To enable this hook, rename this file to "prepare-commit-msg".

# This hook includes three examples.  The first comments out the
# "Conflicts:" part of a merge commit.
#
# The second includes the output of "git diff --name-status -r"
# into the message, just before the "git status" output.  It is
# commented because it doesn't cope with --amend or with squashed
# commits.
#
# The third example adds a Signed-off-by line to the message, that can
# still be edited.  This is rarely a good idea.

case "$2,$3" in
  merge,)
    /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;;

# ,|template,)
#   /usr/bin/perl -i.bak -pe '
#      print "\n" . `git diff --cached --name-status -r`
#        if /^#/ && $first++ == 0' "$1" ;;

  *) ;;
esac

# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"

commit-msg

From man githooks:

This hook is invoked by git commit and git merge, and can be
bypassed with the --no-verify option. It takes a single
parameter, the name of the file that holds the proposed commit
log message. Exiting with a non-zero status causes the command
to abort.

The hook is allowed to edit the message file in place, and can
be used to normalize the message into some project standard
format. It can also be used to refuse the commit after
inspecting the message file.

The default commit-msg hook, when enabled, detects duplicate
"Signed-off-by" lines, and aborts the commit if one is found.

The hook:

#!/bin/sh
#
# An example hook script to check the commit log message.
# Called by "git commit" with one argument, the name of the file
# that has the commit message.  The hook should exit with non-zero
# status after issuing an appropriate message if it wants to stop the
# commit.  The hook is allowed to edit the commit message file.
#
# To enable this hook, rename this file to "commit-msg".

# Uncomment the below to add a Signed-off-by line to the message.
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
# hook is more suited to it.
#
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"

# This example catches duplicate Signed-off-by lines.

test "" = "$(grep '^Signed-off-by: ' "$1" |
         sort | uniq -c | sed -e '/^[   ]*1[    ]/d')" || {
        echo >&2 Duplicate Signed-off-by lines.
        exit 1
}

post-commit

From man githooks:

This hook is invoked by git commit. It takes no parameters, and
is invoked after a commit is made.

This hook is meant primarily for notification, and cannot affect
the outcome of git commit.

There is no sample script provided by Git; you're on your own here.

Overriding the default hooks

git init

When initializing a new repository, the template content used for the new repository comes from one of (in order of precedence):

  • the --template=<template directory> option
  • the value of the $GIT_TEMPLATE_DIR environment variable
  • the init.templateDir configuration option
  • the contents of /usr/share/git-core/templates/

git clone

When cloning a repository, the only way to override the installation of default hooks (and template content) is via the --template=<template directory> command-line option.

During normal operation

From man git-config:

core.hooksPath
    By default Git will look for your hooks in the
    $GIT_DIR/hooks directory. Set this to different path, e.g.
    /etc/git/hooks, and Git will try to find your hooks in that
    directory, e.g.  /etc/git/hooks/pre-receive instead of in
    $GIT_DIR/hooks/pre-receive.

    The path can be either absolute or relative. A relative
    path is taken as relative to the directory where the hooks
    are run (see the "DESCRIPTION" section of githooks(5)).

    This configuration variable is useful in cases where you’d
    like to centrally configure your Git hooks instead of
    configuring them on a per-repository basis, or as a more
    flexible and centralized alternative to having an
    init.templateDir where you’ve changed default hooks.
git_hooks.txt · Last modified: 2019/02/19 11:10 by rpjday