From patchwork Tue May 14 00:23:25 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: "brian m. carlson" X-Patchwork-Id: 10941873 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 5ECBB933 for ; Tue, 14 May 2019 00:23:54 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 4BEEE2852C for ; Tue, 14 May 2019 00:23:54 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 403D32853E; Tue, 14 May 2019 00:23:54 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-8.0 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 438BA2852C for ; Tue, 14 May 2019 00:23:53 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726732AbfENAXw (ORCPT ); Mon, 13 May 2019 20:23:52 -0400 Received: from injection.crustytoothpaste.net ([192.241.140.119]:36398 "EHLO injection.crustytoothpaste.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726623AbfENAXv (ORCPT ); Mon, 13 May 2019 20:23:51 -0400 Received: from genre.crustytoothpaste.net (unknown [IPv6:2001:470:b978:101:89af:9dea:d4e0:996c]) (using TLSv1.2 with cipher ECDHE-RSA-CHACHA20-POLY1305 (256/256 bits)) (No client certificate requested) by injection.crustytoothpaste.net (Postfix) with ESMTPSA id 634B56081D; Tue, 14 May 2019 00:23:48 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=crustytoothpaste.net; s=default; t=1557793429; bh=x5Gk5KjbdXVxXq1GK3c2GUBbQW+lU5HF1SnzXoTDJGc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:Content-Type:From: Reply-To:Subject:Date:To:CC:Resent-Date:Resent-From:Resent-To: Resent-Cc:In-Reply-To:References:Content-Type:Content-Disposition; b=O+NuPCCKZOYr8RmRoByqezBSIaR7pVE8/E61phVb5psMCyNYpk55g102QPoPzI48p EWqa8lNIh+sBhZ9GcHQahpa9IZc8WkVvLFfyaAlo34CDdbakR4zpwcRdIcOWlYzLV1 EIiiHncHnGjR8NI+5F05YgUx+Fl+bSxd4+Hj/8XvKZH4j7oXn3EW0GCnCJ5QtW0yUv /XX1kLatdAYshQdGmghXYff24c/ZwJ/JcV9G5dCu49/x7eQOEYQjTql00e2YO9Dzfh X9ifV6ZAcjuOqjxWrv+9GSjZSQAp0ZS5K4xInGKorSrEHKMFtIbvdSTLXmW+VdDNW+ 9liFOVwkxJgguG8h8+ao7Ri+bc+0Z45yCXjNcdMRaOJgZhXJpopIIn2HjQGD1DUHm9 ndz5fMJIt7Lp1YXXTBknqrt5DoMTEYiYJwddn9Wsd49orLMHKWgA3Kx+ryxy3Gy2BR sBYU9ArfDCLG+ihgtmJvZzMi6scj9TISiwSHiRAHdELVCOd+/GA From: "brian m. carlson" To: Cc: Jeff King , Duy Nguyen , Johannes Schindelin , Junio C Hamano , Johannes Sixt , =?utf-8?b?w4Z2YXIgQXJuZmrDtnLDsCBCamFybWFzb24=?= , Phillip Wood , Jonathan Nieder Subject: [PATCH v2 1/7] run-command: add preliminary support for multiple hooks Date: Tue, 14 May 2019 00:23:25 +0000 Message-Id: <20190514002332.121089-2-sandals@crustytoothpaste.net> X-Mailer: git-send-email 2.21.0.1020.gf2820cf01a In-Reply-To: <20190514002332.121089-1-sandals@crustytoothpaste.net> References: <20190514002332.121089-1-sandals@crustytoothpaste.net> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 127.0.1.1 Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP A variety of types of software take advantage of Git's hooks. However, if a user would like to integrate multiple pieces of software which use a particular hook, they currently must manage those hooks themselves, which can be burdensome. Sometimes various pieces of software try to overwrite each other's hooks, leading to problems. To solve this problem, introduce a framework for running multiple hooks using a ".d" directory named similarly to the hook, running each hook in order sorted by name. Wire this framework up for those functions using run_hook_le or run_hook_ve. To preserve backwards compatibility, ensure that multiple hooks run only if there is no hook using the current hook style. If we are running multiple hooks and one of them exits nonzero, don't execute the remaining hooks and return that exit code immediately. This allows hooks to fail fast and it avoids having to deal with what happens if multiple hooks fail with different exit statuses. Create a test framework for testing multiple hooks with different commands. This is necessary because not all hooks use run_hook_ve or run_hook_le and we'll want to ensure all the various hooks work without needing to write lots of duplicative test code. Test the pre-commit hook to verify that the run_hook_ve implementation works correctly. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: brian m. carlson --- builtin/commit.c | 2 +- run-command.c | 173 +++++++++++++++++++++++++++++-------- run-command.h | 15 ++++ t/lib-hooks.sh | 172 ++++++++++++++++++++++++++++++++++++ t/t7503-pre-commit-hook.sh | 15 ++++ 5 files changed, 342 insertions(+), 35 deletions(-) create mode 100644 t/lib-hooks.sh diff --git a/builtin/commit.c b/builtin/commit.c index 833ecb316a..29bf80e0d1 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -943,7 +943,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, return 0; } - if (!no_verify && find_hook("pre-commit")) { + if (!no_verify && find_hooks("pre-commit", NULL)) { /* * Re-read the index as pre-commit hook could have updated it, * and write it out as a tree. We must do this before we invoke diff --git a/run-command.c b/run-command.c index 3449db319b..eb075ac86b 100644 --- a/run-command.c +++ b/run-command.c @@ -1308,53 +1308,143 @@ int async_with_fork(void) #endif } +/* + * Return 1 if a hook exists at path (which may be modified) using access(2) + * with check (which should be F_OK or X_OK), 0 otherwise. If strip is true, + * additionally consider the same filename but with STRIP_EXTENSION added. + * If check is X_OK, warn if the hook exists but is not executable. + */ +static int has_hook(struct strbuf *path, int strip, int check) +{ + if (access(path->buf, check) < 0) { + int err = errno; + + if (strip) { +#ifdef STRIP_EXTENSION + strbuf_addstr(path, STRIP_EXTENSION); + if (access(path->buf, check) >= 0) + return 1; + if (errno == EACCES) + err = errno; +#endif + } + + if (err == EACCES && advice_ignored_hook) { + static struct string_list advise_given = STRING_LIST_INIT_DUP; + + if (!string_list_lookup(&advise_given, path->buf)) { + string_list_insert(&advise_given, path->buf); + advise(_("The '%s' hook was ignored because " + "it's not set as executable.\n" + "You can disable this warning with " + "`git config advice.ignoredHook false`."), + path->buf); + } + } + return 0; + } + return 1; +} + const char *find_hook(const char *name) { static struct strbuf path = STRBUF_INIT; strbuf_reset(&path); strbuf_git_path(&path, "hooks/%s", name); - if (access(path.buf, X_OK) < 0) { - int err = errno; - -#ifdef STRIP_EXTENSION - strbuf_addstr(&path, STRIP_EXTENSION); - if (access(path.buf, X_OK) >= 0) - return path.buf; - if (errno == EACCES) - err = errno; -#endif - - if (err == EACCES && advice_ignored_hook) { - static struct string_list advise_given = STRING_LIST_INIT_DUP; - - if (!string_list_lookup(&advise_given, name)) { - string_list_insert(&advise_given, name); - advise(_("The '%s' hook was ignored because " - "it's not set as executable.\n" - "You can disable this warning with " - "`git config advice.ignoredHook false`."), - path.buf); - } - } - return NULL; + if (has_hook(&path, 1, X_OK)) { + return path.buf; } - return path.buf; + return NULL; } -int run_hook_ve(const char *const *env, const char *name, va_list args) +int find_hooks(const char *name, struct string_list *list) { - struct child_process hook = CHILD_PROCESS_INIT; - const char *p; + struct strbuf path = STRBUF_INIT; + DIR *d; + struct dirent *de; - p = find_hook(name); - if (!p) + /* + * We look for a single hook. If present, return it, and skip the + * individual directories. + */ + strbuf_git_path(&path, "hooks/%s", name); + if (has_hook(&path, 1, X_OK)) { + if (list) + string_list_append(list, path.buf); + return 1; + } + + if (has_hook(&path, 1, F_OK)) return 0; - argv_array_push(&hook.args, p); - while ((p = va_arg(args, const char *))) - argv_array_push(&hook.args, p); - hook.env = env; + strbuf_reset(&path); + strbuf_git_path(&path, "hooks/%s.d", name); + d = opendir(path.buf); + if (!d) { + if (list) + string_list_clear(list, 0); + return 0; + } + while ((de = readdir(d))) { + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) + continue; + strbuf_reset(&path); + strbuf_git_path(&path, "hooks/%s.d/%s", name, de->d_name); + if (has_hook(&path, 0, X_OK)) { + if (list) + string_list_append(list, path.buf); + else + return 1; + } + } + closedir(d); + strbuf_reset(&path); + if (!list->nr) { + return 0; + } + + string_list_sort(list); + return 1; +} + +int for_each_hook(const char *name, + int (*handler)(const char *name, const char *path, void *), + void *data) +{ + struct string_list paths = STRING_LIST_INIT_DUP; + int i, ret = 0; + + find_hooks(name, &paths); + for (i = 0; i < paths.nr; i++) { + const char *p = paths.items[i].string; + + ret = handler(name, p, data); + if (ret) + break; + } + + string_list_clear(&paths, 0); + return ret; +} + +struct hook_data { + const char *const *env; + struct string_list *args; +}; + +static int do_run_hook_ve(const char *name, const char *path, void *cb) +{ + struct hook_data *data = cb; + struct child_process hook = CHILD_PROCESS_INIT; + struct string_list_item *arg; + + argv_array_push(&hook.args, path); + for_each_string_list_item(arg, data->args) { + argv_array_push(&hook.args, arg->string); + } + + hook.env = data->env; hook.no_stdin = 1; hook.stdout_to_stderr = 1; hook.trace2_hook_name = name; @@ -1362,6 +1452,21 @@ int run_hook_ve(const char *const *env, const char *name, va_list args) return run_command(&hook); } +int run_hook_ve(const char *const *env, const char *name, va_list args) +{ + struct string_list arglist = STRING_LIST_INIT_DUP; + struct hook_data data = {env, &arglist}; + const char *p; + int ret = 0; + + while ((p = va_arg(args, const char *))) + string_list_append(&arglist, p); + + ret = for_each_hook(name, do_run_hook_ve, &data); + string_list_clear(&arglist, 0); + return ret; +} + int run_hook_le(const char *const *env, const char *name, ...) { va_list args; diff --git a/run-command.h b/run-command.h index a6950691c0..1b3677fcac 100644 --- a/run-command.h +++ b/run-command.h @@ -4,6 +4,7 @@ #include "thread-utils.h" #include "argv-array.h" +#include "string-list.h" struct child_process { const char **argv; @@ -68,6 +69,20 @@ int run_command(struct child_process *); * overwritten by further calls to find_hook and run_hook_*. */ extern const char *find_hook(const char *name); +/* + * Returns the paths to all hook files, or NULL if all hooks are missing or + * disabled. + * Returns 1 if there are hooks; 0 otherwise. If hooks is not NULL, stores the + * names of the hooks into them in the order they should be executed. + */ +int find_hooks(const char *name, struct string_list *hooks); +/* + * Invokes the handler function once for each hook. Returns 0 if all hooks were + * successful, or the exit status of the first failing hook. + */ +int for_each_hook(const char *name, + int (*handler)(const char *name, const char *path, void *), + void *data); LAST_ARG_MUST_BE_NULL extern int run_hook_le(const char *const *env, const char *name, ...); extern int run_hook_ve(const char *const *env, const char *name, va_list args); diff --git a/t/lib-hooks.sh b/t/lib-hooks.sh new file mode 100644 index 0000000000..721250aea0 --- /dev/null +++ b/t/lib-hooks.sh @@ -0,0 +1,172 @@ +create_multihooks () { + mkdir -p "$MULTIHOOK_DIR" + for i in "a $1" "b $2" "c $3" + do + echo "$i" | (while read script ex + do + mkdir -p "$MULTIHOOK_DIR" + write_script "$MULTIHOOK_DIR/$script" <<-EOF + mkdir -p "$OUTPUTDIR" + touch "$OUTPUTDIR/$script" + exit $ex + EOF + done) + done +} + +# Run the multiple hook tests. +# Usage: test_multiple_hooks [--ignore-exit-status] HOOK COMMAND [SKIP-COMMAND] +# HOOK: the name of the hook to test +# COMMAND: command to test the hook for; takes a single argument indicating test +# name. +# SKIP-COMMAND: like $1, except the hook should be skipped. +# --ignore-exit-status: the command does not fail if the exit status from the +# hook is nonzero. +test_multiple_hooks () { + local must_fail cmd skip_cmd hook + if test "$1" = "--ignore-exit-status" + then + shift + else + must_fail="test_must_fail" + fi + hook="$1" + cmd="$2" + skip_cmd="$3" + + HOOKDIR="$(git rev-parse --absolute-git-dir)/hooks" + OUTPUTDIR="$(git rev-parse --absolute-git-dir)/../hook-output" + HOOK="$HOOKDIR/$hook" + MULTIHOOK_DIR="$HOOKDIR/$hook.d" + rm -f "$HOOK" "$MULTIHOOK_DIR" "$OUTPUTDIR" + + test_expect_success "$hook: with no hook" ' + $cmd foo + ' + + if test -n "$skip_cmd" + then + test_expect_success "$hook: skipped hook with no hook" ' + $skip_cmd bar + ' + fi + + test_expect_success 'setup' ' + mkdir -p "$HOOKDIR" && + write_script "$HOOK" <<-EOF + mkdir -p "$OUTPUTDIR" + touch "$OUTPUTDIR/simple" + exit 0 + EOF + ' + + test_expect_success "$hook: with succeeding hook" ' + test_when_finished "rm -fr \"$OUTPUTDIR\"" && + $cmd more && + test -f "$OUTPUTDIR/simple" + ' + + if test -n "$skip_cmd" + then + test_expect_success "$hook: skipped but succeeding hook" ' + test_when_finished "rm -fr \"$OUTPUTDIR\"" && + $skip_cmd even-more && + ! test -f "$OUTPUTDIR/simple" + ' + fi + + test_expect_success "$hook: with both simple and multiple hooks, simple success" ' + test_when_finished "rm -fr \"$OUTPUTDIR\"" && + create_multihooks 0 1 0 && + $cmd yet-more && + test -f "$OUTPUTDIR/simple" && + ! test -f "$OUTPUTDIR/a" && + ! test -f "$OUTPUTDIR/b" && + ! test -f "$OUTPUTDIR/c" + ' + + test_expect_success 'setup' ' + rm -fr "$MULTIHOOK_DIR" && + + # now a hook that fails + write_script "$HOOK" <<-EOF + mkdir -p "$OUTPUTDIR" + touch "$OUTPUTDIR/simple" + exit 1 + EOF + ' + + test_expect_success "$hook: with failing hook" ' + test_when_finished "rm -fr \"$OUTPUTDIR\"" && + $must_fail $cmd another && + test -f "$OUTPUTDIR/simple" + ' + + if test -n "$skip_cmd" + then + test_expect_success "$hook: skipped but failing hook" ' + test_when_finished "rm -fr \"$OUTPUTDIR\"" && + $skip_cmd stuff && + ! test -f "$OUTPUTDIR/simple" + ' + fi + + test_expect_success "$hook: with both simple and multiple hooks, simple failure" ' + test_when_finished "rm -fr \"$OUTPUTDIR\"" && + create_multihooks 0 1 0 && + $must_fail $cmd more-stuff && + test -f "$OUTPUTDIR/simple" && + ! test -f "$OUTPUTDIR/a" && + ! test -f "$OUTPUTDIR/b" && + ! test -f "$OUTPUTDIR/c" + ' + + test_expect_success "$hook: multiple hooks, all successful" ' + test_when_finished "rm -fr \"$OUTPUTDIR\"" && + rm -f "$HOOK" && + create_multihooks 0 0 0 && + $cmd content && + test -f "$OUTPUTDIR/a" && + test -f "$OUTPUTDIR/b" && + test -f "$OUTPUTDIR/c" + ' + + test_expect_success "$hook: hooks after first failure not executed" ' + test_when_finished "rm -fr \"$OUTPUTDIR\"" && + create_multihooks 0 1 0 && + $must_fail $cmd more-content && + test -f "$OUTPUTDIR/a" && + test -f "$OUTPUTDIR/b" && + ! test -f "$OUTPUTDIR/c" + ' + + test_expect_success POSIXPERM "$hook: non-executable hook not executed" ' + test_when_finished "rm -fr \"$OUTPUTDIR\"" && + create_multihooks 0 1 0 && + chmod -x "$MULTIHOOK_DIR/b" && + $cmd things && + test -f "$OUTPUTDIR/a" && + ! test -f "$OUTPUTDIR/b" && + test -f "$OUTPUTDIR/c" + ' + + test_expect_success POSIXPERM "$hook: multiple hooks not executed if simple hook present" ' + test_when_finished "rm -fr \"$OUTPUTDIR\" && rm -f \"$HOOK\"" && + write_script "$HOOK" <<-EOF && + mkdir -p "$OUTPUTDIR" + touch "$OUTPUTDIR/simple" + exit 0 + EOF + create_multihooks 0 1 0 && + chmod -x "$HOOK" && + $cmd other-things && + ! test -f "$OUTPUTDIR/simple" && + ! test -f "$OUTPUTDIR/a" && + ! test -f "$OUTPUTDIR/b" && + ! test -f "$OUTPUTDIR/c" + ' + + test_expect_success 'cleanup' ' + rm -fr "$MULTIHOOK_DIR" + ' +} diff --git a/t/t7503-pre-commit-hook.sh b/t/t7503-pre-commit-hook.sh index 984889b39d..d63d059e04 100755 --- a/t/t7503-pre-commit-hook.sh +++ b/t/t7503-pre-commit-hook.sh @@ -3,6 +3,7 @@ test_description='pre-commit hook' . ./test-lib.sh +. "$TEST_DIRECTORY/lib-hooks.sh" test_expect_success 'with no hook' ' @@ -136,4 +137,18 @@ test_expect_success 'check the author in hook' ' git show -s ' +commit_command () { + echo "$1" >>file && + git add file && + git commit -m "$1" +} + +commit_no_verify_command () { + echo "$1" >>file && + git add file && + git commit --no-verify -m "$1" +} + +test_multiple_hooks pre-commit commit_command commit_no_verify_command + test_done From patchwork Tue May 14 00:23:26 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "brian m. carlson" X-Patchwork-Id: 10941875 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 05B5876 for ; Tue, 14 May 2019 00:23:56 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id E7ED62852C for ; Tue, 14 May 2019 00:23:55 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id DC0782853E; Tue, 14 May 2019 00:23:55 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-8.0 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 427162852C for ; Tue, 14 May 2019 00:23:55 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726753AbfENAXy (ORCPT ); Mon, 13 May 2019 20:23:54 -0400 Received: from injection.crustytoothpaste.net ([192.241.140.119]:36410 "EHLO injection.crustytoothpaste.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726623AbfENAXx (ORCPT ); Mon, 13 May 2019 20:23:53 -0400 Received: from genre.crustytoothpaste.net (unknown [IPv6:2001:470:b978:101:89af:9dea:d4e0:996c]) (using TLSv1.2 with cipher ECDHE-RSA-CHACHA20-POLY1305 (256/256 bits)) (No client certificate requested) by injection.crustytoothpaste.net (Postfix) with ESMTPSA id 14E326081E; Tue, 14 May 2019 00:23:50 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=crustytoothpaste.net; s=default; t=1557793430; bh=7DlsO1mFZm0RqwF6/9Pqs4pYRp8J2urx1klf493ot6k=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From:Reply-To: Subject:Date:To:CC:Resent-Date:Resent-From:Resent-To:Resent-Cc: In-Reply-To:References:Content-Type:Content-Disposition; b=zEfmKZaeI61VukC3Y+9Bj3Jmp4Nw4aSFR7+xqxAsEpAVALpLRh6zpydvnErFH1W37 rpDWXz5LtTniBETcfN8tsaj9U7vmzQOGkBlmaCPMv5wXm2MPjqKuaFq+82kFQPC93D 8+HDwO+ukwLLfHRwyL5sScv+GveOdbykbkZBcI03gT4etPXz2SOaYV2Qm/hXWCr2ND hcZP9SWQE0lceVRvqHPj4jr59VCPbgvJGVzqkAjM8ponelnTaX0o6wx+t7ulwFqH5N aM0uMF6gubq/ERf1NyOX3nQ9mdV3OCXRr5ekVrWhZXn5vwucjgZ0YWnZNjkjgXbxRu 76+rxVLeHStxUhsZ2md+CHoedr6R+01KSTsvdxzyZ2l595KMgFR2OFau3LJz8icWa8 QPRFUoRrRI2hwlFKmjs5Wxz71SZ1CDf3zyOjOJz1u+hJU1l+CnAP1FaZRVXelwcUVG JtMN882QvhwMXe6JrdHNhsSyLyS8kdKbTkCAwr2e7EE1L43Os78 From: "brian m. carlson" To: Cc: Jeff King , Duy Nguyen , Johannes Schindelin , Junio C Hamano , Johannes Sixt , =?utf-8?b?w4Z2YXIgQXJuZmrDtnLDsCBCamFybWFzb24=?= , Phillip Wood , Jonathan Nieder Subject: [PATCH v2 2/7] builtin/receive-pack: add support for multiple hooks Date: Tue, 14 May 2019 00:23:26 +0000 Message-Id: <20190514002332.121089-3-sandals@crustytoothpaste.net> X-Mailer: git-send-email 2.21.0.1020.gf2820cf01a In-Reply-To: <20190514002332.121089-1-sandals@crustytoothpaste.net> References: <20190514002332.121089-1-sandals@crustytoothpaste.net> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 127.0.1.1 Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- builtin/receive-pack.c | 78 ++++++++++++++++++++++++++---------------- t/t5516-fetch-push.sh | 30 ++++++++++++++++ 2 files changed, 78 insertions(+), 30 deletions(-) diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 29f165d8bd..5940f6969a 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -669,6 +669,8 @@ static void prepare_push_cert_sha1(struct child_process *proc) } } +typedef int (*feed_fn)(void *, const char **, size_t *); + struct receive_hook_feed_state { struct command *cmd; int skip_broken; @@ -676,34 +678,36 @@ struct receive_hook_feed_state { const struct string_list *push_options; }; -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 receive_hook_data { + feed_fn fn; + struct receive_hook_feed_state *state; +}; + +static int do_run_and_feed_hook(const char *name, const char *path, void *cbp) { - struct child_process proc = CHILD_PROCESS_INIT; + struct receive_hook_data *data = cbp; + struct child_process proc; struct async muxer; const char *argv[2]; - int code; - - argv[0] = find_hook(hook_name); - if (!argv[0]) - return 0; + int code = 0; + argv[0] = path; argv[1] = NULL; + child_process_init(&proc); proc.argv = argv; proc.in = -1; proc.stdout_to_stderr = 1; - proc.trace2_hook_name = hook_name; + proc.trace2_hook_name = name; - if (feed_state->push_options) { + if (data->state->push_options) { int i; - for (i = 0; i < feed_state->push_options->nr; i++) + for (i = 0; i < data->state->push_options->nr; i++) argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_%d=%s", i, - feed_state->push_options->items[i].string); + data->state->push_options->items[i].string); argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT=%d", - feed_state->push_options->nr); + data->state->push_options->nr); } else argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT"); @@ -734,7 +738,7 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, while (1) { const char *buf; size_t n; - if (feed(feed_state, &buf, &n)) + if (data->fn(data->state, &buf, &n)) break; if (write_in_full(proc.in, buf, n) < 0) break; @@ -748,6 +752,13 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, return finish_command(&proc); } +static int run_and_feed_hook(const char *hook_name, feed_fn feed, + struct receive_hook_feed_state *feed_state) +{ + struct receive_hook_data data = { feed, feed_state }; + return for_each_hook(hook_name, do_run_and_feed_hook, &data); +} + static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep) { struct receive_hook_feed_state *state = state_; @@ -790,16 +801,14 @@ static int run_receive_hook(struct command *commands, return status; } -static int run_update_hook(struct command *cmd) +static int do_run_update_hook(const char *name, const char *path, void *data) { - const char *argv[5]; + struct command *cmd = data; struct child_process proc = CHILD_PROCESS_INIT; + const char *argv[5]; int code; - argv[0] = find_hook("update"); - if (!argv[0]) - return 0; - + argv[0] = path; argv[1] = cmd->ref_name; argv[2] = oid_to_hex(&cmd->old_oid); argv[3] = oid_to_hex(&cmd->new_oid); @@ -819,6 +828,11 @@ static int run_update_hook(struct command *cmd) return finish_command(&proc); } +static int run_update_hook(struct command *cmd) +{ + return for_each_hook("update", do_run_update_hook, cmd); +} + static int is_ref_checked_out(const char *ref) { if (is_bare_repository()) @@ -1011,7 +1025,7 @@ static const char *update_worktree(unsigned char *sha1) argv_array_pushf(&env, "GIT_DIR=%s", absolute_path(get_git_dir())); - if (!find_hook(push_to_checkout_hook)) + if (!find_hooks(push_to_checkout_hook, NULL)) retval = push_to_deploy(sha1, &env, work_tree); else retval = push_to_checkout(sha1, &env, work_tree); @@ -1170,25 +1184,23 @@ static const char *update(struct command *cmd, struct shallow_info *si) } } -static void run_update_post_hook(struct command *commands) +static int do_run_update_post_hook(const char *name, const char *path, void *data) { + struct command *commands = data; struct command *cmd; struct child_process proc = CHILD_PROCESS_INIT; - const char *hook; - hook = find_hook("post-update"); - if (!hook) - return; + 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, hook); + argv_array_push(&proc.args, path); argv_array_push(&proc.args, cmd->ref_name); } if (!proc.args.argc) - return; + return 0; proc.no_stdin = 1; proc.stdout_to_stderr = 1; @@ -1198,8 +1210,14 @@ static void run_update_post_hook(struct command *commands) if (!start_command(&proc)) { if (use_sideband) copy_to_sideband(proc.err, -1, NULL); - finish_command(&proc); + return finish_command(&proc); } + return -1; +} + +static void run_update_post_hook(struct command *commands) +{ + for_each_hook("post-update", do_run_update_post_hook, commands); } static void check_aliased_update_internal(struct command *cmd, diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index c81ca360ac..697c3ab074 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) @@ -1712,4 +1713,33 @@ test_expect_success 'updateInstead with push-to-checkout hook' ' ) ' +test_expect_success 'setup' ' + mk_test_with_hooks hooktest heads/master +' + +cmd_receive () { + git reset --hard && + 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 From patchwork Tue May 14 00:23:27 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "brian m. carlson" X-Patchwork-Id: 10941877 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id A950576 for ; Tue, 14 May 2019 00:23:58 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 97A122852C for ; Tue, 14 May 2019 00:23:58 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 8B8A92853E; Tue, 14 May 2019 00:23:58 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-8.0 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 652492852C for ; Tue, 14 May 2019 00:23:57 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726769AbfENAX4 (ORCPT ); Mon, 13 May 2019 20:23:56 -0400 Received: from injection.crustytoothpaste.net ([192.241.140.119]:36420 "EHLO injection.crustytoothpaste.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726623AbfENAX4 (ORCPT ); Mon, 13 May 2019 20:23:56 -0400 Received: from genre.crustytoothpaste.net (unknown [IPv6:2001:470:b978:101:89af:9dea:d4e0:996c]) (using TLSv1.2 with cipher ECDHE-RSA-CHACHA20-POLY1305 (256/256 bits)) (No client certificate requested) by injection.crustytoothpaste.net (Postfix) with ESMTPSA id BF8ED60443; Tue, 14 May 2019 00:23:52 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=crustytoothpaste.net; s=default; t=1557793433; bh=KN2MXrk3t7ueXlyghaXXv6/LYX3nQ3f0ZEcuyaYpS6U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From:Reply-To: Subject:Date:To:CC:Resent-Date:Resent-From:Resent-To:Resent-Cc: In-Reply-To:References:Content-Type:Content-Disposition; b=ar4aGnoVaY8zxqYTmOLn1/oN6EcZ3Oczq2RffRTBov8Kv6NdAf3hALZpqGW/oTfaj 5r2pEFYL6n2qQ8hR25csRzZad65W7BQUSkByaDtnIWh+ntJWs6UA9AMq3HGo0SsBXp cXL2Rldrceha83Dj0YblaSK95WjHECfXVuogDkyD5ny+bo9E2NAjL49FVS/URoeOO+ jYFYk309sZ4CZy8fx1gPjKIpEpAewaNEVHOppcL+WNhmRDLSYexh/Xm46JjB0jgM97 iY/LBvexojIkQmed0A0pxGTOGs+uUp9cn9WD4WZk/s37qCJjKjVo/3h6CWajsim+WP 7pcd0o4oHx9PhJa1fG+2EwsjTW6l+sw13u5lfsyXxZVnanByLKj0tx0qD6nr+MNIIr ZYrXS014T0XmfmF3cBuWMcarPSHub7dJ+JZC3fNRK5jRcNrJPCMEd32oIIZPLtOyQl v6m/M4f3gZc7ogOJxoYw/UixwAX0w+y7IZmyHv4E1/wQcn8gyEQ From: "brian m. carlson" To: Cc: Jeff King , Duy Nguyen , Johannes Schindelin , Junio C Hamano , Johannes Sixt , =?utf-8?b?w4Z2YXIgQXJuZmrDtnLDsCBCamFybWFzb24=?= , Phillip Wood , Jonathan Nieder Subject: [PATCH v2 3/7] rebase: add support for multiple hooks Date: Tue, 14 May 2019 00:23:27 +0000 Message-Id: <20190514002332.121089-4-sandals@crustytoothpaste.net> X-Mailer: git-send-email 2.21.0.1020.gf2820cf01a In-Reply-To: <20190514002332.121089-1-sandals@crustytoothpaste.net> References: <20190514002332.121089-1-sandals@crustytoothpaste.net> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 127.0.1.1 Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Add support for multiple post-rewrite hooks, both for "git commit --amend" and "git rebase". Unify the hook handling between the am-based and sequencer-based code paths. Additionally add support for multiple prepare-commit-msg hooks. Note that the prepare-commit-msg hook is not passed a set of hooks directly because these are discovered later when the code calls run_hooks_le. Signed-off-by: brian m. carlson --- builtin/am.c | 20 +--------- sequencer.c | 59 ++++++++++++++++++------------ sequencer.h | 2 + t/t5407-post-rewrite-hook.sh | 15 ++++++++ t/t7505-prepare-commit-msg-hook.sh | 9 +++++ 5 files changed, 63 insertions(+), 42 deletions(-) diff --git a/builtin/am.c b/builtin/am.c index 912d9821b1..340eacbd44 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -441,24 +441,8 @@ static int run_applypatch_msg_hook(struct am_state *state) */ static int run_post_rewrite_hook(const struct am_state *state) { - struct child_process cp = CHILD_PROCESS_INIT; - const char *hook = find_hook("post-rewrite"); - int ret; - - if (!hook) - return 0; - - argv_array_push(&cp.args, hook); - argv_array_push(&cp.args, "rebase"); - - cp.in = xopen(am_path(state, "rewritten"), O_RDONLY); - cp.stdout_to_stderr = 1; - cp.trace2_hook_name = "post-rewrite"; - - ret = run_command(&cp); - - close(cp.in); - return ret; + return for_each_hook("post-rewrite", post_rewrite_rebase_hook, + (void *)am_path(state, "rewritten")); } /** diff --git a/sequencer.c b/sequencer.c index 546f281898..b899209f76 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1084,30 +1084,32 @@ int update_head_with_reflog(const struct commit *old_head, return ret; } -static int run_rewrite_hook(const struct object_id *oldoid, - const struct object_id *newoid) +struct rewrite_hook_data { + const struct object_id *oldoid; + const struct object_id *newoid; +}; + +static int do_run_rewrite_hook(const char *name, const char *path, void *p) { + struct rewrite_hook_data *data = p; struct child_process proc = CHILD_PROCESS_INIT; + struct strbuf sb = STRBUF_INIT; const char *argv[3]; int code; - struct strbuf sb = STRBUF_INIT; - - argv[0] = find_hook("post-rewrite"); - if (!argv[0]) - return 0; + argv[0] = path; argv[1] = "amend"; argv[2] = NULL; proc.argv = argv; proc.in = -1; proc.stdout_to_stderr = 1; - proc.trace2_hook_name = "post-rewrite"; + proc.trace2_hook_name = name; code = start_command(&proc); if (code) return code; - strbuf_addf(&sb, "%s %s\n", oid_to_hex(oldoid), oid_to_hex(newoid)); + strbuf_addf(&sb, "%s %s\n", oid_to_hex(data->oldoid), oid_to_hex(data->newoid)); sigchain_push(SIGPIPE, SIG_IGN); write_in_full(proc.in, sb.buf, sb.len); close(proc.in); @@ -1116,6 +1118,25 @@ static int run_rewrite_hook(const struct object_id *oldoid, return finish_command(&proc); } +static int run_rewrite_hook(const struct object_id *oldoid, + const struct object_id *newoid) +{ + struct rewrite_hook_data data = { oldoid, newoid }; + return for_each_hook("post-rewrite", do_run_rewrite_hook, &data); +} + +int post_rewrite_rebase_hook(const char *name, const char *path, void *input) +{ + struct child_process child = CHILD_PROCESS_INIT; + + child.in = open(input, O_RDONLY); + child.stdout_to_stderr = 1; + child.trace2_hook_name = "post-rewrite"; + argv_array_push(&child.args, path); + argv_array_push(&child.args, "rebase"); + return run_command(&child); +} + void commit_post_rewrite(struct repository *r, const struct commit *old_head, const struct object_id *new_head) @@ -1368,7 +1389,7 @@ static int try_to_commit(struct repository *r, goto out; } - if (find_hook("prepare-commit-msg")) { + if (find_hooks("prepare-commit-msg", NULL)) { res = run_prepare_commit_msg_hook(r, msg, hook_commit); if (res) goto out; @@ -3763,8 +3784,6 @@ static int pick_commits(struct repository *r, if (!stat(rebase_path_rewritten_list(), &st) && st.st_size > 0) { struct child_process child = CHILD_PROCESS_INIT; - const char *post_rewrite_hook = - find_hook("post-rewrite"); child.in = open(rebase_path_rewritten_list(), O_RDONLY); child.git_cmd = 1; @@ -3774,18 +3793,10 @@ static int pick_commits(struct repository *r, /* we don't care if this copying failed */ run_command(&child); - if (post_rewrite_hook) { - struct child_process hook = CHILD_PROCESS_INIT; - - hook.in = open(rebase_path_rewritten_list(), - O_RDONLY); - hook.stdout_to_stderr = 1; - hook.trace2_hook_name = "post-rewrite"; - argv_array_push(&hook.args, post_rewrite_hook); - argv_array_push(&hook.args, "rebase"); - /* we don't care if this hook failed */ - run_command(&hook); - } + /* we don't care if this hook failed */ + for_each_hook("post-rewrite", + post_rewrite_rebase_hook, + (void *)rebase_path_rewritten_list()); } apply_autostash(opts); diff --git a/sequencer.h b/sequencer.h index a515ee4457..710ef5c18c 100644 --- a/sequencer.h +++ b/sequencer.h @@ -154,6 +154,8 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla unsigned autosquash, struct todo_list *todo_list); int todo_list_rearrange_squash(struct todo_list *todo_list); +int post_rewrite_rebase_hook(const char *name, const char *path, void *input); + /* * Append a signoff to the commit message in "msgbuf". The ignore_footer * parameter specifies the number of bytes at the end of msgbuf that should diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh index 7344253bfb..f8ce32fe3b 100755 --- a/t/t5407-post-rewrite-hook.sh +++ b/t/t5407-post-rewrite-hook.sh @@ -5,6 +5,7 @@ test_description='Test the post-rewrite hook.' . ./test-lib.sh +. "$TEST_DIRECTORY/lib-hooks.sh" test_expect_success 'setup' ' test_commit A foo A && @@ -263,4 +264,18 @@ test_expect_success 'git rebase -i (exec)' ' verify_hook_input ' +cmd_rebase () { + git reset --hard D && + FAKE_LINES="1 fixup 2" git rebase -i B +} + +cmd_amend () { + git reset --hard D && + echo "D new message" > newmsg && + git commit -Fnewmsg --amend +} + +test_multiple_hooks --ignore-exit-status post-rewrite cmd_rebase +test_multiple_hooks --ignore-exit-status post-rewrite cmd_amend + test_done diff --git a/t/t7505-prepare-commit-msg-hook.sh b/t/t7505-prepare-commit-msg-hook.sh index ba8bd1b514..287e13e29d 100755 --- a/t/t7505-prepare-commit-msg-hook.sh +++ b/t/t7505-prepare-commit-msg-hook.sh @@ -3,6 +3,7 @@ test_description='prepare-commit-msg hook' . ./test-lib.sh +. "$TEST_DIRECTORY/lib-hooks.sh" test_expect_success 'set up commits for rebasing' ' test_commit root && @@ -317,4 +318,12 @@ test_expect_success C_LOCALE_OUTPUT 'with failing hook (cherry-pick)' ' test $(grep -c prepare-commit-msg actual) = 1 ' +cherry_pick_command () { + git checkout -f master && + git checkout -B other b && + git cherry-pick rebase-1 +} + +test_multiple_hooks prepare-commit-msg cherry_pick_command + test_done From patchwork Tue May 14 00:23:29 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "brian m. carlson" X-Patchwork-Id: 10941883 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id D9E4D76 for ; Tue, 14 May 2019 00:24:11 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id C96E12852C for ; Tue, 14 May 2019 00:24:11 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id BD2C82853E; Tue, 14 May 2019 00:24:11 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-8.0 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 555E62852C for ; Tue, 14 May 2019 00:24:11 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726799AbfENAYK (ORCPT ); Mon, 13 May 2019 20:24:10 -0400 Received: from injection.crustytoothpaste.net ([192.241.140.119]:36446 "EHLO injection.crustytoothpaste.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726623AbfENAYJ (ORCPT ); Mon, 13 May 2019 20:24:09 -0400 Received: from genre.crustytoothpaste.net (unknown [IPv6:2001:470:b978:101:89af:9dea:d4e0:996c]) (using TLSv1.2 with cipher ECDHE-RSA-CHACHA20-POLY1305 (256/256 bits)) (No client certificate requested) by injection.crustytoothpaste.net (Postfix) with ESMTPSA id 0AEE960821; Tue, 14 May 2019 00:24:06 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=crustytoothpaste.net; s=default; t=1557793447; bh=TzUG9jK8dfwV5fOCpym/nZrYIHq63IOSoVFbdb5CNVk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From:Reply-To: Subject:Date:To:CC:Resent-Date:Resent-From:Resent-To:Resent-Cc: In-Reply-To:References:Content-Type:Content-Disposition; b=m67kzRFKetJpVoFHyN44jH02SAWrjj7BQkBfPoYlXjJgiq9a7VxN7XNfbo/SxULYJ y9eAvA7soAqFCeImuhndLsdN5Uw3aIVXpOKN6RNMdUCkY1wZJXYd+y51RnkOOlRc+O GBxrlIGUWmBcRvaIJiQSctwcZliQ7nN3EJ5MLyKjO5lbj4LcHMCKKtqKx9w9hqp7dK kU5c/R/taPlGsUIpIgP/jQPhDB/u0Pyihj5VRxG5CoPcZ0GGJv/yE+BeXhNQF+C681 DbJOlL20qANoejJEUVch9R5nBEL1eo1Z+ntsLM7tUnhrCS6yN2FCiswlPqOvGdRXlm D8cL3yYzDslIkLlxkMVzuRKLe0dSryUHWmGYz6lhFGiX2H2+XtUmpKr2gkhleln30c 7B4jGkzYM1mC9RkivKWyFWIhY+N9NJbrUzW+d7xlT0DuY8xt7ffYmSjwFqM3HLdsSn 7w5io0wBCh3GKFphs1Rq3vj9RmH8tg+j7KUe1SjUlgLQ10xHa9f From: "brian m. carlson" To: Cc: Jeff King , Duy Nguyen , Johannes Schindelin , Junio C Hamano , Johannes Sixt , =?utf-8?b?w4Z2YXIgQXJuZmrDtnLDsCBCamFybWFzb24=?= , Phillip Wood , Jonathan Nieder Subject: [PATCH v2 4/7] builtin/worktree: add support for multiple post-checkout hooks Date: Tue, 14 May 2019 00:23:29 +0000 Message-Id: <20190514002332.121089-6-sandals@crustytoothpaste.net> X-Mailer: git-send-email 2.21.0.1020.gf2820cf01a In-Reply-To: <20190514002332.121089-1-sandals@crustytoothpaste.net> References: <20190514002332.121089-1-sandals@crustytoothpaste.net> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 127.0.1.1 Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Add support for multiple post-checkout hooks. We test only one possible path in the multiple hook case because the same code path is used for all checkouts and we know the hooks work from earlier assertions. Signed-off-by: brian m. carlson --- builtin/worktree.c | 44 ++++++++++++++++++++++------------- t/t5403-post-checkout-hook.sh | 8 +++++++ 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/builtin/worktree.c b/builtin/worktree.c index d2a7e2f3f1..1edcde8c84 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -262,6 +262,32 @@ static void validate_worktree_add(const char *path, const struct add_opts *opts) free_worktrees(worktrees); } +struct hook_data { + struct commit *commit; + const char *dir; +}; + +static int run_post_checkout_hook(const char *name, const char *path, void *p) +{ + struct hook_data *data = p; + struct commit *commit = data->commit; + const char *env[] = { "GIT_DIR", "GIT_WORK_TREE", NULL }; + struct child_process cp = CHILD_PROCESS_INIT; + + cp.git_cmd = 0; + cp.no_stdin = 1; + cp.stdout_to_stderr = 1; + cp.dir = data->dir; + cp.env = env; + cp.argv = NULL; + cp.trace2_hook_name = "post-checkout"; + argv_array_pushl(&cp.args, absolute_path(path), + oid_to_hex(&null_oid), + oid_to_hex(&commit->object.oid), + "1", NULL); + return run_command(&cp); +} + static int add_worktree(const char *path, const char *refname, const struct add_opts *opts) { @@ -395,22 +421,8 @@ static int add_worktree(const char *path, const char *refname, * is_junk is cleared, but do return appropriate code when hook fails. */ if (!ret && opts->checkout) { - const char *hook = find_hook("post-checkout"); - if (hook) { - const char *env[] = { "GIT_DIR", "GIT_WORK_TREE", NULL }; - cp.git_cmd = 0; - cp.no_stdin = 1; - cp.stdout_to_stderr = 1; - cp.dir = path; - cp.env = env; - cp.argv = NULL; - cp.trace2_hook_name = "post-checkout"; - argv_array_pushl(&cp.args, absolute_path(hook), - oid_to_hex(&null_oid), - oid_to_hex(&commit->object.oid), - "1", NULL); - ret = run_command(&cp); - } + struct hook_data data = { commit, path }; + ret = for_each_hook("post-checkout", run_post_checkout_hook, &data); } argv_array_clear(&child_env); diff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh index a39b3b5c78..aa265ce610 100755 --- a/t/t5403-post-checkout-hook.sh +++ b/t/t5403-post-checkout-hook.sh @@ -5,6 +5,7 @@ test_description='Test the post-checkout hook.' . ./test-lib.sh +. "$TEST_DIRECTORY/lib-hooks.sh" test_expect_success setup ' mkdir -p .git/hooks && @@ -73,4 +74,11 @@ test_expect_success 'post-checkout hook is triggered by clone' ' test -f clone3/.git/post-checkout.args ' +cmd_rebase () { + git checkout -B hook-test rebase-on-me^ && + git rebase rebase-on-me +} + +test_multiple_hooks post-checkout cmd_rebase + test_done From patchwork Tue May 14 00:23:30 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "brian m. carlson" X-Patchwork-Id: 10941885 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 38C58933 for ; Tue, 14 May 2019 00:24:13 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 2810F2852C for ; Tue, 14 May 2019 00:24:13 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 1BB292853E; Tue, 14 May 2019 00:24:13 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-8.0 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 9B5DA2852C for ; Tue, 14 May 2019 00:24:12 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726805AbfENAYL (ORCPT ); Mon, 13 May 2019 20:24:11 -0400 Received: from injection.crustytoothpaste.net ([192.241.140.119]:36456 "EHLO injection.crustytoothpaste.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726780AbfENAYL (ORCPT ); Mon, 13 May 2019 20:24:11 -0400 Received: from genre.crustytoothpaste.net (unknown [IPv6:2001:470:b978:101:89af:9dea:d4e0:996c]) (using TLSv1.2 with cipher ECDHE-RSA-CHACHA20-POLY1305 (256/256 bits)) (No client certificate requested) by injection.crustytoothpaste.net (Postfix) with ESMTPSA id 219B060823; Tue, 14 May 2019 00:24:08 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=crustytoothpaste.net; s=default; t=1557793448; bh=dOUWA6YEzh0vTAMKFMBd1HY3+nW1f7nKelNZ14Tw4fQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From:Reply-To: Subject:Date:To:CC:Resent-Date:Resent-From:Resent-To:Resent-Cc: In-Reply-To:References:Content-Type:Content-Disposition; b=PyRY3du/iVhySbIH/1xVAqYuMbQnYcpz0gZ10qLcDwL1zS6sl4Thxn2gvDxDimywe g2F8JyD0a2gcbeNL9L+Dm6XbQFxhBga2s44a0jyt3injXW4no6E3t/atTaZvZGbyaL kReT9pTlGkzQp6gJ68f+cTXFbkp86UymAftE/xbD45lZsJic3J/1ldKkZOdRleeHqr h/vEw8Q8kOZgmxOmZh6nBo0XlnJ8Cyxl0ng53dbMI162YAL1pJGnF2tpJu6vQ8oHqK ZYEpiOq12njEDIjmmTmZ9h6NqCR0EIhmIKnD1TI4VOCejhSK0ex/fE6H3YgICiCryP qSCMddf/dhbWHGhyw8RCXAqyBPhP5z4mUUPnwJesHY9LPt6okREsHr8dpH1WD3E9hD I27TCC8bOweY80WUS3hfBbTcc+/+OkQ7qEcPHspF3011ocf1J6lMupa9x6EVbg6KKR PYyEAJ9q3aO1oyRKLCsIL/iRsVBrzahXLuX1Al0bBTLTeQFaRHb From: "brian m. carlson" To: Cc: Jeff King , Duy Nguyen , Johannes Schindelin , Junio C Hamano , Johannes Sixt , =?utf-8?b?w4Z2YXIgQXJuZmrDtnLDsCBCamFybWFzb24=?= , Phillip Wood , Jonathan Nieder Subject: [PATCH v2 5/7] transport: add support for multiple pre-push hooks Date: Tue, 14 May 2019 00:23:30 +0000 Message-Id: <20190514002332.121089-7-sandals@crustytoothpaste.net> X-Mailer: git-send-email 2.21.0.1020.gf2820cf01a In-Reply-To: <20190514002332.121089-1-sandals@crustytoothpaste.net> References: <20190514002332.121089-1-sandals@crustytoothpaste.net> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 127.0.1.1 Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Add support for multiple pre-push hooks. Remove find_hook since there are no longer any callers of it. Signed-off-by: brian m. carlson --- run-command.c | 12 ------------ run-command.h | 6 ------ t/t5571-pre-push-hook.sh | 19 +++++++++++++++++++ transport.c | 29 +++++++++++++++++++---------- 4 files changed, 38 insertions(+), 28 deletions(-) diff --git a/run-command.c b/run-command.c index eb075ac86b..191d6f6f7e 100644 --- a/run-command.c +++ b/run-command.c @@ -1346,18 +1346,6 @@ static int has_hook(struct strbuf *path, int strip, int check) return 1; } -const char *find_hook(const char *name) -{ - static struct strbuf path = STRBUF_INIT; - - strbuf_reset(&path); - strbuf_git_path(&path, "hooks/%s", name); - if (has_hook(&path, 1, X_OK)) { - return path.buf; - } - return NULL; -} - int find_hooks(const char *name, struct string_list *list) { struct strbuf path = STRBUF_INIT; diff --git a/run-command.h b/run-command.h index 1b3677fcac..15974e26d4 100644 --- a/run-command.h +++ b/run-command.h @@ -63,12 +63,6 @@ int finish_command(struct child_process *); int finish_command_in_signal(struct child_process *); int run_command(struct child_process *); -/* - * Returns the path to the hook file, or NULL if the hook is missing - * or disabled. Note that this points to static storage that will be - * overwritten by further calls to find_hook and run_hook_*. - */ -extern const char *find_hook(const char *name); /* * Returns the paths to all hook files, or NULL if all hooks are missing or * disabled. diff --git a/t/t5571-pre-push-hook.sh b/t/t5571-pre-push-hook.sh index ac53d63869..754ad8eb93 100755 --- a/t/t5571-pre-push-hook.sh +++ b/t/t5571-pre-push-hook.sh @@ -2,6 +2,7 @@ test_description='check pre-push hooks' . ./test-lib.sh +. "$TEST_DIRECTORY/lib-hooks.sh" # Setup hook that always succeeds HOOKDIR="$(git rev-parse --git-dir)/hooks" @@ -125,4 +126,22 @@ test_expect_success 'sigpipe does not cause pre-push hook failure' ' git push parent1 "refs/heads/b/*:refs/heads/b/*" ' +push_command () { + test_commit "$1" && + git push hooks refs/heads/master:refs/heads/master +} + +push_no_verify_command () { + test_commit "$1" && + git push --no-verify hooks refs/heads/master:refs/heads/master +} + +test_expect_success 'setup' ' + git checkout master && + git init --bare hooktest && + git remote add hooks hooktest +' + +test_multiple_hooks pre-push push_command push_no_verify_command + test_done diff --git a/transport.c b/transport.c index 365ea574c7..7672f4fb57 100644 --- a/transport.c +++ b/transport.c @@ -1042,20 +1042,23 @@ static void die_with_unpushed_submodules(struct string_list *needs_pushing) die(_("Aborting.")); } -static int run_pre_push_hook(struct transport *transport, - struct ref *remote_refs) +struct pre_push_hook_data { + struct transport *transport; + struct ref *remote_refs; +}; + +static int do_run_pre_push_hook(const char *name, const char *path, void *p) { + struct pre_push_hook_data *data = p; + struct child_process proc = CHILD_PROCESS_INIT; int ret = 0, x; struct ref *r; - struct child_process proc = CHILD_PROCESS_INIT; struct strbuf buf; const char *argv[4]; - if (!(argv[0] = find_hook("pre-push"))) - return 0; - - argv[1] = transport->remote->name; - argv[2] = transport->url; + argv[0] = path; + argv[1] = data->transport->remote->name; + argv[2] = data->transport->url; argv[3] = NULL; proc.argv = argv; @@ -1071,7 +1074,7 @@ static int run_pre_push_hook(struct transport *transport, strbuf_init(&buf, 256); - for (r = remote_refs; r; r = r->next) { + for (r = data->remote_refs; r; r = r->next) { if (!r->peer_ref) continue; if (r->status == REF_STATUS_REJECT_NONFASTFORWARD) continue; if (r->status == REF_STATUS_REJECT_STALE) continue; @@ -1101,10 +1104,16 @@ static int run_pre_push_hook(struct transport *transport, x = finish_command(&proc); if (!ret) ret = x; - return ret; } +static int run_pre_push_hook(struct transport *transport, + struct ref *remote_refs) +{ + struct pre_push_hook_data data = { transport, remote_refs }; + return for_each_hook("pre-push", do_run_pre_push_hook, &data); +} + int transport_push(struct repository *r, struct transport *transport, struct refspec *rs, int flags, From patchwork Tue May 14 00:23:31 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "brian m. carlson" X-Patchwork-Id: 10941887 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id B111A933 for ; Tue, 14 May 2019 00:24:14 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 9FF0D2852C for ; Tue, 14 May 2019 00:24:14 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 941032853E; Tue, 14 May 2019 00:24:14 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-8.0 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id DA27A2852C for ; Tue, 14 May 2019 00:24:13 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726813AbfENAYN (ORCPT ); Mon, 13 May 2019 20:24:13 -0400 Received: from injection.crustytoothpaste.net ([192.241.140.119]:36456 "EHLO injection.crustytoothpaste.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726804AbfENAYM (ORCPT ); Mon, 13 May 2019 20:24:12 -0400 Received: from genre.crustytoothpaste.net (unknown [IPv6:2001:470:b978:101:89af:9dea:d4e0:996c]) (using TLSv1.2 with cipher ECDHE-RSA-CHACHA20-POLY1305 (256/256 bits)) (No client certificate requested) by injection.crustytoothpaste.net (Postfix) with ESMTPSA id C61F360822; Tue, 14 May 2019 00:24:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=crustytoothpaste.net; s=default; t=1557793450; bh=TlbC/tVQ/AUhI66bSwUfO41yMQ4vF6rZIkXKBfC6+Mw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From:Reply-To: Subject:Date:To:CC:Resent-Date:Resent-From:Resent-To:Resent-Cc: In-Reply-To:References:Content-Type:Content-Disposition; b=exyF8xc+c0jfuIcwiANChnGCVPy5uiK0qARC9+/uYm+61e3Ox+eVuEfK+fcERHgad V9RqbxxZ3d9CfDC1lJJ+O4mdy/WBvSahAFqJqWyc2MDyCs1nwOUdEII1qo8+fYTPTZ Oq+sXCZoGdVsTCmKsDexNXYfLSyWB2G8hN9iUgZj6AY2rUOriA6wuEogol7l9t0YW7 JgjrPJFsLoWQauTJO0t7iVxlkTZL6f+BuYAiOzGSClgljA+XuBc/ueWhQQBAJAyTFZ O2tCCA8FCHeuzeD9slNJTTakObtjY5iVXdlZ0MbCPq66jRS9Nt2iCrGrZGvwvqQNW4 20tcGnztvQrSEeH3hEHSdXeLYB79kYVD8ncdVXdwGfe2SougpQBaET6bQJX8nd5DhH 9q1DyTrZWHJTXwmi/2l9yJYZA6YYhUCc1T8bd7ZWJpwYNhXqn1Xp8ydC2OAa1Pcfbc gL681gp1FPhLB+BdEj9usJvt+xqm3ahbyNfZePlJevFuPOXtD59 From: "brian m. carlson" To: Cc: Jeff King , Duy Nguyen , Johannes Schindelin , Junio C Hamano , Johannes Sixt , =?utf-8?b?w4Z2YXIgQXJuZmrDtnLDsCBCamFybWFzb24=?= , Phillip Wood , Jonathan Nieder Subject: [PATCH v2 6/7] config: allow configuration of multiple hook error behavior Date: Tue, 14 May 2019 00:23:31 +0000 Message-Id: <20190514002332.121089-8-sandals@crustytoothpaste.net> X-Mailer: git-send-email 2.21.0.1020.gf2820cf01a In-Reply-To: <20190514002332.121089-1-sandals@crustytoothpaste.net> References: <20190514002332.121089-1-sandals@crustytoothpaste.net> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 127.0.1.1 Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP There are a variety of situations in which a user may want an error behavior for multiple hooks other than the default. Add a config option, hook..errorBehavior to allow users to customize this behavior on a per-hook basis. Provide options for the default behavior (exiting early), executing all hooks and succeeding if all hooks succeed, or executing all hooks and succeeding if any hook succeeds. Signed-off-by: brian m. carlson --- config.c | 27 +++++++++++++ run-command.c | 42 +++++++++++++++++--- run-command.h | 5 +++ t/lib-hooks.sh | 106 ++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 173 insertions(+), 7 deletions(-) diff --git a/config.c b/config.c index c2846df3f1..9cba4061a9 100644 --- a/config.c +++ b/config.c @@ -19,6 +19,7 @@ #include "utf8.h" #include "dir.h" #include "color.h" +#include "run-command.h" struct config_source { struct config_source *prev; @@ -1093,6 +1094,29 @@ int git_config_color(char *dest, const char *var, const char *value) return 0; } +static int git_default_hook_config(const char *key, const char *value) +{ + const char *hook; + size_t key_len; + uintptr_t behavior; + + key += strlen("hook."); + if (strip_suffix(key, ".errorbehavior", &key_len)) { + hook = xmemdupz(key, key_len); + if (!strcmp(value, "stop-on-first")) + behavior = HOOK_ERROR_STOP_ON_FIRST; + else if (!strcmp(value, "report-any-error")) + behavior = HOOK_ERROR_REPORT_ANY_ERROR; + else if (!strcmp(value, "report-any-success")) + behavior = HOOK_ERROR_REPORT_ANY_SUCCESS; + else + die(_("invalid mode for hook %s error behavior: %s"), hook, value); + string_list_insert(&hook_error_behavior, hook)->util = (void *)behavior; + return 0; + } + return 0; +} + static int git_default_core_config(const char *var, const char *value, void *cb) { /* This needs a better name */ @@ -1450,6 +1474,9 @@ int git_default_config(const char *var, const char *value, void *cb) starts_with(var, "committer.")) return git_ident_config(var, value, cb); + if (starts_with(var, "hook.")) + return git_default_hook_config(var, value); + if (starts_with(var, "i18n.")) return git_default_i18n_config(var, value); diff --git a/run-command.c b/run-command.c index 191d6f6f7e..70fb19a55b 100644 --- a/run-command.c +++ b/run-command.c @@ -1308,6 +1308,8 @@ int async_with_fork(void) #endif } +struct string_list hook_error_behavior = STRING_LIST_INIT_NODUP; + /* * Return 1 if a hook exists at path (which may be modified) using access(2) * with check (which should be F_OK or X_OK), 0 otherwise. If strip is true, @@ -1401,18 +1403,48 @@ int for_each_hook(const char *name, void *data) { struct string_list paths = STRING_LIST_INIT_DUP; - int i, ret = 0; + int i, hret = 0; + uintptr_t behavior = HOOK_ERROR_STOP_ON_FIRST; + struct string_list_item *item; + /* Use -2 as sentinel because failure to exec is -1. */ + int ret = -2; + + item = string_list_lookup(&hook_error_behavior, name); + if (item) + behavior = (uintptr_t)item->util; find_hooks(name, &paths); for (i = 0; i < paths.nr; i++) { const char *p = paths.items[i].string; - ret = handler(name, p, data); - if (ret) - break; + hret = handler(name, p, data); + switch (behavior) { + case HOOK_ERROR_STOP_ON_FIRST: + if (hret) { + ret = hret; + goto out; + } + break; + case HOOK_ERROR_REPORT_ANY_SUCCESS: + if (ret == -2) + ret = 1; + if (!hret) + ret = 0; + break; + case HOOK_ERROR_REPORT_ANY_ERROR: + if (ret == -2) + ret = 0; + if (hret) + ret = hret; + break; + default: + BUG("unknown hook error behavior"); + } } - +out: string_list_clear(&paths, 0); + if (ret == -2) + return 0; return ret; } diff --git a/run-command.h b/run-command.h index 15974e26d4..879ebb768f 100644 --- a/run-command.h +++ b/run-command.h @@ -63,6 +63,11 @@ int finish_command(struct child_process *); int finish_command_in_signal(struct child_process *); int run_command(struct child_process *); +#define HOOK_ERROR_STOP_ON_FIRST 1 +#define HOOK_ERROR_REPORT_ANY_ERROR 2 +#define HOOK_ERROR_REPORT_ANY_SUCCESS 3 +extern struct string_list hook_error_behavior; + /* * Returns the paths to all hook files, or NULL if all hooks are missing or * disabled. diff --git a/t/lib-hooks.sh b/t/lib-hooks.sh index 721250aea0..c1d7688313 100644 --- a/t/lib-hooks.sh +++ b/t/lib-hooks.sh @@ -121,7 +121,7 @@ test_multiple_hooks () { ! test -f "$OUTPUTDIR/c" ' - test_expect_success "$hook: multiple hooks, all successful" ' + test_expect_success "$hook: multiple hooks, all successful by default" ' test_when_finished "rm -fr \"$OUTPUTDIR\"" && rm -f "$HOOK" && create_multihooks 0 0 0 && @@ -131,7 +131,40 @@ test_multiple_hooks () { test -f "$OUTPUTDIR/c" ' - test_expect_success "$hook: hooks after first failure not executed" ' + test_expect_success "$hook: multiple hooks, all successful with stop-on-first" ' + test_when_finished "rm -fr \"$OUTPUTDIR\"" && + test_config "hook.$hook.errorbehavior" stop-on-first && + rm -f "$HOOK" && + create_multihooks 0 0 0 && + $cmd content-stop-on-first && + test -f "$OUTPUTDIR/a" && + test -f "$OUTPUTDIR/b" && + test -f "$OUTPUTDIR/c" + ' + + test_expect_success "$hook: multiple hooks, all successful with report-any-error" ' + test_when_finished "rm -fr \"$OUTPUTDIR\"" && + test_config "hook.$hook.errorbehavior" report-any-error && + rm -f "$HOOK" && + create_multihooks 0 0 0 && + $cmd content-report-any-error && + test -f "$OUTPUTDIR/a" && + test -f "$OUTPUTDIR/b" && + test -f "$OUTPUTDIR/c" + ' + + test_expect_success "$hook: multiple hooks, all successful with report-any-success" ' + test_when_finished "rm -fr \"$OUTPUTDIR\"" && + test_config "hook.$hook.errorbehavior" report-any-success && + rm -f "$HOOK" && + create_multihooks 0 0 0 && + $cmd content-report-any-success && + test -f "$OUTPUTDIR/a" && + test -f "$OUTPUTDIR/b" && + test -f "$OUTPUTDIR/c" + ' + + test_expect_success "$hook: hooks after first failure not executed by default" ' test_when_finished "rm -fr \"$OUTPUTDIR\"" && create_multihooks 0 1 0 && $must_fail $cmd more-content && @@ -140,6 +173,75 @@ test_multiple_hooks () { ! test -f "$OUTPUTDIR/c" ' + test_expect_success "$hook: hooks after first failure not executed with stop-on-first" ' + test_when_finished "rm -fr \"$OUTPUTDIR\"" && + test_config "hook.$hook.errorbehavior" stop-on-first && + create_multihooks 0 1 0 && + $must_fail $cmd more-content-stop-on-first && + test -f "$OUTPUTDIR/a" && + test -f "$OUTPUTDIR/b" && + ! test -f "$OUTPUTDIR/c" + ' + + test_expect_success "$hook: hooks after first failure executed with report-any-error" ' + test_when_finished "rm -fr \"$OUTPUTDIR\"" && + test_config "hook.$hook.errorbehavior" report-any-error && + create_multihooks 0 1 0 && + $must_fail $cmd more-content-report-any-error && + test -f "$OUTPUTDIR/a" && + test -f "$OUTPUTDIR/b" && + test -f "$OUTPUTDIR/c" + ' + + test_expect_success "$hook: hooks after first failure executed with report-any-success" ' + test_when_finished "rm -fr \"$OUTPUTDIR\"" && + test_config "hook.$hook.errorbehavior" report-any-success && + create_multihooks 0 1 0 && + $cmd more-content-report-any-success && + test -f "$OUTPUTDIR/a" && + test -f "$OUTPUTDIR/b" && + test -f "$OUTPUTDIR/c" + ' + + test_expect_success "$hook: failing hooks by default" ' + test_when_finished "rm -fr \"$OUTPUTDIR\"" && + create_multihooks 1 1 1 && + $must_fail $cmd most-content && + test -f "$OUTPUTDIR/a" && + ! test -f "$OUTPUTDIR/b" && + ! test -f "$OUTPUTDIR/c" + ' + + test_expect_success "$hook: failing hooks with stop-on-first" ' + test_when_finished "rm -fr \"$OUTPUTDIR\"" && + test_config "hook.$hook.errorbehavior" stop-on-first && + create_multihooks 1 1 1 && + $must_fail $cmd most-content-stop-on-first && + test -f "$OUTPUTDIR/a" && + ! test -f "$OUTPUTDIR/b" && + ! test -f "$OUTPUTDIR/c" + ' + + test_expect_success "$hook: failing hooks with report-any-error" ' + test_when_finished "rm -fr \"$OUTPUTDIR\"" && + test_config "hook.$hook.errorbehavior" report-any-error && + create_multihooks 1 1 1 && + $must_fail $cmd most-content-report-any-error && + test -f "$OUTPUTDIR/a" && + test -f "$OUTPUTDIR/b" && + test -f "$OUTPUTDIR/c" + ' + + test_expect_success "$hook: failing hooks with report-any-success" ' + test_when_finished "rm -fr \"$OUTPUTDIR\"" && + test_config "hook.$hook.errorbehavior" report-any-success && + create_multihooks 1 1 1 && + $must_fail $cmd most-content-report-any-success && + test -f "$OUTPUTDIR/a" && + test -f "$OUTPUTDIR/b" && + test -f "$OUTPUTDIR/c" + ' + test_expect_success POSIXPERM "$hook: non-executable hook not executed" ' test_when_finished "rm -fr \"$OUTPUTDIR\"" && create_multihooks 0 1 0 && From patchwork Tue May 14 00:23:32 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "brian m. carlson" X-Patchwork-Id: 10941889 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 34B4A76 for ; Tue, 14 May 2019 00:24:16 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 233BE28535 for ; Tue, 14 May 2019 00:24:16 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 0F9E82852C; Tue, 14 May 2019 00:24:16 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-8.0 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 97BEA2852C for ; Tue, 14 May 2019 00:24:15 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726821AbfENAYO (ORCPT ); Mon, 13 May 2019 20:24:14 -0400 Received: from injection.crustytoothpaste.net ([192.241.140.119]:36474 "EHLO injection.crustytoothpaste.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726811AbfENAYO (ORCPT ); Mon, 13 May 2019 20:24:14 -0400 Received: from genre.crustytoothpaste.net (unknown [IPv6:2001:470:b978:101:89af:9dea:d4e0:996c]) (using TLSv1.2 with cipher ECDHE-RSA-CHACHA20-POLY1305 (256/256 bits)) (No client certificate requested) by injection.crustytoothpaste.net (Postfix) with ESMTPSA id 2302960825; Tue, 14 May 2019 00:24:11 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=crustytoothpaste.net; s=default; t=1557793451; bh=tXkwZBie4yZ08BeN/2OOShqFaVZ0FAhWy+7epQChYlM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From:Reply-To: Subject:Date:To:CC:Resent-Date:Resent-From:Resent-To:Resent-Cc: In-Reply-To:References:Content-Type:Content-Disposition; b=oCbG0Ta2moYhRXAZqGgyjfm/KFlMO6YlIMuW9uoQl4vKfBv2mlTcxTjk3zklIQ3xG 2rvyjpmglIgyNWqYGv6oJT54sXDS50VeVrdpjyX5DSFOnfBq+p/i2eN5V2XIYNpboz nncM4qzvPrhd6qLs3aoTyK5wA1/GvcoqyJdLMMksQ8t+Cgky9MoOxE/DD3EH8Sw+xB xFaKNfCUXNdIwVYoDgzvSXLE+CUL57ZpSEp8i82+O6aXEzDpGOP5ykqAzpBxfVyaXz 84nCPUHM+/5P1gmJgfLgh5avGm5d3zMN5/x9kR/VyQUHE4Tlf88RGC3exA+fQ9rHDZ d87I2pvAESEJwLQt8qNop6zsTiQOjgV+DLtGfwmb0pmAOBcLoikhkAqIIa+FJKvd1T u6ecBv2fqne6dV5ubl7+lpCzT6U3do2BUu/aNTznqh1ODW33aeLcV5MliH7PXagYB7 /iRfjj7w1xX3x34qvAsXSAXrHZMS22kmf+zcQlvYke4bjsiECh3 From: "brian m. carlson" To: Cc: Jeff King , Duy Nguyen , Johannes Schindelin , Junio C Hamano , Johannes Sixt , =?utf-8?b?w4Z2YXIgQXJuZmrDtnLDsCBCamFybWFzb24=?= , Phillip Wood , Jonathan Nieder Subject: [PATCH v2 7/7] docs: document multiple hooks Date: Tue, 14 May 2019 00:23:32 +0000 Message-Id: <20190514002332.121089-9-sandals@crustytoothpaste.net> X-Mailer: git-send-email 2.21.0.1020.gf2820cf01a In-Reply-To: <20190514002332.121089-1-sandals@crustytoothpaste.net> References: <20190514002332.121089-1-sandals@crustytoothpaste.net> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 127.0.1.1 Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Document the semantics and behavior of multiple hooks, including the configuration options and requirements for them to be run. Signed-off-by: brian m. carlson --- Documentation/config.txt | 2 ++ Documentation/config/hook.txt | 19 +++++++++++++++++++ Documentation/githooks.txt | 9 +++++++++ 3 files changed, 30 insertions(+) create mode 100644 Documentation/config/hook.txt diff --git a/Documentation/config.txt b/Documentation/config.txt index d87846faa6..f62b8ce494 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -350,6 +350,8 @@ include::config/guitool.txt[] include::config/help.txt[] +include::config/hook.txt[] + include::config/http.txt[] include::config/i18n.txt[] diff --git a/Documentation/config/hook.txt b/Documentation/config/hook.txt new file mode 100644 index 0000000000..4585cc7f55 --- /dev/null +++ b/Documentation/config/hook.txt @@ -0,0 +1,19 @@ +hook..errorBehavior:: + Control the error behavior when using multiple hooks. ++ +-- +* `stop-on-first` - If a hook fails, do not execute further hooks, even if the + command normally ignores whether hooks succeed or fail, and return its exit + code as the exit code of the hook set. If all hooks succeed, the exit code is 0. + This is the default. +* `report-any-error` - Always execute all hooks, but return the exit code of the + first failing hook as the exit code of the hook set. If all hooks succeed, the + exit code of the hook set is 0. +* `report-any-success` - Always execute all hooks, and if any hook succeeds, + return 0 as the exit code of the hook set. If all hooks fail, the exit code of + the hook set is 1. +-- ++ +If the exit code of the hook set is zero, then the hooks are considered to have +succeeded; otherwise, they are considered to have failed. Note that the success +or failure of some hooks is ignored (see linkgit:githooks[5] for more). diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index 786e778ab8..c680e575b3 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -31,6 +31,15 @@ Hooks can get their arguments via the environment, command-line arguments, and stdin. See the documentation for each hook below for details. +It is possible to provide multiple hooks for a single function. If the +main hook file is absent, hooks are additionally looked for in a +directory with the name of the main hook file with a `.d` appended. +(That is, if `post-receive` is missing, `post-receive.d` is inspected +for any hooks that might be present.) Each of these hooks is executed in order, +sorted by file name. By default, if a hook fails, additional hooks are not +executed, but this can be controlled with the `hook.*.errorBehavior` setting +(see linkgit:git-config[1]). + `git init` may copy hooks to the new repository, depending on its configuration. See the "TEMPLATE DIRECTORY" section in linkgit:git-init[1] for details. When the rest of this document refers