@matheusbsbs / matheus-bagliano / commits / 8089c85bcb
Fork 0

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>

Paolo Bonzini committed Feb 5, 2008 at 08:04 UTC 8089c85bcba89464b9b2a32fa948ed85eb367e70
6 files changed +242 -2
Documentation/git-commit.txt
+2 -2
@@ -280,8 +280,8 @@ order).
280
281 HOOKS
282 -----
283 -This command can run `commit-msg`, `pre-commit`, and
284 -`post-commit` hooks. See link:hooks.html[hooks] for more
283 +This command can run `commit-msg`, `prepare-commit-msg`, `pre-commit`,
284 +and `post-commit` hooks. See link:hooks.html[hooks] for more
285 information.
286
287
Documentation/hooks.txt
+25
@@ -65,6 +65,31 @@ All the `git-commit` hooks are invoked with the environment
65 variable `GIT_EDITOR=:` if the command will not bring up an editor
66 to modify the commit message.
67
68 +prepare-commit-msg
69 +------------------
70 +
71 +This hook is invoked by `git-commit` right after preparing the
72 +default log message, and before the editor is started.
73 +
74 +It takes one to three parameters. The first is the name of the file
75 +that the commit log message. The second is the source of the commit
76 +message, and can be: `message` (if a `\-m` or `\-F` option was
77 +given); `template` (if a `\-t` option was given or the
78 +configuration option `commit.template` is set); `merge` (if the
79 +commit is a merge or a `.git/MERGE_MSG` file exists); `squash`
80 +(if a `.git/SQUASH_MSG` file exists); or `commit`, followed by
81 +a commit SHA1 (if a `\-c`, `\-C` or `\--amend` option was given).
82 +
83 +If the exit status is non-zero, `git-commit` will abort.
84 +
85 +The purpose of the hook is to edit the message file in place, and
86 +it is not suppressed by the `\--no-verify` option. A non-zero exit
87 +means a failure of the hook and aborts the commit. It should not
88 +be used as replacement for pre-commit hook.
89 +
90 +The sample `prepare-commit-msg` hook that comes with git comments
91 +out the `Conflicts:` part of a merge's commit message.
92 +
93 commit-msg
94 ----------
95
builtin-commit.c
+21
@@ -394,6 +394,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix)
394 struct strbuf sb;
395 char *buffer;
396 FILE *fp;
397 + const char *hook_arg1 = NULL;
398 + const char *hook_arg2 = NULL;
399
400 if (!no_verify && run_hook(index_file, "pre-commit", NULL))
401 return 0;
@@ -401,32 +403,47 @@ static int prepare_to_commit(const char *index_file, const char *prefix)
403 strbuf_init(&sb, 0);
404 if (message.len) {
405 strbuf_addbuf(&sb, &message);
406 + hook_arg1 = "message";
407 } else if (logfile && !strcmp(logfile, "-")) {
408 if (isatty(0))
409 fprintf(stderr, "(reading log message from standard input)\n");
410 if (strbuf_read(&sb, 0, 0) < 0)
411 die("could not read log from standard input");
412 + hook_arg1 = "message";
413 } else if (logfile) {
414 if (strbuf_read_file(&sb, logfile, 0) < 0)
415 die("could not read log file '%s': %s",
416 logfile, strerror(errno));
417 + hook_arg1 = "message";
418 } else if (use_message) {
419 buffer = strstr(use_message_buffer, "\n\n");
420 if (!buffer || buffer[2] == '\0')
421 die("commit has empty message");
422 strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
423 + hook_arg1 = "commit";
424 + hook_arg2 = use_message;
425 } else if (!stat(git_path("MERGE_MSG"), &statbuf)) {
426 if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0)
427 die("could not read MERGE_MSG: %s", strerror(errno));
428 + hook_arg1 = "merge";
429 } else if (!stat(git_path("SQUASH_MSG"), &statbuf)) {
430 if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0)
431 die("could not read SQUASH_MSG: %s", strerror(errno));
432 + hook_arg1 = "squash";
433 } else if (template_file && !stat(template_file, &statbuf)) {
434 if (strbuf_read_file(&sb, template_file, 0) < 0)
435 die("could not read %s: %s",
436 template_file, strerror(errno));
437 + hook_arg1 = "template";
438 }
439
440 + /*
441 + * This final case does not modify the template message,
442 + * it just sets the argument to the prepare-commit-msg hook.
443 + */
444 + else if (in_merge)
445 + hook_arg1 = "merge";
446 +
447 fp = fopen(git_path(commit_editmsg), "w");
448 if (fp == NULL)
449 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)
551 return 0;
552 }
553
554 + if (run_hook(index_file, "prepare-commit-msg",
555 + git_path(commit_editmsg), hook_arg1, hook_arg2, NULL))
556 + return 0;
557 +
558 if (use_editor) {
559 char index[PATH_MAX];
560 const char *env[2] = { index, NULL };
t/t7505-prepare-commit-msg-hook.sh new
+155
@@ -0,0 +1,155 @@
1 +#!/bin/sh
2 +
3 +test_description='prepare-commit-msg hook'
4 +
5 +. ./test-lib.sh
6 +
7 +test_expect_success 'with no hook' '
8 +
9 + echo "foo" > file &&
10 + git add file &&
11 + git commit -m "first"
12 +
13 +'
14 +
15 +# set up fake editor for interactive editing
16 +cat > fake-editor <<'EOF'
17 +#!/bin/sh
18 +exit 0
19 +EOF
20 +chmod +x fake-editor
21 +FAKE_EDITOR="$(pwd)/fake-editor"
22 +export FAKE_EDITOR
23 +
24 +# now install hook that always succeeds and adds a message
25 +HOOKDIR="$(git rev-parse --git-dir)/hooks"
26 +HOOK="$HOOKDIR/prepare-commit-msg"
27 +mkdir -p "$HOOKDIR"
28 +cat > "$HOOK" <<'EOF'
29 +#!/bin/sh
30 +if test "$2" = commit; then
31 + source=$(git-rev-parse "$3")
32 +else
33 + source=${2-default}
34 +fi
35 +if test "$GIT_EDITOR" = :; then
36 + sed -e "1s/.*/$source (no editor)/" "$1" > msg.tmp
37 +else
38 + sed -e "1s/.*/$source/" "$1" > msg.tmp
39 +fi
40 +mv msg.tmp "$1"
41 +exit 0
42 +EOF
43 +chmod +x "$HOOK"
44 +
45 +echo dummy template > "$(git rev-parse --git-dir)/template"
46 +
47 +test_expect_success 'with hook (-m)' '
48 +
49 + echo "more" >> file &&
50 + git add file &&
51 + git commit -m "more" &&
52 + test "`git log -1 --pretty=format:%s`" = "message (no editor)"
53 +
54 +'
55 +
56 +test_expect_success 'with hook (-m editor)' '
57 +
58 + echo "more" >> file &&
59 + git add file &&
60 + GIT_EDITOR="$FAKE_EDITOR" git commit -e -m "more more" &&
61 + test "`git log -1 --pretty=format:%s`" = message
62 +
63 +'
64 +
65 +test_expect_success 'with hook (-t)' '
66 +
67 + echo "more" >> file &&
68 + git add file &&
69 + git commit -t "$(git rev-parse --git-dir)/template" &&
70 + test "`git log -1 --pretty=format:%s`" = template
71 +
72 +'
73 +
74 +test_expect_success 'with hook (-F)' '
75 +
76 + echo "more" >> file &&
77 + git add file &&
78 + (echo more | git commit -F -) &&
79 + test "`git log -1 --pretty=format:%s`" = "message (no editor)"
80 +
81 +'
82 +
83 +test_expect_success 'with hook (-F editor)' '
84 +
85 + echo "more" >> file &&
86 + git add file &&
87 + (echo more more | GIT_EDITOR="$FAKE_EDITOR" git commit -e -F -) &&
88 + test "`git log -1 --pretty=format:%s`" = message
89 +
90 +'
91 +
92 +test_expect_success 'with hook (-C)' '
93 +
94 + head=`git rev-parse HEAD` &&
95 + echo "more" >> file &&
96 + git add file &&
97 + git commit -C $head &&
98 + test "`git log -1 --pretty=format:%s`" = "$head (no editor)"
99 +
100 +'
101 +
102 +test_expect_success 'with hook (editor)' '
103 +
104 + echo "more more" >> file &&
105 + git add file &&
106 + GIT_EDITOR="$FAKE_EDITOR" git commit &&
107 + test "`git log -1 --pretty=format:%s`" = default
108 +
109 +'
110 +
111 +test_expect_success 'with hook (--amend)' '
112 +
113 + head=`git rev-parse HEAD` &&
114 + echo "more" >> file &&
115 + git add file &&
116 + GIT_EDITOR="$FAKE_EDITOR" git commit --amend &&
117 + test "`git log -1 --pretty=format:%s`" = "$head"
118 +
119 +'
120 +
121 +test_expect_success 'with hook (-c)' '
122 +
123 + head=`git rev-parse HEAD` &&
124 + echo "more" >> file &&
125 + git add file &&
126 + GIT_EDITOR="$FAKE_EDITOR" git commit -c $head &&
127 + test "`git log -1 --pretty=format:%s`" = "$head"
128 +
129 +'
130 +
131 +cat > "$HOOK" <<'EOF'
132 +#!/bin/sh
133 +exit 1
134 +EOF
135 +
136 +test_expect_success 'with failing hook' '
137 +
138 + head=`git rev-parse HEAD` &&
139 + echo "more" >> file &&
140 + git add file &&
141 + ! GIT_EDITOR="$FAKE_EDITOR" git commit -c $head
142 +
143 +'
144 +
145 +test_expect_success 'with failing hook (--no-verify)' '
146 +
147 + head=`git rev-parse HEAD` &&
148 + echo "more" >> file &&
149 + git add file &&
150 + ! GIT_EDITOR="$FAKE_EDITOR" git commit --no-verify -c $head
151 +
152 +'
153 +
154 +
155 +test_done
templates/hooks--commit-msg
+3
@@ -9,6 +9,9 @@
9 # To enable this hook, make this file executable.
10
11 # Uncomment the below to add a Signed-off-by line to the message.
12 +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
13 +# hook is more suited to it.
14 +#
15 # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
16 # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
17
templates/hooks--prepare-commit-msg new
+36
@@ -0,0 +1,36 @@
1 +#!/bin/sh
2 +#
3 +# An example hook script to prepare the commit log message.
4 +# Called by git-commit with the name of the file that has the
5 +# commit message, followed by the description of the commit
6 +# message's source. The hook's purpose is to edit the commit
7 +# message file. If the hook fails with a non-zero status,
8 +# the commit is aborted.
9 +#
10 +# To enable this hook, make this file executable.
11 +
12 +# This hook includes three examples. The first comments out the
13 +# "Conflicts:" part of a merge commit.
14 +#
15 +# The second includes the output of "git diff --name-status -r"
16 +# into the message, just before the "git status" output. It is
17 +# commented because it doesn't cope with --amend or with squashed
18 +# commits.
19 +#
20 +# The third example adds a Signed-off-by line to the message, that can
21 +# still be edited. This is rarely a good idea.
22 +
23 +case "$2 $3" in
24 + merge)
25 + sed -i '/^Conflicts:/,/#/!b;s/^/# &/;s/^# #/#/' "$1" ;;
26 +
27 +# ""|template)
28 +# perl -i -pe '
29 +# print "\n" . `git diff --cached --name-status -r`
30 +# if /^#/ && $first++ == 0' "$1" ;;
31 +
32 + *) ;;
33 +esac
34 +
35 +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
36 +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"