User Tools

Site Tools


git_hooks

This is an old revision of the document!


A PCRE internal error occured. This might be caused by a faulty plugin

===== 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 ===== 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*'' ===== 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. ===== Supporting code ===== ==== run_commit_hook() ==== <code> 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; } </code> ==== builtin/commit.c ==== <code> static int prepare_to_commit(const char *index_file, const char *prefix, struct commit *current_head, struct wt_status *s, struct strbuf *author_ident) { struct stat statbuf; struct strbuf committer_ident = STRBUF_INIT; int commitable; struct strbuf sb = STRBUF_INIT; const char *hook_arg1 = NULL; const char *hook_arg2 = NULL; int clean_message_contents = (cleanup_mode != COMMIT_MSG_CLEANUP_NONE); int old_display_comment_prefix; /* This checks and barfs if author is badly specified */ determine_author_info(author_ident); if (!no_verify && run_commit_hook(use_editor, index_file, "pre-commit", NULL)) return 0; ... snip ... if (run_commit_hook(use_editor, index_file, "prepare-commit-msg", git_path_commit_editmsg(), hook_arg1, hook_arg2, NULL)) return 0; if (use_editor) { struct argv_array env = ARGV_ARRAY_INIT; argv_array_pushf(&env, "GIT_INDEX_FILE=%s", index_file); if (launch_editor(git_path_commit_editmsg(), NULL, env.argv)) { fprintf(stderr, _("Please supply the message using either -m or -F option.\n")); exit(1); } argv_array_clear(&env); } if (!no_verify && run_commit_hook(use_editor, index_file, "commit-msg", git_path_commit_editmsg(), NULL)) { return 0; } return 1; } </code> <code> int cmd_commit(int argc, const char **argv, const char *prefix) { const char *argv_gc_auto[] = {"gc", "--auto", NULL}; static struct wt_status s; static struct option builtin_commit_options[] = { ... snip ... run_commit_hook(use_editor, get_index_file(), "post-commit", NULL); if (amend && !no_post_rewrite) { commit_post_rewrite(current_head, &oid); } if (!quiet) { unsigned int flags = 0; if (!current_head) flags |= SUMMARY_INITIAL_COMMIT; if (author_date_is_interesting()) flags |= SUMMARY_SHOW_AUTHOR_DATE; print_commit_summary(prefix, &oid, flags); } UNLEAK(err); UNLEAK(sb); return 0; } </code> ===== The client-side hooks ===== ==== pre-commit ==== In a nutshell (from ''man githooks''): <code> 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. </code> Really, it's just the last line that matters, which fails on whitespace errors: <code> #!/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. </code> Here's the money command: <code> exec git diff-index --check --cached $against -- </code> ==== prepare-commit-msg ==== From ''man githooks'': <code> 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. </code> The sample hook: <code> #!/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" </code> ==== commit-msg ==== From ''man githooks'': <code> 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. </code> The hook: <code> #!/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 } </code> ==== post-commit ==== From ''man githooks'': <code> 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. </code>

git_hooks.1550568007.txt.gz · Last modified: 2019/02/19 09:20 by rpjday