Add support for multiple hooks for the pre-receive, post-receive, update, post-update, and push-to-checkout hooks. Add tests for these hooks using the multiple hook test framework. Because the invocations of test_multiple_hooks contain multiple test assertions, they (and the cd commands that surround them) must occur outside of a subshell, or a failing test will not be noticed. Signed-off-by: brian m. carlson <sandals@xxxxxxxxxxxxxxxxxxxx> --- builtin/receive-pack.c | 212 ++++++++++++++++++++++++----------------- t/t5516-fetch-push.sh | 29 ++++++ 2 files changed, 152 insertions(+), 89 deletions(-) diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 29f165d8bd..7454834d2a 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -680,72 +680,82 @@ typedef int (*feed_fn)(void *, const char **, size_t *); static int run_and_feed_hook(const char *hook_name, feed_fn feed, struct receive_hook_feed_state *feed_state) { - struct child_process proc = CHILD_PROCESS_INIT; + struct child_process proc; + struct string_list *hooks; + struct string_list_item *p; struct async muxer; const char *argv[2]; - int code; + int code = 0; - argv[0] = find_hook(hook_name); - if (!argv[0]) + hooks = find_hooks(hook_name); + if (!hooks) return 0; - argv[1] = NULL; + for_each_string_list_item(p, hooks) { + argv[0] = p->string; + argv[1] = NULL; - proc.argv = argv; - proc.in = -1; - proc.stdout_to_stderr = 1; - proc.trace2_hook_name = hook_name; + child_process_init(&proc); + proc.argv = argv; + proc.in = -1; + proc.stdout_to_stderr = 1; + proc.trace2_hook_name = hook_name; - if (feed_state->push_options) { - int i; - for (i = 0; i < feed_state->push_options->nr; i++) - argv_array_pushf(&proc.env_array, - "GIT_PUSH_OPTION_%d=%s", i, - feed_state->push_options->items[i].string); - argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT=%d", - feed_state->push_options->nr); - } else - argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT"); + if (feed_state->push_options) { + int i; + for (i = 0; i < feed_state->push_options->nr; i++) + argv_array_pushf(&proc.env_array, + "GIT_PUSH_OPTION_%d=%s", i, + feed_state->push_options->items[i].string); + argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT=%d", + feed_state->push_options->nr); + } else + argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT"); - if (tmp_objdir) - argv_array_pushv(&proc.env_array, tmp_objdir_env(tmp_objdir)); + if (tmp_objdir) + argv_array_pushv(&proc.env_array, tmp_objdir_env(tmp_objdir)); - if (use_sideband) { - memset(&muxer, 0, sizeof(muxer)); - muxer.proc = copy_to_sideband; - muxer.in = -1; - code = start_async(&muxer); - if (code) - return code; - proc.err = muxer.in; - } + if (use_sideband) { + memset(&muxer, 0, sizeof(muxer)); + muxer.proc = copy_to_sideband; + muxer.in = -1; + code = start_async(&muxer); + if (code) + break; + proc.err = muxer.in; + } - prepare_push_cert_sha1(&proc); + prepare_push_cert_sha1(&proc); - code = start_command(&proc); - if (code) { + code = start_command(&proc); + if (code) { + if (use_sideband) + finish_async(&muxer); + break; + } + + sigchain_push(SIGPIPE, SIG_IGN); + + while (1) { + const char *buf; + size_t n; + if (feed(feed_state, &buf, &n)) + break; + if (write_in_full(proc.in, buf, n) < 0) + break; + } + close(proc.in); if (use_sideband) finish_async(&muxer); - return code; - } - sigchain_push(SIGPIPE, SIG_IGN); + sigchain_pop(SIGPIPE); - while (1) { - const char *buf; - size_t n; - if (feed(feed_state, &buf, &n)) - break; - if (write_in_full(proc.in, buf, n) < 0) + code = finish_command(&proc); + if (code) break; } - close(proc.in); - if (use_sideband) - finish_async(&muxer); - - sigchain_pop(SIGPIPE); - - return finish_command(&proc); + free_hooks(hooks); + return code; } static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep) @@ -793,30 +803,41 @@ static int run_receive_hook(struct command *commands, static int run_update_hook(struct command *cmd) { const char *argv[5]; - struct child_process proc = CHILD_PROCESS_INIT; + struct child_process proc; + struct string_list *hooks; + struct string_list_item *p; int code; - argv[0] = find_hook("update"); - if (!argv[0]) + hooks = find_hooks("update"); + if (!hooks) return 0; - argv[1] = cmd->ref_name; - argv[2] = oid_to_hex(&cmd->old_oid); - argv[3] = oid_to_hex(&cmd->new_oid); - argv[4] = NULL; + for_each_string_list_item(p, hooks) { + child_process_init(&proc); - proc.no_stdin = 1; - proc.stdout_to_stderr = 1; - proc.err = use_sideband ? -1 : 0; - proc.argv = argv; - proc.trace2_hook_name = "update"; + argv[0] = p->string; + argv[1] = cmd->ref_name; + argv[2] = oid_to_hex(&cmd->old_oid); + argv[3] = oid_to_hex(&cmd->new_oid); + argv[4] = NULL; - code = start_command(&proc); - if (code) - return code; - if (use_sideband) - copy_to_sideband(proc.err, -1, NULL); - return finish_command(&proc); + proc.no_stdin = 1; + proc.stdout_to_stderr = 1; + proc.err = use_sideband ? -1 : 0; + proc.argv = argv; + proc.trace2_hook_name = "update"; + + code = start_command(&proc); + if (code) + return code; + if (use_sideband) + copy_to_sideband(proc.err, -1, NULL); + code = finish_command(&proc); + if (code) + break; + } + free_hooks(hooks); + return code; } static int is_ref_checked_out(const char *ref) @@ -1005,16 +1026,20 @@ static const char *update_worktree(unsigned char *sha1) const char *retval; const char *work_tree = git_work_tree_cfg ? git_work_tree_cfg : ".."; struct argv_array env = ARGV_ARRAY_INIT; + struct string_list *hooks; if (is_bare_repository()) return "denyCurrentBranch = updateInstead needs a worktree"; argv_array_pushf(&env, "GIT_DIR=%s", absolute_path(get_git_dir())); - if (!find_hook(push_to_checkout_hook)) + hooks = find_hooks(push_to_checkout_hook); + if (!hooks) retval = push_to_deploy(sha1, &env, work_tree); - else + else { + free_hooks(hooks); retval = push_to_checkout(sha1, &env, work_tree); + } argv_array_clear(&env); return retval; @@ -1173,33 +1198,42 @@ static const char *update(struct command *cmd, struct shallow_info *si) static void run_update_post_hook(struct command *commands) { struct command *cmd; - struct child_process proc = CHILD_PROCESS_INIT; - const char *hook; + struct child_process proc; + struct string_list *hooks; + struct string_list_item *p; - hook = find_hook("post-update"); - if (!hook) + hooks = find_hooks("post-update"); + if (!hooks) return; - for (cmd = commands; cmd; cmd = cmd->next) { - if (cmd->error_string || cmd->did_not_exist) - continue; + for_each_string_list_item(p, hooks) { + child_process_init(&proc); + + for (cmd = commands; cmd; cmd = cmd->next) { + if (cmd->error_string || cmd->did_not_exist) + continue; + if (!proc.args.argc) + argv_array_push(&proc.args, p->string); + argv_array_push(&proc.args, cmd->ref_name); + } if (!proc.args.argc) - argv_array_push(&proc.args, hook); - argv_array_push(&proc.args, cmd->ref_name); - } - if (!proc.args.argc) - return; + return; - proc.no_stdin = 1; - proc.stdout_to_stderr = 1; - proc.err = use_sideband ? -1 : 0; - proc.trace2_hook_name = "post-update"; + proc.no_stdin = 1; + proc.stdout_to_stderr = 1; + proc.err = use_sideband ? -1 : 0; + proc.trace2_hook_name = "post-update"; - if (!start_command(&proc)) { - if (use_sideband) - copy_to_sideband(proc.err, -1, NULL); - finish_command(&proc); + if (!start_command(&proc)) { + int ret; + if (use_sideband) + copy_to_sideband(proc.err, -1, NULL); + ret = finish_command(&proc); + if (ret) + break; + } } + free_hooks(hooks); } static void check_aliased_update_internal(struct command *cmd, diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 49bf4280e8..3143422344 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -15,6 +15,7 @@ This test checks the following functionality: ' . ./test-lib.sh +. "$TEST_DIRECTORY/lib-hooks.sh" D=$(pwd) @@ -1700,4 +1701,32 @@ test_expect_success 'updateInstead with push-to-checkout hook' ' ) ' +test_expect_success 'setup' ' + mk_test_with_hooks hooktest heads/master +' + +cmd_receive () { + echo "$1" >>../file && + git -C .. add file && + git -C .. commit -m "$1" && + git -C .. push hooktest refs/heads/master:refs/heads/master +} + +cd hooktest +test_multiple_hooks pre-receive cmd_receive +test_multiple_hooks --ignore-exit-status post-receive cmd_receive +test_multiple_hooks update cmd_receive +test_multiple_hooks --ignore-exit-status post-update cmd_receive +cd .. + +test_expect_success 'setup' ' + rm -fr hooktest && + git init hooktest && + git -C hooktest config receive.denyCurrentBranch updateInstead +' + +cd hooktest +test_multiple_hooks push-to-checkout cmd_receive +cd .. + test_done