Skip to content

Commit f76bd8c

Browse files
committed
Merge branch 'js/pre-merge-commit-hook'
A new "pre-merge-commit" hook has been introduced. * js/pre-merge-commit-hook: merge: --no-verify to bypass pre-merge-commit hook git-merge: honor pre-merge-commit hook merge: do no-verify like commit t7503: verify proper hook execution
2 parents a2e524e + bc40ce4 commit f76bd8c

7 files changed

+336
-143
lines changed

Documentation/git-merge.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ SYNOPSIS
1010
--------
1111
[verse]
1212
'git merge' [-n] [--stat] [--no-commit] [--squash] [--[no-]edit]
13-
[-s <strategy>] [-X <strategy-option>] [-S[<keyid>]]
13+
[--no-verify] [-s <strategy>] [-X <strategy-option>] [-S[<keyid>]]
1414
[--[no-]allow-unrelated-histories]
1515
[--[no-]rerere-autoupdate] [-m <msg>] [-F <file>] [<commit>...]
1616
'git merge' (--continue | --abort | --quit)

Documentation/githooks.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,28 @@ The default 'pre-commit' hook, when enabled--and with the
103103
`hooks.allownonascii` config option unset or set to false--prevents
104104
the use of non-ASCII filenames.
105105

106+
pre-merge-commit
107+
~~~~~~~~~~~~~~~~
108+
109+
This hook is invoked by linkgit:git-merge[1], and can be bypassed
110+
with the `--no-verify` option. It takes no parameters, and is
111+
invoked after the merge has been carried out successfully and before
112+
obtaining the proposed commit log message to
113+
make a commit. Exiting with a non-zero status from this script
114+
causes the `git merge` command to abort before creating a commit.
115+
116+
The default 'pre-merge-commit' hook, when enabled, runs the
117+
'pre-commit' hook, if the latter is enabled.
118+
119+
This hook is invoked with the environment variable
120+
`GIT_EDITOR=:` if the command will not bring up an editor
121+
to modify the commit message.
122+
123+
If the merge cannot be carried out automatically, the conflicts
124+
need to be resolved and the result committed separately (see
125+
linkgit:git-merge[1]). At that point, this hook will not be executed,
126+
but the 'pre-commit' hook will, if it is enabled.
127+
106128
prepare-commit-msg
107129
~~~~~~~~~~~~~~~~~~
108130

Documentation/merge-options.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ option can be used to override --squash.
105105
+
106106
With --squash, --commit is not allowed, and will fail.
107107

108+
--no-verify::
109+
This option bypasses the pre-merge and commit-msg hooks.
110+
See also linkgit:githooks[5].
111+
108112
-s <strategy>::
109113
--strategy=<strategy>::
110114
Use the given merge strategy; can be supplied more than

builtin/merge.c

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ static int show_progress = -1;
8181
static int default_to_upstream = 1;
8282
static int signoff;
8383
static const char *sign_commit;
84-
static int verify_msg = 1;
84+
static int no_verify;
8585

8686
static struct strategy all_strategy[] = {
8787
{ "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL },
@@ -287,7 +287,7 @@ static struct option builtin_merge_options[] = {
287287
N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
288288
OPT_BOOL(0, "overwrite-ignore", &overwrite_ignore, N_("update ignored files (default)")),
289289
OPT_BOOL(0, "signoff", &signoff, N_("add Signed-off-by:")),
290-
OPT_BOOL(0, "verify", &verify_msg, N_("verify commit-msg hook")),
290+
OPT_BOOL(0, "no-verify", &no_verify, N_("bypass pre-merge-commit and commit-msg hooks")),
291291
OPT_END()
292292
};
293293

@@ -816,6 +816,18 @@ static void write_merge_heads(struct commit_list *);
816816
static void prepare_to_commit(struct commit_list *remoteheads)
817817
{
818818
struct strbuf msg = STRBUF_INIT;
819+
const char *index_file = get_index_file();
820+
821+
if (!no_verify && run_commit_hook(0 < option_edit, index_file, "pre-merge-commit", NULL))
822+
abort_commit(remoteheads, NULL);
823+
/*
824+
* Re-read the index as pre-merge-commit hook could have updated it,
825+
* and write it out as a tree. We must do this before we invoke
826+
* the editor and after we invoke run_status above.
827+
*/
828+
if (find_hook("pre-merge-commit"))
829+
discard_cache();
830+
read_cache_from(index_file);
819831
strbuf_addbuf(&msg, &merge_msg);
820832
if (squash)
821833
BUG("the control must not reach here under --squash");
@@ -842,7 +854,7 @@ static void prepare_to_commit(struct commit_list *remoteheads)
842854
abort_commit(remoteheads, NULL);
843855
}
844856

845-
if (verify_msg && run_commit_hook(0 < option_edit, get_index_file(),
857+
if (!no_verify && run_commit_hook(0 < option_edit, get_index_file(),
846858
"commit-msg",
847859
git_path_merge_msg(the_repository), NULL))
848860
abort_commit(remoteheads, NULL);
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
#!/bin/sh
2+
3+
test_description='pre-commit and pre-merge-commit hooks'
4+
5+
. ./test-lib.sh
6+
7+
HOOKDIR="$(git rev-parse --git-dir)/hooks"
8+
PRECOMMIT="$HOOKDIR/pre-commit"
9+
PREMERGE="$HOOKDIR/pre-merge-commit"
10+
11+
# Prepare sample scripts that write their $0 to actual_hooks
12+
test_expect_success 'sample script setup' '
13+
mkdir -p "$HOOKDIR" &&
14+
write_script "$HOOKDIR/success.sample" <<-\EOF &&
15+
echo $0 >>actual_hooks
16+
exit 0
17+
EOF
18+
write_script "$HOOKDIR/fail.sample" <<-\EOF &&
19+
echo $0 >>actual_hooks
20+
exit 1
21+
EOF
22+
write_script "$HOOKDIR/non-exec.sample" <<-\EOF &&
23+
echo $0 >>actual_hooks
24+
exit 1
25+
EOF
26+
chmod -x "$HOOKDIR/non-exec.sample" &&
27+
write_script "$HOOKDIR/require-prefix.sample" <<-\EOF &&
28+
echo $0 >>actual_hooks
29+
test $GIT_PREFIX = "success/"
30+
EOF
31+
write_script "$HOOKDIR/check-author.sample" <<-\EOF
32+
echo $0 >>actual_hooks
33+
test "$GIT_AUTHOR_NAME" = "New Author" &&
34+
test "$GIT_AUTHOR_EMAIL" = "[email protected]"
35+
EOF
36+
'
37+
38+
test_expect_success 'root commit' '
39+
echo "root" >file &&
40+
git add file &&
41+
git commit -m "zeroth" &&
42+
git checkout -b side &&
43+
echo "foo" >foo &&
44+
git add foo &&
45+
git commit -m "make it non-ff" &&
46+
git branch side-orig side &&
47+
git checkout master
48+
'
49+
50+
test_expect_success 'setup conflicting branches' '
51+
test_when_finished "git checkout master" &&
52+
git checkout -b conflicting-a master &&
53+
echo a >conflicting &&
54+
git add conflicting &&
55+
git commit -m conflicting-a &&
56+
git checkout -b conflicting-b master &&
57+
echo b >conflicting &&
58+
git add conflicting &&
59+
git commit -m conflicting-b
60+
'
61+
62+
test_expect_success 'with no hook' '
63+
test_when_finished "rm -f actual_hooks" &&
64+
echo "foo" >file &&
65+
git add file &&
66+
git commit -m "first" &&
67+
test_path_is_missing actual_hooks
68+
'
69+
70+
test_expect_success 'with no hook (merge)' '
71+
test_when_finished "rm -f actual_hooks" &&
72+
git branch -f side side-orig &&
73+
git checkout side &&
74+
git merge -m "merge master" master &&
75+
git checkout master &&
76+
test_path_is_missing actual_hooks
77+
'
78+
79+
test_expect_success '--no-verify with no hook' '
80+
test_when_finished "rm -f actual_hooks" &&
81+
echo "bar" >file &&
82+
git add file &&
83+
git commit --no-verify -m "bar" &&
84+
test_path_is_missing actual_hooks
85+
'
86+
87+
test_expect_success '--no-verify with no hook (merge)' '
88+
test_when_finished "rm -f actual_hooks" &&
89+
git branch -f side side-orig &&
90+
git checkout side &&
91+
git merge --no-verify -m "merge master" master &&
92+
git checkout master &&
93+
test_path_is_missing actual_hooks
94+
'
95+
96+
test_expect_success 'with succeeding hook' '
97+
test_when_finished "rm -f \"$PRECOMMIT\" expected_hooks actual_hooks" &&
98+
cp "$HOOKDIR/success.sample" "$PRECOMMIT" &&
99+
echo "$PRECOMMIT" >expected_hooks &&
100+
echo "more" >>file &&
101+
git add file &&
102+
git commit -m "more" &&
103+
test_cmp expected_hooks actual_hooks
104+
'
105+
106+
test_expect_success 'with succeeding hook (merge)' '
107+
test_when_finished "rm -f \"$PREMERGE\" expected_hooks actual_hooks" &&
108+
cp "$HOOKDIR/success.sample" "$PREMERGE" &&
109+
echo "$PREMERGE" >expected_hooks &&
110+
git checkout side &&
111+
git merge -m "merge master" master &&
112+
git checkout master &&
113+
test_cmp expected_hooks actual_hooks
114+
'
115+
116+
test_expect_success 'automatic merge fails; both hooks are available' '
117+
test_when_finished "rm -f \"$PREMERGE\" \"$PRECOMMIT\"" &&
118+
test_when_finished "rm -f expected_hooks actual_hooks" &&
119+
test_when_finished "git checkout master" &&
120+
cp "$HOOKDIR/success.sample" "$PREMERGE" &&
121+
cp "$HOOKDIR/success.sample" "$PRECOMMIT" &&
122+
123+
git checkout conflicting-a &&
124+
test_must_fail git merge -m "merge conflicting-b" conflicting-b &&
125+
test_path_is_missing actual_hooks &&
126+
127+
echo "$PRECOMMIT" >expected_hooks &&
128+
echo a+b >conflicting &&
129+
git add conflicting &&
130+
git commit -m "resolve conflict" &&
131+
test_cmp expected_hooks actual_hooks
132+
'
133+
134+
test_expect_success '--no-verify with succeeding hook' '
135+
test_when_finished "rm -f \"$PRECOMMIT\" actual_hooks" &&
136+
cp "$HOOKDIR/success.sample" "$PRECOMMIT" &&
137+
echo "even more" >>file &&
138+
git add file &&
139+
git commit --no-verify -m "even more" &&
140+
test_path_is_missing actual_hooks
141+
'
142+
143+
test_expect_success '--no-verify with succeeding hook (merge)' '
144+
test_when_finished "rm -f \"$PREMERGE\" actual_hooks" &&
145+
cp "$HOOKDIR/success.sample" "$PREMERGE" &&
146+
git branch -f side side-orig &&
147+
git checkout side &&
148+
git merge --no-verify -m "merge master" master &&
149+
git checkout master &&
150+
test_path_is_missing actual_hooks
151+
'
152+
153+
test_expect_success 'with failing hook' '
154+
test_when_finished "rm -f \"$PRECOMMIT\" expected_hooks actual_hooks" &&
155+
cp "$HOOKDIR/fail.sample" "$PRECOMMIT" &&
156+
echo "$PRECOMMIT" >expected_hooks &&
157+
echo "another" >>file &&
158+
git add file &&
159+
test_must_fail git commit -m "another" &&
160+
test_cmp expected_hooks actual_hooks
161+
'
162+
163+
test_expect_success '--no-verify with failing hook' '
164+
test_when_finished "rm -f \"$PRECOMMIT\" actual_hooks" &&
165+
cp "$HOOKDIR/fail.sample" "$PRECOMMIT" &&
166+
echo "stuff" >>file &&
167+
git add file &&
168+
git commit --no-verify -m "stuff" &&
169+
test_path_is_missing actual_hooks
170+
'
171+
172+
test_expect_success 'with failing hook (merge)' '
173+
test_when_finished "rm -f \"$PREMERGE\" expected_hooks actual_hooks" &&
174+
cp "$HOOKDIR/fail.sample" "$PREMERGE" &&
175+
echo "$PREMERGE" >expected_hooks &&
176+
git checkout side &&
177+
test_must_fail git merge -m "merge master" master &&
178+
git checkout master &&
179+
test_cmp expected_hooks actual_hooks
180+
'
181+
182+
test_expect_success '--no-verify with failing hook (merge)' '
183+
test_when_finished "rm -f \"$PREMERGE\" actual_hooks" &&
184+
cp "$HOOKDIR/fail.sample" "$PREMERGE" &&
185+
git branch -f side side-orig &&
186+
git checkout side &&
187+
git merge --no-verify -m "merge master" master &&
188+
git checkout master &&
189+
test_path_is_missing actual_hooks
190+
'
191+
192+
test_expect_success POSIXPERM 'with non-executable hook' '
193+
test_when_finished "rm -f \"$PRECOMMIT\" actual_hooks" &&
194+
cp "$HOOKDIR/non-exec.sample" "$PRECOMMIT" &&
195+
echo "content" >>file &&
196+
git add file &&
197+
git commit -m "content" &&
198+
test_path_is_missing actual_hooks
199+
'
200+
201+
test_expect_success POSIXPERM '--no-verify with non-executable hook' '
202+
test_when_finished "rm -f \"$PRECOMMIT\" actual_hooks" &&
203+
cp "$HOOKDIR/non-exec.sample" "$PRECOMMIT" &&
204+
echo "more content" >>file &&
205+
git add file &&
206+
git commit --no-verify -m "more content" &&
207+
test_path_is_missing actual_hooks
208+
'
209+
210+
test_expect_success POSIXPERM 'with non-executable hook (merge)' '
211+
test_when_finished "rm -f \"$PREMERGE\" actual_hooks" &&
212+
cp "$HOOKDIR/non-exec.sample" "$PREMERGE" &&
213+
git branch -f side side-orig &&
214+
git checkout side &&
215+
git merge -m "merge master" master &&
216+
git checkout master &&
217+
test_path_is_missing actual_hooks
218+
'
219+
220+
test_expect_success POSIXPERM '--no-verify with non-executable hook (merge)' '
221+
test_when_finished "rm -f \"$PREMERGE\" actual_hooks" &&
222+
cp "$HOOKDIR/non-exec.sample" "$PREMERGE" &&
223+
git branch -f side side-orig &&
224+
git checkout side &&
225+
git merge --no-verify -m "merge master" master &&
226+
git checkout master &&
227+
test_path_is_missing actual_hooks
228+
'
229+
230+
test_expect_success 'with hook requiring GIT_PREFIX' '
231+
test_when_finished "rm -rf \"$PRECOMMIT\" expected_hooks actual_hooks success" &&
232+
cp "$HOOKDIR/require-prefix.sample" "$PRECOMMIT" &&
233+
echo "$PRECOMMIT" >expected_hooks &&
234+
echo "more content" >>file &&
235+
git add file &&
236+
mkdir success &&
237+
(
238+
cd success &&
239+
git commit -m "hook requires GIT_PREFIX = success/"
240+
) &&
241+
test_cmp expected_hooks actual_hooks
242+
'
243+
244+
test_expect_success 'with failing hook requiring GIT_PREFIX' '
245+
test_when_finished "rm -rf \"$PRECOMMIT\" expected_hooks actual_hooks fail" &&
246+
cp "$HOOKDIR/require-prefix.sample" "$PRECOMMIT" &&
247+
echo "$PRECOMMIT" >expected_hooks &&
248+
echo "more content" >>file &&
249+
git add file &&
250+
mkdir fail &&
251+
(
252+
cd fail &&
253+
test_must_fail git commit -m "hook must fail"
254+
) &&
255+
git checkout -- file &&
256+
test_cmp expected_hooks actual_hooks
257+
'
258+
259+
test_expect_success 'check the author in hook' '
260+
test_when_finished "rm -f \"$PRECOMMIT\" expected_hooks actual_hooks" &&
261+
cp "$HOOKDIR/check-author.sample" "$PRECOMMIT" &&
262+
cat >expected_hooks <<-EOF &&
263+
$PRECOMMIT
264+
$PRECOMMIT
265+
$PRECOMMIT
266+
EOF
267+
test_must_fail git commit --allow-empty -m "by a.u.thor" &&
268+
(
269+
GIT_AUTHOR_NAME="New Author" &&
270+
GIT_AUTHOR_EMAIL="[email protected]" &&
271+
export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL &&
272+
git commit --allow-empty -m "by new.author via env" &&
273+
git show -s
274+
) &&
275+
git commit --author="New Author <[email protected]>" \
276+
--allow-empty -m "by new.author via command line" &&
277+
git show -s &&
278+
test_cmp expected_hooks actual_hooks
279+
'
280+
281+
test_done

0 commit comments

Comments
 (0)