@@ -428,7 +428,7 @@ static int run_applypatch_msg_hook(struct am_state *state)
int ret;
assert(state->msg);
- ret = run_hook_le(NULL, "applypatch-msg", am_path(state, "final-commit"), NULL);
+ ret = run_hook_le(NULL, 0, "applypatch-msg", am_path(state, "final-commit"), NULL);
if (!ret) {
FREE_AND_NULL(state->msg);
@@ -1559,7 +1559,7 @@ static void do_commit(const struct am_state *state)
const char *reflog_msg, *author, *committer = NULL;
struct strbuf sb = STRBUF_INIT;
- if (run_hook_le(NULL, "pre-applypatch", NULL))
+ if (run_hook_le(NULL, 0, "pre-applypatch", NULL))
exit(1);
if (write_cache_as_tree(&tree, 0, NULL))
@@ -1611,7 +1611,7 @@ static void do_commit(const struct am_state *state)
fclose(fp);
}
- run_hook_le(NULL, "post-applypatch", NULL);
+ run_hook_le(NULL, 0, "post-applypatch", NULL);
strbuf_release(&sb);
}
@@ -104,7 +104,7 @@ struct branch_info {
static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,
int changed)
{
- return run_hook_le(NULL, "post-checkout",
+ return run_hook_le(NULL, 0, "post-checkout",
oid_to_hex(old_commit ? &old_commit->object.oid : &null_oid),
oid_to_hex(new_commit ? &new_commit->object.oid : &null_oid),
changed ? "1" : "0", NULL);
@@ -816,7 +816,7 @@ static int checkout(int submodule_progress)
if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
die(_("unable to write new index file"));
- err |= run_hook_le(NULL, "post-checkout", oid_to_hex(&null_oid),
+ err |= run_hook_le(NULL, 0, "post-checkout", oid_to_hex(&null_oid),
oid_to_hex(&oid), "1", NULL);
if (!err && (option_recurse_submodules.nr > 0)) {
@@ -385,7 +385,7 @@ static int need_to_gc(void)
else
return 0;
- if (run_hook_le(NULL, "pre-auto-gc", NULL))
+ if (run_hook_le(NULL, 0, "pre-auto-gc", NULL))
return 0;
return 1;
}
@@ -474,7 +474,7 @@ static void finish(struct commit *head_commit,
}
/* Run a post-merge hook */
- run_hook_le(NULL, "post-merge", squash ? "1" : "0", NULL);
+ run_hook_le(NULL, 0, "post-merge", squash ? "1" : "0", NULL);
apply_autostash(git_path_merge_autostash(the_repository));
strbuf_release(&reflog_message);
@@ -2014,7 +2014,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
/* If a hook exists, give it a chance to interrupt*/
if (!ok_to_skip_pre_rebase &&
- run_hook_le(NULL, "pre-rebase", options.upstream_arg,
+ run_hook_le(NULL, 0, "pre-rebase", options.upstream_arg,
argc ? argv[0] : NULL, NULL))
die(_("The pre-rebase hook refused to rebase."));
@@ -1384,7 +1384,7 @@ static const char *push_to_checkout(unsigned char *hash,
const char *work_tree)
{
strvec_pushf(env, "GIT_WORK_TREE=%s", absolute_path(work_tree));
- if (run_hook_le(env->v, push_to_checkout_hook,
+ if (run_hook_le(env->v, 0, push_to_checkout_hook,
hash_to_hex(hash), NULL))
return "push-to-checkout hook declined";
else
@@ -1646,7 +1646,7 @@ int run_commit_hook(int editor_is_used, const char *index_file,
strvec_push(&hook_env, "GIT_EDITOR=:");
va_start(args, name);
- ret = run_hook_ve(hook_env.v, name, args);
+ ret = run_hook_ve(hook_env.v, RUN_HOOK_ALLOW_STDIN, name, args);
va_end(args);
strvec_clear(&hook_env);
@@ -3070,7 +3070,7 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l
else
ret = close_lock_file_gently(lock);
- run_hook_le(NULL, "post-index-change",
+ run_hook_le(NULL, 0, "post-index-change",
istate->updated_workdir ? "1" : "0",
istate->updated_skipworktree ? "1" : "0", NULL);
istate->updated_workdir = 0;
@@ -127,7 +127,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,
reflog_head);
}
if (run_hook)
- run_hook_le(NULL, "post-checkout",
+ run_hook_le(NULL, 0, "post-checkout",
oid_to_hex(orig ? orig : &null_oid),
oid_to_hex(oid), "1", NULL);
@@ -1343,7 +1343,7 @@ const char *find_hook(const char *name)
return path.buf;
}
-int run_hook_ve(const char *const *env, const char *name, va_list args)
+int run_hook_ve(const char *const *env, int opt, const char *name, va_list args)
{
struct child_process hook = CHILD_PROCESS_INIT;
const char *p;
@@ -1356,20 +1356,21 @@ int run_hook_ve(const char *const *env, const char *name, va_list args)
while ((p = va_arg(args, const char *)))
strvec_push(&hook.args, p);
hook.env = env;
- hook.no_stdin = 1;
+ if (!(opt & RUN_HOOK_ALLOW_STDIN))
+ hook.no_stdin = 1;
hook.stdout_to_stderr = 1;
hook.trace2_hook_name = name;
return run_command(&hook);
}
-int run_hook_le(const char *const *env, const char *name, ...)
+int run_hook_le(const char *const *env, int opt, const char *name, ...)
{
va_list args;
int ret;
va_start(args, name);
- ret = run_hook_ve(env, name, args);
+ ret = run_hook_ve(env, opt, name, args);
va_end(args);
return ret;
@@ -201,11 +201,16 @@ int run_command(struct child_process *);
*/
const char *find_hook(const char *name);
+#define RUN_HOOK_ALLOW_STDIN 1
+
/**
* Run a hook.
- * The first argument is a pathname to an index file, or NULL
- * if the hook uses the default index file or no index is needed.
- * The second argument is the name of the hook.
+ * The first argument is an array of environment variables, or NULL
+ * if the hook uses the default environment and doesn't require
+ * additional variables.
+ * The second argument is zero or RUN_HOOK_ALLOW_STDIN, which enables
+ * stdin for the child process (the default is no_stdin).
+ * The third argument is the name of the hook.
* The further arguments correspond to the hook arguments.
* The last argument has to be NULL to terminate the arguments list.
* If the hook does not exist or is not executable, the return
@@ -215,8 +220,8 @@ const char *find_hook(const char *name);
* On execution, .stdout_to_stderr and .no_stdin will be set.
*/
LAST_ARG_MUST_BE_NULL
-int run_hook_le(const char *const *env, const char *name, ...);
-int run_hook_ve(const char *const *env, const char *name, va_list args);
+int run_hook_le(const char *const *env, int opt, const char *name, ...);
+int run_hook_ve(const char *const *env, int opt, const char *name, va_list args);
/*
* Trigger an auto-gc
@@ -7,6 +7,7 @@ test_description='pre-commit and pre-merge-commit hooks'
HOOKDIR="$(git rev-parse --git-dir)/hooks"
PRECOMMIT="$HOOKDIR/pre-commit"
PREMERGE="$HOOKDIR/pre-merge-commit"
+POSTCOMMIT="$HOOKDIR/post-commit"
# Prepare sample scripts that write their $0 to actual_hooks
test_expect_success 'sample script setup' '
@@ -28,11 +29,15 @@ test_expect_success 'sample script setup' '
echo $0 >>actual_hooks
test $GIT_PREFIX = "success/"
EOF
- write_script "$HOOKDIR/check-author.sample" <<-\EOF
+ write_script "$HOOKDIR/check-author.sample" <<-\EOF &&
echo $0 >>actual_hooks
test "$GIT_AUTHOR_NAME" = "New Author" &&
test "$GIT_AUTHOR_EMAIL" = "newauthor@example.com"
EOF
+ write_script "$HOOKDIR/user-input.sample" <<-\EOF
+ ! read -r line || echo "$line" > hook_input
+ exit 0
+ EOF
'
test_expect_success 'root commit' '
@@ -278,4 +283,34 @@ test_expect_success 'check the author in hook' '
test_cmp expected_hooks actual_hooks
'
+test_expect_success 'with user input' '
+ test_when_finished "rm -f \"$PRECOMMIT\" user_input hook_input" &&
+ cp "$HOOKDIR/user-input.sample" "$PRECOMMIT" &&
+ echo "user input" > user_input &&
+ echo "more" >>file &&
+ git add file &&
+ git commit -m "more" < user_input &&
+ test_cmp user_input hook_input
+'
+
+test_expect_success 'post-commit with user input' '
+ test_when_finished "rm -f \"$POSTCOMMIT\" user_input hook_input" &&
+ cp "$HOOKDIR/user-input.sample" "$POSTCOMMIT" &&
+ echo "user input" > user_input &&
+ echo "more" >>file &&
+ git add file &&
+ git commit -m "more" < user_input &&
+ test_cmp user_input hook_input
+'
+
+test_expect_success 'with user input (merge)' '
+ test_when_finished "rm -f \"$PREMERGE\" user_input hook_input" &&
+ cp "$HOOKDIR/user-input.sample" "$PREMERGE" &&
+ echo "user input" > user_input &&
+ git checkout side &&
+ git merge -m "merge master" master < user_input &&
+ git checkout master &&
+ test_cmp user_input hook_input
+'
+
test_done
@@ -294,5 +294,20 @@ test_expect_success 'hook is called for reword during `rebase -i`' '
'
+# now a hook that accepts input and writes it as the commit message
+cat > "$HOOK" <<'EOF'
+#!/bin/sh
+! read -r line || echo "$line" > "$1"
+EOF
+chmod +x "$HOOK"
+
+test_expect_success 'hook with user input' '
+
+ echo "additional" >> file &&
+ git add file &&
+ echo "user input" | git commit -m "additional" &&
+ commit_msg_is "user input"
+
+'
test_done
@@ -91,6 +91,11 @@ else
fi
test "$GIT_EDITOR" = : && source="$source (no editor)"
+if read -r line
+then
+ source="$source $line"
+fi
+
if test $rebasing = 1
then
echo "$source $(get_last_cmd)" >"$1"
@@ -113,6 +118,15 @@ test_expect_success 'with hook (-m)' '
'
+test_expect_success 'with hook (-m and input)' '
+
+ echo "more" >> file &&
+ git add file &&
+ echo "user input" | git commit -m "more" &&
+ test "$(git log -1 --pretty=format:%s)" = "message (no editor) user input"
+
+'
+
test_expect_success 'with hook (-m editor)' '
echo "more" >> file &&