User Tools

Site Tools


git_hooks

This is an old revision of the document!


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 prefix (exact spelling is important).

Properties of hooks

  • They must be marked executable in order to be active.
  • They normally live in $GIT_DIR/hooks, unless overridden via core.hooksPath.
  • Running git init on an existing directory will copy in only new hooks; it will never overwrite existing hooks.
  • Hooks can get their arguments via the environment, command-line arguments, and stdin.

pre-commit

prepare-commit-msg

commit-msg

post-commit

Supporting code

run_commit_hook()

int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...)
{
        struct argv_array hook_env = ARGV_ARRAY_INIT;
        va_list args;
        int ret;

        argv_array_pushf(&hook_env, "GIT_INDEX_FILE=%s", index_file);

        /*
         * Let the hook know that no editor will be launched.
         */
        if (!editor_is_used)
                argv_array_push(&hook_env, "GIT_EDITOR=:");

        va_start(args, name);
        ret = run_hook_ve(hook_env.argv,name, args);
        va_end(args);
        argv_array_clear(&hook_env);

        return ret;
}

The client-side hooks

pre-commit

In a nutshell (from man githooks):

This hook is invoked by git commit, 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.

Here's the money command:

exec git diff-index --check --cached $against --

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.
git_hooks.1550569114.txt.gz · Last modified: 2019/02/19 09:38 by rpjday