git-commit: add a prepare-commit-msg hook

The prepare-commit-msg hook is run whenever a "fresh" commit message
is prepared, just before it is shown in the editor (if it is).
Its purpose is to modify the commit message in-place.

It takes one to three parameters.  The first is the name of the file that
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 SHA1 as the third parameter (if a -c, -C or --amend option
was given).

If its exit status is non-zero, git-commit will abort.  The hook is
not suppressed by the --no-verify option, so it should not be used
as a replacement for the pre-commit hook.

The sample prepare-commit-msg comments out the `Conflicts:` part of
a merge's commit message; other examples are commented out, including
adding a Signed-off-by line at the bottom of the commit messsage,
that the user can then edit or discard altogether.

Signed-off-by: Paolo Bonzini <bonzini@gnu.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Paolo Bonzini 2008-02-05 08:04:18 +01:00 коммит произвёл Junio C Hamano
Родитель ec84bd000a
Коммит 8089c85bcb
6 изменённых файлов: 242 добавлений и 2 удалений

Просмотреть файл

@ -280,8 +280,8 @@ order).
HOOKS
-----
This command can run `commit-msg`, `pre-commit`, and
`post-commit` hooks. See link:hooks.html[hooks] for more
This command can run `commit-msg`, `prepare-commit-msg`, `pre-commit`,
and `post-commit` hooks. See link:hooks.html[hooks] for more
information.

Просмотреть файл

@ -65,6 +65,31 @@ All the `git-commit` hooks are invoked with the environment
variable `GIT_EDITOR=:` if the command will not bring up an editor
to modify the commit message.
prepare-commit-msg
------------------
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 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 SHA1 (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 comments
out the `Conflicts:` part of a merge's commit message.
commit-msg
----------

Просмотреть файл

@ -394,6 +394,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix)
struct strbuf sb;
char *buffer;
FILE *fp;
const char *hook_arg1 = NULL;
const char *hook_arg2 = NULL;
if (!no_verify && run_hook(index_file, "pre-commit", NULL))
return 0;
@ -401,32 +403,47 @@ static int prepare_to_commit(const char *index_file, const char *prefix)
strbuf_init(&sb, 0);
if (message.len) {
strbuf_addbuf(&sb, &message);
hook_arg1 = "message";
} else if (logfile && !strcmp(logfile, "-")) {
if (isatty(0))
fprintf(stderr, "(reading log message from standard input)\n");
if (strbuf_read(&sb, 0, 0) < 0)
die("could not read log from standard input");
hook_arg1 = "message";
} else if (logfile) {
if (strbuf_read_file(&sb, logfile, 0) < 0)
die("could not read log file '%s': %s",
logfile, strerror(errno));
hook_arg1 = "message";
} else if (use_message) {
buffer = strstr(use_message_buffer, "\n\n");
if (!buffer || buffer[2] == '\0')
die("commit has empty message");
strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
hook_arg1 = "commit";
hook_arg2 = use_message;
} else if (!stat(git_path("MERGE_MSG"), &statbuf)) {
if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0)
die("could not read MERGE_MSG: %s", strerror(errno));
hook_arg1 = "merge";
} else if (!stat(git_path("SQUASH_MSG"), &statbuf)) {
if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0)
die("could not read SQUASH_MSG: %s", strerror(errno));
hook_arg1 = "squash";
} else if (template_file && !stat(template_file, &statbuf)) {
if (strbuf_read_file(&sb, template_file, 0) < 0)
die("could not read %s: %s",
template_file, strerror(errno));
hook_arg1 = "template";
}
/*
* This final case does not modify the template message,
* it just sets the argument to the prepare-commit-msg hook.
*/
else if (in_merge)
hook_arg1 = "merge";
fp = fopen(git_path(commit_editmsg), "w");
if (fp == NULL)
die("could not open %s", git_path(commit_editmsg));
@ -534,6 +551,10 @@ static int prepare_to_commit(const char *index_file, const char *prefix)
return 0;
}
if (run_hook(index_file, "prepare-commit-msg",
git_path(commit_editmsg), hook_arg1, hook_arg2, NULL))
return 0;
if (use_editor) {
char index[PATH_MAX];
const char *env[2] = { index, NULL };

Просмотреть файл

@ -0,0 +1,155 @@
#!/bin/sh
test_description='prepare-commit-msg hook'
. ./test-lib.sh
test_expect_success 'with no hook' '
echo "foo" > file &&
git add file &&
git commit -m "first"
'
# set up fake editor for interactive editing
cat > fake-editor <<'EOF'
#!/bin/sh
exit 0
EOF
chmod +x fake-editor
FAKE_EDITOR="$(pwd)/fake-editor"
export FAKE_EDITOR
# now install hook that always succeeds and adds a message
HOOKDIR="$(git rev-parse --git-dir)/hooks"
HOOK="$HOOKDIR/prepare-commit-msg"
mkdir -p "$HOOKDIR"
cat > "$HOOK" <<'EOF'
#!/bin/sh
if test "$2" = commit; then
source=$(git-rev-parse "$3")
else
source=${2-default}
fi
if test "$GIT_EDITOR" = :; then
sed -e "1s/.*/$source (no editor)/" "$1" > msg.tmp
else
sed -e "1s/.*/$source/" "$1" > msg.tmp
fi
mv msg.tmp "$1"
exit 0
EOF
chmod +x "$HOOK"
echo dummy template > "$(git rev-parse --git-dir)/template"
test_expect_success 'with hook (-m)' '
echo "more" >> file &&
git add file &&
git commit -m "more" &&
test "`git log -1 --pretty=format:%s`" = "message (no editor)"
'
test_expect_success 'with hook (-m editor)' '
echo "more" >> file &&
git add file &&
GIT_EDITOR="$FAKE_EDITOR" git commit -e -m "more more" &&
test "`git log -1 --pretty=format:%s`" = message
'
test_expect_success 'with hook (-t)' '
echo "more" >> file &&
git add file &&
git commit -t "$(git rev-parse --git-dir)/template" &&
test "`git log -1 --pretty=format:%s`" = template
'
test_expect_success 'with hook (-F)' '
echo "more" >> file &&
git add file &&
(echo more | git commit -F -) &&
test "`git log -1 --pretty=format:%s`" = "message (no editor)"
'
test_expect_success 'with hook (-F editor)' '
echo "more" >> file &&
git add file &&
(echo more more | GIT_EDITOR="$FAKE_EDITOR" git commit -e -F -) &&
test "`git log -1 --pretty=format:%s`" = message
'
test_expect_success 'with hook (-C)' '
head=`git rev-parse HEAD` &&
echo "more" >> file &&
git add file &&
git commit -C $head &&
test "`git log -1 --pretty=format:%s`" = "$head (no editor)"
'
test_expect_success 'with hook (editor)' '
echo "more more" >> file &&
git add file &&
GIT_EDITOR="$FAKE_EDITOR" git commit &&
test "`git log -1 --pretty=format:%s`" = default
'
test_expect_success 'with hook (--amend)' '
head=`git rev-parse HEAD` &&
echo "more" >> file &&
git add file &&
GIT_EDITOR="$FAKE_EDITOR" git commit --amend &&
test "`git log -1 --pretty=format:%s`" = "$head"
'
test_expect_success 'with hook (-c)' '
head=`git rev-parse HEAD` &&
echo "more" >> file &&
git add file &&
GIT_EDITOR="$FAKE_EDITOR" git commit -c $head &&
test "`git log -1 --pretty=format:%s`" = "$head"
'
cat > "$HOOK" <<'EOF'
#!/bin/sh
exit 1
EOF
test_expect_success 'with failing hook' '
head=`git rev-parse HEAD` &&
echo "more" >> file &&
git add file &&
! GIT_EDITOR="$FAKE_EDITOR" git commit -c $head
'
test_expect_success 'with failing hook (--no-verify)' '
head=`git rev-parse HEAD` &&
echo "more" >> file &&
git add file &&
! GIT_EDITOR="$FAKE_EDITOR" git commit --no-verify -c $head
'
test_done

Просмотреть файл

@ -9,6 +9,9 @@
# To enable this hook, make this file executable.
# 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"

Просмотреть файл

@ -0,0 +1,36 @@
#!/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, make this file executable.
# 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)
sed -i '/^Conflicts:/,/#/!b;s/^/# &/;s/^# #/#/' "$1" ;;
# ""|template)
# perl -i -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"