From patchwork Thu Aug 12 00:42:53 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Emily Shaffer X-Patchwork-Id: 12432127 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-26.3 required=3.0 tests=BAYES_00,DKIMWL_WL_MED, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER,INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS, USER_AGENT_GIT,USER_IN_DEF_DKIM_WL autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 62E04C4338F for ; Thu, 12 Aug 2021 00:43:17 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 3FE9F60F35 for ; Thu, 12 Aug 2021 00:43:17 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233127AbhHLAnk (ORCPT ); Wed, 11 Aug 2021 20:43:40 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:57954 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231649AbhHLAni (ORCPT ); Wed, 11 Aug 2021 20:43:38 -0400 Received: from mail-qt1-x84a.google.com (mail-qt1-x84a.google.com [IPv6:2607:f8b0:4864:20::84a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 29186C061765 for ; Wed, 11 Aug 2021 17:43:14 -0700 (PDT) Received: by mail-qt1-x84a.google.com with SMTP id j3-20020ac85f830000b029029113b02ff5so2268965qta.7 for ; Wed, 11 Aug 2021 17:43:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20161025; h=date:in-reply-to:message-id:mime-version:references:subject:from:to :cc; bh=GUgf+ALJ43t6XtzIfYl21KLgyCL7rzGSjpKK4ROtw5w=; b=ChXVSNnzHRpjvQ3ANqbErY0x3SFmQbD6Etlc5XsH67beT4ZN3yUv5JHF/USb8Mk0sM 40TMNMLXVLAQdQVlgDglbNll1wQhkyhSQ/BAuv5HLIsic1trZirgZfd3HJjmwuYXgXwX 1iA2QAiEkdtI8LC9HI32o5X1N8eAKgiUIHxO9RlMy+t8unRcpwOclHCzkHdIDTKenJdj f7TaNG4TGqbo1W4UCLpx90mgt71u8pQRa7OR+aqbUW3Egq2kBvGzbjp69dk3JfnVf2+i MqjO3HCGWmp2n0cFRQb8VXDFmi2Jx7GabcKJ99j+R4RnqJr/GN747KMQw5EkuSiVs1Vu HEcw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:in-reply-to:message-id:mime-version :references:subject:from:to:cc; bh=GUgf+ALJ43t6XtzIfYl21KLgyCL7rzGSjpKK4ROtw5w=; b=m0SnBrJ0bA2J3OAhq2qoKSATT9uCpF+dDArDrEBAoYqpkcoxEsqTiCzWp07nRTRxMV UlotEtsiUbQEyldm7jTt7+Y7LF547kGxk4fOny0V2FCvEzPPxjmDqw08x/H3ewnkruk2 0ZaW55zLA/ptYOJ74PEzs3b84js/B3a7OW3OgN8h/O/oPr+Oo+5g05yZQJTlHm1+NnXz nkwf5Kd9D54/USDvnnwvdBpmQBQGKDu33qCVNCS06PjOs7H4nOMrNRNKYKlKahc5oqU7 WKtgBI9pa9AYBKOOv5LZTvolvexaIrQvj4VYOqzEpxBKqGHM14sBtLWPVotq858lrfMf H+RQ== X-Gm-Message-State: AOAM5302H9zci9y8Bokjk53XV7kX6GNPHz78Ao03yYmQrF/SJNLRglDZ Ba0xd8Shtk9ZM3+Zs6kz4mu4Pr/sNcEnA0aLfISEkOEfnlQo84I302Ulk9xN9COqj/U5b4tNgIz aQvNO4pwxyl8QKTf3Lzpgpw3HEHg7uzJnF8z/kt7xcEzwBqq1KYrqKSPJ040U+rlfLqPTdmQ8Ow == X-Google-Smtp-Source: ABdhPJyin5+5QzWtMEO+rI0OXsiEqQ4mCgu5Qdg2uOF+OMRoMKU7HhbAnb0cTTAxuUXaxCf0fDTrWPbmZSOhFEynI9Y= X-Received: from podkayne.svl.corp.google.com ([2620:15c:2ce:200:1377:1116:63ab:bf4b]) (user=emilyshaffer job=sendgmr) by 2002:ad4:498a:: with SMTP id t10mr1386481qvx.8.1628728993285; Wed, 11 Aug 2021 17:43:13 -0700 (PDT) Date: Wed, 11 Aug 2021 17:42:53 -0700 In-Reply-To: <20210812004258.74318-1-emilyshaffer@google.com> Message-Id: <20210812004258.74318-2-emilyshaffer@google.com> Mime-Version: 1.0 References: <20210812004258.74318-1-emilyshaffer@google.com> X-Mailer: git-send-email 2.32.0.605.g8dce9f2422-goog Subject: [PATCH v2 1/6] hook: run a list of hooks instead From: Emily Shaffer To: git@vger.kernel.org Cc: Emily Shaffer Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org To prepare for multihook support, teach hook.[hc] to take a list of hooks at run_hooks and run_found_hooks. Right now the list is always one entry, but in the future we will allow users to supply more than one executable for a single hook event. Signed-off-by: Emily Shaffer --- builtin/hook.c | 14 ++++--- hook.c | 103 +++++++++++++++++++++++++++++++++++-------------- hook.h | 16 +++++++- 3 files changed, 96 insertions(+), 37 deletions(-) diff --git a/builtin/hook.c b/builtin/hook.c index 5eb7cf73a4..4d39c9e75e 100644 --- a/builtin/hook.c +++ b/builtin/hook.c @@ -25,7 +25,8 @@ static int run(int argc, const char **argv, const char *prefix) struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; int ignore_missing = 0; const char *hook_name; - const char *hook_path; + struct list_head *hooks; + struct option run_options[] = { OPT_BOOL(0, "ignore-missing", &ignore_missing, N_("exit quietly with a zero exit code if the requested hook cannot be found")), @@ -58,15 +59,16 @@ static int run(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); hook_name = argv[0]; - if (ignore_missing) - return run_hooks_oneshot(hook_name, &opt); - hook_path = find_hook(hook_name); - if (!hook_path) { + hooks = hook_list(hook_name); + if (list_empty(hooks)) { + /* ... act like run_hooks_oneshot() under --ignore-missing */ + if (ignore_missing) + return 0; error("cannot find a hook named %s", hook_name); return 1; } - ret = run_hooks(hook_name, hook_path, &opt); + ret = run_hooks(hook_name, hooks, &opt); run_hooks_opt_clear(&opt); return ret; usage: diff --git a/hook.c b/hook.c index ee20b2e365..80e150548c 100644 --- a/hook.c +++ b/hook.c @@ -4,6 +4,28 @@ #include "hook-list.h" #include "config.h" +static void free_hook(struct hook *ptr) +{ + if (ptr) { + free(ptr->feed_pipe_cb_data); + } + free(ptr); +} + +static void remove_hook(struct list_head *to_remove) +{ + struct hook *hook_to_remove = list_entry(to_remove, struct hook, list); + list_del(to_remove); + free_hook(hook_to_remove); +} + +void clear_hook_list(struct list_head *head) +{ + struct list_head *pos, *tmp; + list_for_each_safe(pos, tmp, head) + remove_hook(pos); +} + static int known_hook(const char *name) { const char **p; @@ -71,6 +93,30 @@ int hook_exists(const char *name) return !!find_hook(name); } +struct list_head* hook_list(const char* hookname) +{ + struct list_head *hook_head = xmalloc(sizeof(struct list_head)); + + INIT_LIST_HEAD(hook_head); + + if (!hookname) + return NULL; + + if (have_git_dir()) { + const char *hook_path = find_hook(hookname); + + /* Add the hook from the hookdir */ + if (hook_path) { + struct hook *to_add = xmalloc(sizeof(*to_add)); + to_add->hook_path = hook_path; + to_add->feed_pipe_cb_data = NULL; + list_add_tail(&to_add->list, hook_head); + } + } + + return hook_head; +} + void run_hooks_opt_clear(struct run_hooks_opt *o) { strvec_clear(&o->env); @@ -128,7 +174,10 @@ static int pick_next_hook(struct child_process *cp, cp->dir = hook_cb->options->dir; /* add command */ - strvec_push(&cp->args, run_me->hook_path); + if (hook_cb->options->absolute_path) + strvec_push(&cp->args, absolute_path(run_me->hook_path)); + else + strvec_push(&cp->args, run_me->hook_path); /* * add passed-in argv, without expanding - let the user get back @@ -139,12 +188,12 @@ static int pick_next_hook(struct child_process *cp, /* Provide context for errors if necessary */ *pp_task_cb = run_me; - /* - * This pick_next_hook() will be called again, we're only - * running one hook, so indicate that no more work will be - * done. - */ - hook_cb->run_me = NULL; + /* Get the next entry ready */ + if (hook_cb->run_me->list.next == hook_cb->head) + hook_cb->run_me = NULL; + else + hook_cb->run_me = list_entry(hook_cb->run_me->list.next, + struct hook, list); return 1; } @@ -179,13 +228,9 @@ static int notify_hook_finished(int result, return 0; } -int run_hooks(const char *hook_name, const char *hook_path, - struct run_hooks_opt *options) +int run_hooks(const char *hook_name, struct list_head *hooks, + struct run_hooks_opt *options) { - struct strbuf abs_path = STRBUF_INIT; - struct hook my_hook = { - .hook_path = hook_path, - }; struct hook_cb_data cb_data = { .rc = 0, .hook_name = hook_name, @@ -197,11 +242,9 @@ int run_hooks(const char *hook_name, const char *hook_path, if (!options) BUG("a struct run_hooks_opt must be provided to run_hooks"); - if (options->absolute_path) { - strbuf_add_absolute_path(&abs_path, hook_path); - my_hook.hook_path = abs_path.buf; - } - cb_data.run_me = &my_hook; + + cb_data.head = hooks; + cb_data.run_me = list_first_entry(hooks, struct hook, list); run_processes_parallel_tr2(jobs, pick_next_hook, @@ -213,18 +256,15 @@ int run_hooks(const char *hook_name, const char *hook_path, "hook", hook_name); - - if (options->absolute_path) - strbuf_release(&abs_path); - free(my_hook.feed_pipe_cb_data); + clear_hook_list(hooks); return cb_data.rc; } int run_hooks_oneshot(const char *hook_name, struct run_hooks_opt *options) { - const char *hook_path; - int ret; + struct list_head *hooks; + int ret = 0; struct run_hooks_opt hook_opt_scratch = RUN_HOOKS_OPT_INIT; if (!options) @@ -233,14 +273,19 @@ int run_hooks_oneshot(const char *hook_name, struct run_hooks_opt *options) if (options->path_to_stdin && options->feed_pipe) BUG("choose only one method to populate stdin"); - hook_path = find_hook(hook_name); - if (!hook_path) { - ret = 0; + hooks = hook_list(hook_name); + + /* + * If you need to act on a missing hook, use run_found_hooks() + * instead + */ + if (list_empty(hooks)) goto cleanup; - } - ret = run_hooks(hook_name, hook_path, options); + ret = run_hooks(hook_name, hooks, options); + cleanup: run_hooks_opt_clear(options); + clear_hook_list(hooks); return ret; } diff --git a/hook.h b/hook.h index 58dfbf474c..7705e6a529 100644 --- a/hook.h +++ b/hook.h @@ -3,6 +3,7 @@ #include "strbuf.h" #include "strvec.h" #include "run-command.h" +#include "list.h" /* * Returns the path to the hook file, or NULL if the hook is missing @@ -17,6 +18,7 @@ const char *find_hook(const char *name); int hook_exists(const char *hookname); struct hook { + struct list_head list; /* The path to the hook */ const char *hook_path; @@ -27,6 +29,12 @@ struct hook { void *feed_pipe_cb_data; }; +/* + * Provides a linked list of 'struct hook' detailing commands which should run + * in response to the 'hookname' event, in execution order. + */ +struct list_head* hook_list(const char *hookname); + struct run_hooks_opt { /* Environment vars to be set for each hook */ @@ -97,6 +105,7 @@ struct hook_cb_data { /* rc reflects the cumulative failure state */ int rc; const char *hook_name; + struct list_head *head; struct hook *run_me; struct run_hooks_opt *options; int *invoked_hook; @@ -110,8 +119,8 @@ void run_hooks_opt_clear(struct run_hooks_opt *o); * * See run_hooks_oneshot() for the simpler one-shot API. */ -int run_hooks(const char *hookname, const char *hook_path, - struct run_hooks_opt *options); +int run_hooks(const char *hookname, struct list_head *hooks, + struct run_hooks_opt *options); /** * Calls find_hook() on your "hook_name" and runs the hooks (if any) @@ -123,4 +132,7 @@ int run_hooks(const char *hookname, const char *hook_path, */ int run_hooks_oneshot(const char *hook_name, struct run_hooks_opt *options); +/* Empties the list at 'head', calling 'free_hook()' on each entry */ +void clear_hook_list(struct list_head *head); + #endif From patchwork Thu Aug 12 00:42:54 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Emily Shaffer X-Patchwork-Id: 12432129 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-26.3 required=3.0 tests=BAYES_00,DKIMWL_WL_MED, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER,INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS, URIBL_BLOCKED,USER_AGENT_GIT,USER_IN_DEF_DKIM_WL autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id E35F1C4320A for ; Thu, 12 Aug 2021 00:43:18 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id C218760FC3 for ; Thu, 12 Aug 2021 00:43:18 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233152AbhHLAnm (ORCPT ); Wed, 11 Aug 2021 20:43:42 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:57968 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231649AbhHLAnk (ORCPT ); Wed, 11 Aug 2021 20:43:40 -0400 Received: from mail-qt1-x849.google.com (mail-qt1-x849.google.com [IPv6:2607:f8b0:4864:20::849]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 892C3C061765 for ; Wed, 11 Aug 2021 17:43:16 -0700 (PDT) Received: by mail-qt1-x849.google.com with SMTP id r13-20020ac85c8d0000b029028efef0404cso2254340qta.14 for ; Wed, 11 Aug 2021 17:43:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20161025; h=date:in-reply-to:message-id:mime-version:references:subject:from:to :cc:content-transfer-encoding; bh=JtIDsVyWQ6ZoB9IXVAEzKb4E9DwVjqwDJabf0K90n/o=; b=bUOtKdPH+vMAryQCM12rc94ZsR4I3HwpalXsI/AHuRMegnPs+Y2rK7FOTu/gLzczOV 2Oe/K5iREjhTiJ1JwZjdzVlOrBtR9khWTtQJKvg7T71wY6vMtc0pyDzzfq3RfbiHAVRt er/3CMuh3nn7y/Eu3EWylTlxECKtsSz6OhHsnK/s5jajFocp0jvPqHIFdtsQodC2q2cK M+DL5ixVRLqt4btUlGNjhlJGKzf20VItgGa+WiZkKTsxzC5OCLznaJXRyuywWekO37LN VwCyvQvnHtPQjpqXCN0+NDQIzsScxvJsdT4xSroo+lVETsg/NnIAe3H95S3fqTz9la5o SnfA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:in-reply-to:message-id:mime-version :references:subject:from:to:cc:content-transfer-encoding; bh=JtIDsVyWQ6ZoB9IXVAEzKb4E9DwVjqwDJabf0K90n/o=; b=jvkGxo+HnFmuFfhxjZ1xaGMuT9oJ/dCcL6hOOJ40NC1DzAo+/pHvoorqVvnlRuKiLA rkwxajRG4YHOPkJ25/RXBikcIZVVKmBNTnpdFYbGH7c5EvpnIn+0bxZ0m6G4xH7JZi5k VCoZvIOEv6v3pE5xChAApxP0Uys4Axu0xu39FbYTjTvfHUx8RhS33fpCkxpHDzEs5I7z tW3rfPWPqQlIW7zHbWW3UYyFaTeJfg/kMCkI1xTHqPUQLAdc5RreAuwzlmPuoKfppQD1 moVPAN24A4Rc9qmJJjrhfHzqxSXSFv2MFMYtWEqKEZ+/YXNIrmv0Bmp8w9Ajk1wwoYnZ 3K3w== X-Gm-Message-State: AOAM532ssnQlMZ7TKOUk6enFuzC6W5ru2TQgWXhmY06zkSczZ7Mq53Ji kZtxag4VnOxSWJb5cuu3CpPCiEofqIox8oRBh8FfW9J25tYgorO4r/xayAdA8S2s6yyQBenBq1W uFA7ZZuOFGqY2xWWDR89GsNmz9xssScxuBglvOVngB0HsbuPC90OLWUm9pkcjkFiyyA+wT1VbOQ == X-Google-Smtp-Source: ABdhPJxwI1I106pS0eluUlHhxzrCUcIHc1UkjjoxzaMjSqo6e1LYL8b2/4ZAlGmhiAguSVRl7l99NVSjapvvUQssipc= X-Received: from podkayne.svl.corp.google.com ([2620:15c:2ce:200:1377:1116:63ab:bf4b]) (user=emilyshaffer job=sendgmr) by 2002:a0c:d801:: with SMTP id h1mr1459636qvj.60.1628728995716; Wed, 11 Aug 2021 17:43:15 -0700 (PDT) Date: Wed, 11 Aug 2021 17:42:54 -0700 In-Reply-To: <20210812004258.74318-1-emilyshaffer@google.com> Message-Id: <20210812004258.74318-3-emilyshaffer@google.com> Mime-Version: 1.0 References: <20210812004258.74318-1-emilyshaffer@google.com> X-Mailer: git-send-email 2.32.0.605.g8dce9f2422-goog Subject: [PATCH v2 2/6] hook: allow parallel hook execution From: Emily Shaffer To: git@vger.kernel.org Cc: Emily Shaffer , " =?utf-8?b?w4Z2YXIgQXJuZmo=?= =?utf-8?b?w7Zyw7AgQmphcm1hc29u?= " Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org In many cases, there's no reason not to allow hooks to execute in parallel. run_processes_parallel() is well-suited - it's a task queue that runs its housekeeping in series, which means users don't need to worry about thread safety on their callback data. True multithreaded execution with the async_* functions isn't necessary here. Synchronous hook execution can be achieved by only allowing 1 job to run at a time. Teach run_hooks() to use that function for simple hooks which don't require stdin or capture of stderr. Signed-off-by: Emily Shaffer Helped-by: Ævar Arnfjörð Bjarmason --- Documentation/config/hook.txt | 4 ++++ Documentation/git-hook.txt | 17 ++++++++++++++++- builtin/am.c | 4 ++-- builtin/checkout.c | 2 +- builtin/clone.c | 2 +- builtin/hook.c | 4 +++- builtin/merge.c | 2 +- builtin/rebase.c | 2 +- builtin/receive-pack.c | 9 +++++---- builtin/worktree.c | 2 +- commit.c | 2 +- hook.c | 36 +++++++++++++++++++++++++++++++---- hook.h | 24 ++++++++++++++++++----- read-cache.c | 2 +- refs.c | 2 +- reset.c | 3 ++- sequencer.c | 4 ++-- transport.c | 2 +- 18 files changed, 94 insertions(+), 29 deletions(-) create mode 100644 Documentation/config/hook.txt diff --git a/Documentation/config/hook.txt b/Documentation/config/hook.txt new file mode 100644 index 0000000000..96d3d6572c --- /dev/null +++ b/Documentation/config/hook.txt @@ -0,0 +1,4 @@ +hook.jobs:: + Specifies how many hooks can be run simultaneously during parallelized + hook execution. If unspecified, defaults to the number of processors on + the current system. diff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt index fa68c1f391..8bf82b5dd4 100644 --- a/Documentation/git-hook.txt +++ b/Documentation/git-hook.txt @@ -8,7 +8,8 @@ git-hook - run git hooks SYNOPSIS -------- [verse] -'git hook' run [--to-stdin=] [--ignore-missing] [-- ] +'git hook' run [--to-stdin=] [--ignore-missing] [(-j|--jobs) ] + [-- ] DESCRIPTION ----------- @@ -42,6 +43,20 @@ OPTIONS tools that want to do a blind one-shot run of a hook that may or may not be present. +-j:: +--jobs:: + Only valid for `run`. ++ +Specify how many hooks to run simultaneously. If this flag is not specified, use +the value of the `hook.jobs` config. If the config is not specified, use the +number of CPUs on the current system. Some hooks may be ineligible for +parallelization: for example, 'commit-msg' intends hooks modify the commit +message body and cannot be parallelized. + +CONFIGURATION +------------- +include::config/hook.txt[] + SEE ALSO -------- linkgit:githooks[5] diff --git a/builtin/am.c b/builtin/am.c index 9e3d4d9ab4..c24a27d6a1 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -446,7 +446,7 @@ static void am_destroy(const struct am_state *state) static int run_applypatch_msg_hook(struct am_state *state) { int ret; - struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_SYNC; assert(state->msg); strvec_push(&opt.args, am_path(state, "final-commit")); @@ -467,7 +467,7 @@ static int run_applypatch_msg_hook(struct am_state *state) */ static int run_post_rewrite_hook(const struct am_state *state) { - struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC; strvec_push(&opt.args, "rebase"); opt.path_to_stdin = am_path(state, "rewritten"); diff --git a/builtin/checkout.c b/builtin/checkout.c index 6d69b4c011..27166c0bb8 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -107,7 +107,7 @@ struct branch_info { static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit, int changed) { - struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_SYNC; /* "new_commit" can be NULL when checking out from the index before a commit exists. */ diff --git a/builtin/clone.c b/builtin/clone.c index 27fc05ee51..599e7a7936 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -776,7 +776,7 @@ static int checkout(int submodule_progress) struct tree *tree; struct tree_desc t; int err = 0; - struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT; + struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT_SYNC; if (option_no_checkout) return 0; diff --git a/builtin/hook.c b/builtin/hook.c index 4d39c9e75e..12c9126032 100644 --- a/builtin/hook.c +++ b/builtin/hook.c @@ -22,7 +22,7 @@ static const char * const builtin_hook_run_usage[] = { static int run(int argc, const char **argv, const char *prefix) { int i; - struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_SYNC; int ignore_missing = 0; const char *hook_name; struct list_head *hooks; @@ -32,6 +32,8 @@ static int run(int argc, const char **argv, const char *prefix) N_("exit quietly with a zero exit code if the requested hook cannot be found")), OPT_STRING(0, "to-stdin", &opt.path_to_stdin, N_("path"), N_("file to read into hooks' stdin")), + OPT_INTEGER('j', "jobs", &opt.jobs, + N_("run up to hooks simultaneously")), OPT_END(), }; int ret; diff --git a/builtin/merge.c b/builtin/merge.c index 9bd4a2532c..c749c382c3 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -448,7 +448,7 @@ static void finish(struct commit *head_commit, const struct object_id *new_head, const char *msg) { struct strbuf reflog_message = STRBUF_INIT; - struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC; const struct object_id *head = &head_commit->object.oid; if (!msg) diff --git a/builtin/rebase.c b/builtin/rebase.c index e7c668c99b..fecf248ed9 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1314,7 +1314,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) char *squash_onto_name = NULL; int reschedule_failed_exec = -1; int allow_preemptive_ff = 1; - struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT; + struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT_ASYNC; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index ebec6f3bb1..b32dcc9000 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -909,7 +909,7 @@ static int run_receive_hook(struct command *commands, int skip_broken, const struct string_list *push_options) { - struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC; struct receive_hook_feed_context ctx; struct command *iter = commands; @@ -948,7 +948,7 @@ static int run_receive_hook(struct command *commands, static int run_update_hook(struct command *cmd) { - struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC; strvec_pushl(&opt.args, cmd->ref_name, @@ -1432,7 +1432,8 @@ static const char *push_to_checkout(unsigned char *hash, struct strvec *env, const char *work_tree) { - struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_SYNC; + opt.invoked_hook = invoked_hook; strvec_pushf(env, "GIT_WORK_TREE=%s", absolute_path(work_tree)); @@ -1628,7 +1629,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) static void run_update_post_hook(struct command *commands) { struct command *cmd; - struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC; for (cmd = commands; cmd; cmd = cmd->next) { if (cmd->error_string || cmd->did_not_exist) diff --git a/builtin/worktree.c b/builtin/worktree.c index 330867c19b..efead564c1 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -382,7 +382,7 @@ 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) { - struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_SYNC; strvec_pushl(&opt.env, "GIT_DIR", "GIT_WORK_TREE", NULL); strvec_pushl(&opt.args, diff --git a/commit.c b/commit.c index 842e47beae..0e6e5a5b27 100644 --- a/commit.c +++ b/commit.c @@ -1700,7 +1700,7 @@ int run_commit_hook(int editor_is_used, const char *index_file, int *invoked_hook, const char *name, ...) { - struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_SYNC; va_list args; const char *arg; diff --git a/hook.c b/hook.c index 80e150548c..37f682c6d8 100644 --- a/hook.c +++ b/hook.c @@ -228,6 +228,28 @@ static int notify_hook_finished(int result, return 0; } +/* + * Determines how many jobs to use after we know we want to parallelize. First + * priority is the config 'hook.jobs' and second priority is the number of CPUs. + */ +static int configured_hook_jobs(void) +{ + /* + * The config and the CPU count probably won't change during the process + * lifetime, so cache the result in case we invoke multiple hooks during + * one process. + */ + static int jobs = 0; + if (jobs) + return jobs; + + if (git_config_get_int("hook.jobs", &jobs)) + /* if the config isn't set, fall back to CPU count. */ + jobs = online_cpus(); + + return jobs; +} + int run_hooks(const char *hook_name, struct list_head *hooks, struct run_hooks_opt *options) { @@ -237,16 +259,18 @@ int run_hooks(const char *hook_name, struct list_head *hooks, .options = options, .invoked_hook = options->invoked_hook, }; - int jobs = 1; if (!options) BUG("a struct run_hooks_opt must be provided to run_hooks"); - cb_data.head = hooks; cb_data.run_me = list_first_entry(hooks, struct hook, list); - run_processes_parallel_tr2(jobs, + /* INIT_ASYNC sets jobs to 0, so go look up how many to use. */ + if (!options->jobs) + options->jobs = configured_hook_jobs(); + + run_processes_parallel_tr2(options->jobs, pick_next_hook, notify_start_failure, options->feed_pipe, @@ -265,7 +289,11 @@ int run_hooks_oneshot(const char *hook_name, struct run_hooks_opt *options) { struct list_head *hooks; int ret = 0; - struct run_hooks_opt hook_opt_scratch = RUN_HOOKS_OPT_INIT; + /* + * Turn on parallelism by default. Hooks which don't want it should + * specify their options accordingly. + */ + struct run_hooks_opt hook_opt_scratch = RUN_HOOKS_OPT_INIT_ASYNC; if (!options) options = &hook_opt_scratch; diff --git a/hook.h b/hook.h index 7705e6a529..4f90228a0c 100644 --- a/hook.h +++ b/hook.h @@ -43,6 +43,13 @@ struct run_hooks_opt /* Args to be passed to each hook */ struct strvec args; + /* + * Number of threads to parallelize across. Set to 0 to use the + * 'hook.jobs' config or, if that config is unset, the number of cores + * on the system. + */ + int jobs; + /* Resolve and run the "absolute_path(hook)" instead of * "hook". Used for "git worktree" hooks */ @@ -85,11 +92,6 @@ struct run_hooks_opt int *invoked_hook; }; -#define RUN_HOOKS_OPT_INIT { \ - .env = STRVEC_INIT, \ - .args = STRVEC_INIT, \ -} - /* * To specify a 'struct string_list', set 'run_hooks_opt.feed_pipe_ctx' to the * string_list and set 'run_hooks_opt.feed_pipe' to 'pipe_from_string_list()'. @@ -111,6 +113,18 @@ struct hook_cb_data { int *invoked_hook; }; +#define RUN_HOOKS_OPT_INIT_SYNC { \ + .jobs = 1, \ + .env = STRVEC_INIT, \ + .args = STRVEC_INIT, \ +} + +#define RUN_HOOKS_OPT_INIT_ASYNC { \ + .jobs = 0, \ + .env = STRVEC_INIT, \ + .args = STRVEC_INIT, \ +} + void run_hooks_opt_clear(struct run_hooks_opt *o); /** diff --git a/read-cache.c b/read-cache.c index 90099ca14d..fd2bc67667 100644 --- a/read-cache.c +++ b/read-cache.c @@ -3068,7 +3068,7 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l { int ret; int was_full = !istate->sparse_index; - struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT; + struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT_ASYNC; ret = convert_to_sparse(istate); diff --git a/refs.c b/refs.c index 73d4a93926..305a075746 100644 --- a/refs.c +++ b/refs.c @@ -2062,7 +2062,7 @@ int ref_update_reject_duplicates(struct string_list *refnames, static int run_transaction_hook(struct ref_transaction *transaction, const char *state) { - struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC; struct string_list to_stdin = STRING_LIST_INIT_NODUP; int ret = 0, i; diff --git a/reset.c b/reset.c index 6499bc5127..b93fe6a783 100644 --- a/reset.c +++ b/reset.c @@ -128,7 +128,8 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action, reflog_head); } if (run_hook) { - struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC; + strvec_pushl(&opt.args, oid_to_hex(orig ? orig : null_oid()), oid_to_hex(oid), diff --git a/sequencer.c b/sequencer.c index f451e23d0c..f13a0cbfbe 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1148,7 +1148,7 @@ int update_head_with_reflog(const struct commit *old_head, static int run_rewrite_hook(const struct object_id *oldoid, const struct object_id *newoid) { - struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC; struct strbuf tmp = STRBUF_INIT; struct string_list to_stdin = STRING_LIST_INIT_DUP; int code; @@ -4522,7 +4522,7 @@ static int pick_commits(struct repository *r, if (!stat(rebase_path_rewritten_list(), &st) && st.st_size > 0) { struct child_process notes_cp = CHILD_PROCESS_INIT; - struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT; + struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT_ASYNC; notes_cp.in = open(rebase_path_rewritten_list(), O_RDONLY); notes_cp.git_cmd = 1; diff --git a/transport.c b/transport.c index 4ca8fc0391..abf8785885 100644 --- a/transport.c +++ b/transport.c @@ -1204,7 +1204,7 @@ static int run_pre_push_hook(struct transport *transport, struct ref *remote_refs) { int ret = 0; - struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC; struct ref *r; struct string_list to_stdin = STRING_LIST_INIT_NODUP; From patchwork Thu Aug 12 00:42:55 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Emily Shaffer X-Patchwork-Id: 12432131 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-26.3 required=3.0 tests=BAYES_00,DKIMWL_WL_MED, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER,INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS, USER_AGENT_GIT,USER_IN_DEF_DKIM_WL autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 55B25C4338F for ; Thu, 12 Aug 2021 00:43:22 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 3896B60FC3 for ; Thu, 12 Aug 2021 00:43:22 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233189AbhHLAnp (ORCPT ); Wed, 11 Aug 2021 20:43:45 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:57986 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233159AbhHLAnn (ORCPT ); Wed, 11 Aug 2021 20:43:43 -0400 Received: from mail-yb1-xb49.google.com (mail-yb1-xb49.google.com [IPv6:2607:f8b0:4864:20::b49]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id E3197C061765 for ; Wed, 11 Aug 2021 17:43:18 -0700 (PDT) Received: by mail-yb1-xb49.google.com with SMTP id a62-20020a254d410000b0290592f360b0ccso4428774ybb.14 for ; Wed, 11 Aug 2021 17:43:18 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20161025; h=date:in-reply-to:message-id:mime-version:references:subject:from:to :cc; bh=1zwxAfztIy14JhjmSWzdl9jruSn/79HMVHXTDHt2S34=; b=B5g4c54d67rbkRimssqFdmu136+2n0PGryvLJYlM9u48UjtKTTxuxFRDKAahzo2JRk /E/JOr+3YXNzR3xQIDwgO+6a/yY4NHrZhZeKYRjDUDuR6z6P3b3PD/wUVq5gUJJaFI9b 4+LvECYeWWRsxqsX+QUNHzVhnLYgK9Pofu2hLZ2cmKBguW6RqIsLfGdZQdBfGXEtXyAO gSfGyloqxD3gb2hRx3NeXZx2+hrGrjfdeiTtSI4g3PTnKZ0UMQhqst9gEEHoRmBBEmaR BPv4+tgEZ6Bi0hs+tgDRSkBxCVlI1JEr4Pv4P6Zzm2uxNA/lljT7/tJBiTAlzGmTdlJA bK1Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:in-reply-to:message-id:mime-version :references:subject:from:to:cc; bh=1zwxAfztIy14JhjmSWzdl9jruSn/79HMVHXTDHt2S34=; b=OEc5xV01bPy2B8I0iP0CXVYvwv5pb+5jM8Zd47tNLuIXrO5HzGCA2+C0agg7Bgg6W2 f0rvDNhLFTVvbi4X+8qhuZBUnHnOc3hGmZ0H6BPNOKHASw4QxwghkOHlmaDtZTQlIjcM NlGfwNhc7EHRhv4JcY5CaGHVtIrkR7rxK29tWTm/P5EDJjjnVMxnCbXl7aO0NajCeBxi 8n9ip5+btVsNUd0rAHiAyEcwxMYDhkMiWAl6wOCS84IoBExWbx3GEkTxHL1CprpCD3uY b5soLQ6ejDqgSiFTb+0+fRMoKIAa7ubcdhBpEzf4H0/44IfB5jL6elFhArFP+4ugi9q0 V11g== X-Gm-Message-State: AOAM530gQ55spcLiVt69iehdMUA7hJqeyuGjLom/1EMEDVXC62clEctw ioQkN8WYEkmDE+9JylStWp8pJvVLo3zkW4iY/+7/Cvk43g4eQvbnMgGzUgXguAvAgSRTTjQo2LI nBhs3hYAUTVXUv5FgwyKj7sdTMMd9JbxDuK7APBRLjjKLUhkqe9d/s7k2psHX67o/oUHpSjWyUQ == X-Google-Smtp-Source: ABdhPJz3nMMaxNuU3sFJC/Mxjm/UuzostAEuRWMvpw2IMVebN1hLE9PjY0F5mqX1Rf+rPWkcmDC0JhM3qmBk2HLjTpg= X-Received: from podkayne.svl.corp.google.com ([2620:15c:2ce:200:1377:1116:63ab:bf4b]) (user=emilyshaffer job=sendgmr) by 2002:a25:ba93:: with SMTP id s19mr998928ybg.265.1628728998056; Wed, 11 Aug 2021 17:43:18 -0700 (PDT) Date: Wed, 11 Aug 2021 17:42:55 -0700 In-Reply-To: <20210812004258.74318-1-emilyshaffer@google.com> Message-Id: <20210812004258.74318-4-emilyshaffer@google.com> Mime-Version: 1.0 References: <20210812004258.74318-1-emilyshaffer@google.com> X-Mailer: git-send-email 2.32.0.605.g8dce9f2422-goog Subject: [PATCH v2 3/6] hook: introduce "git hook list" From: Emily Shaffer To: git@vger.kernel.org Cc: Emily Shaffer Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org If more than one hook will be run, it may be useful to see a list of which hooks should be run. At very least, it will be useful for us to test the semantics of multihooks ourselves. For now, only list the hooks which will run in the order they will run in; later, it might be useful to include more information like where the hooks were configured and whether or not they will run. Signed-off-by: Emily Shaffer --- builtin/hook.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ hook.c | 18 ++++++++---------- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/builtin/hook.c b/builtin/hook.c index 12c9126032..c36b05376c 100644 --- a/builtin/hook.c +++ b/builtin/hook.c @@ -8,8 +8,11 @@ #define BUILTIN_HOOK_RUN_USAGE \ N_("git hook run [--to-stdin=] [-- ]") +#define BUILTIN_HOOK_LIST_USAGE \ + N_("git hook list ") static const char * const builtin_hook_usage[] = { + BUILTIN_HOOK_LIST_USAGE, BUILTIN_HOOK_RUN_USAGE, NULL }; @@ -19,6 +22,50 @@ static const char * const builtin_hook_run_usage[] = { NULL }; +static const char *const builtin_hook_list_usage[] = { + BUILTIN_HOOK_LIST_USAGE, + NULL +}; + +static int list(int argc, const char **argv, const char *prefix) +{ + struct list_head *head, *pos; + const char *hookname = NULL; + struct strbuf hookdir_annotation = STRBUF_INIT; + + struct option list_options[] = { + OPT_END(), + }; + + argc = parse_options(argc, argv, prefix, list_options, + builtin_hook_list_usage, 0); + + if (argc < 1) + usage_msg_opt(_("You must specify a hook event name to list."), + builtin_hook_list_usage, list_options); + + hookname = argv[0]; + + head = hook_list(hookname); + + if (list_empty(head)) { + printf(_("no commands configured for hook '%s'\n"), + hookname); + return 0; + } + + list_for_each(pos, head) { + struct hook *item = list_entry(pos, struct hook, list); + item = list_entry(pos, struct hook, list); + if (item) + printf("%s\n", item->hook_path); + } + + clear_hook_list(head); + strbuf_release(&hookdir_annotation); + + return 0; +} static int run(int argc, const char **argv, const char *prefix) { int i; @@ -88,6 +135,8 @@ int cmd_hook(int argc, const char **argv, const char *prefix) if (!argc) goto usage; + if (!strcmp(argv[0], "list")) + return list(argc, argv, prefix); if (!strcmp(argv[0], "run")) return run(argc, argv, prefix); diff --git a/hook.c b/hook.c index 37f682c6d8..2714b63473 100644 --- a/hook.c +++ b/hook.c @@ -96,22 +96,20 @@ int hook_exists(const char *name) struct list_head* hook_list(const char* hookname) { struct list_head *hook_head = xmalloc(sizeof(struct list_head)); + const char *hook_path = find_hook(hookname); + INIT_LIST_HEAD(hook_head); if (!hookname) return NULL; - if (have_git_dir()) { - const char *hook_path = find_hook(hookname); - - /* Add the hook from the hookdir */ - if (hook_path) { - struct hook *to_add = xmalloc(sizeof(*to_add)); - to_add->hook_path = hook_path; - to_add->feed_pipe_cb_data = NULL; - list_add_tail(&to_add->list, hook_head); - } + /* Add the hook from the hookdir */ + if (hook_path) { + struct hook *to_add = xmalloc(sizeof(*to_add)); + to_add->hook_path = hook_path; + to_add->feed_pipe_cb_data = NULL; + list_add_tail(&to_add->list, hook_head); } return hook_head; From patchwork Thu Aug 12 00:42:56 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Emily Shaffer X-Patchwork-Id: 12432133 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-26.3 required=3.0 tests=BAYES_00,DKIMWL_WL_MED, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER,INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS, USER_AGENT_GIT,USER_IN_DEF_DKIM_WL autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 307DEC4320A for ; Thu, 12 Aug 2021 00:43:23 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 0CFAA60FC3 for ; Thu, 12 Aug 2021 00:43:23 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233178AbhHLAnq (ORCPT ); Wed, 11 Aug 2021 20:43:46 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:58002 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233193AbhHLAnp (ORCPT ); Wed, 11 Aug 2021 20:43:45 -0400 Received: from mail-qt1-x84a.google.com (mail-qt1-x84a.google.com [IPv6:2607:f8b0:4864:20::84a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 379D9C061799 for ; Wed, 11 Aug 2021 17:43:21 -0700 (PDT) Received: by mail-qt1-x84a.google.com with SMTP id 12-20020ac8570c0000b029029624d5a057so2264519qtw.12 for ; Wed, 11 Aug 2021 17:43:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20161025; h=date:in-reply-to:message-id:mime-version:references:subject:from:to :cc; bh=NcExMpBskBtD+KGM4GW9neOABzmjEoIy2WzFCzlO9to=; b=WsQ//w4/8vD5T+x3PgHlUNvEGAyczi1KTXbx2/OamJ59SdViUc17FvA0bIFCsvpcYd cuQnrWYwF3Sb5/vJ89eQp1zw/Tvkzy76UDF4XwcwQS634bmfrY46pwfY3JTflPO35TSQ MhzF6Gp961iSvFAtwOtXa/N5YRkGZ8v7Qrd4EUbL6g4oTmVJjxfmZuFq7rN6JeFQtune 6CTpl0L3NYVdlV60+0U+OyXKa65X2YJ3mSQ2G0MSSywjUZxbLGeUXlU4mGqEMUqy/WKb mUSjNY/gFapQAlnZMzMe15TYWZ4KR+VLxrlOJsOEpGswyX75B1ctzAGnqsUEIQFhi6AJ GTqg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:in-reply-to:message-id:mime-version :references:subject:from:to:cc; bh=NcExMpBskBtD+KGM4GW9neOABzmjEoIy2WzFCzlO9to=; b=PWR5FSnnSTHoque8lhRQQ7fdQmxJBIZNB2q3wa7AqxGyj29jCL7041voUQ8lAAZlaF 9mhNjE96pkC/+ZcXbHkXIZXV2xBQs/RML2w0XRQF+KCOq+h1U9+9qTNo2AC5t4oraMsJ 0fnd1uKNS6Dha7PPbFp87hQBYlsTLbghcraxzaw9Sug7QBdpwfKbA5j+lFCRlC3wReFv 5vzhkTQ3IyyK8KZSBU85WYT36nShlzp7QFWaR45EtYMTxrPWC29aVjNJQxPrMun0U6mY nBtXCuhP7wZFsK9JgktXyYm+QyHP3WxSCygN65d950BkDzVjVxtbEQTeolX2KsGBHG5O 3LLw== X-Gm-Message-State: AOAM532gU2YT85WLlrl8unCuAMkSUR7U1xpqax7p6XdaDwy/odEEgglk 6dVx/FDlLd3J0ngSspGLpQe8x+a67TXVAHKCiNwnrR2LlbuTU+6fUc3JlOggBVywKZzaZT4zvHb 4BpVpRaQJEzTH03UtdY0TVzbHgrNPDRMmjqxGnfKTWcYACq5yjyBpdiwZbjWG4//ZabhP95+eBQ == X-Google-Smtp-Source: ABdhPJx4pEIQZvYKY2AJKL3a771PylLhGZukMwrwtRnqYpA1jFEfM+MfMrNzTnz4XqD7oZN1MnFjLejteqNyqDE+Go0= X-Received: from podkayne.svl.corp.google.com ([2620:15c:2ce:200:1377:1116:63ab:bf4b]) (user=emilyshaffer job=sendgmr) by 2002:a05:6214:5182:: with SMTP id kl2mr1323830qvb.19.1628729000282; Wed, 11 Aug 2021 17:43:20 -0700 (PDT) Date: Wed, 11 Aug 2021 17:42:56 -0700 In-Reply-To: <20210812004258.74318-1-emilyshaffer@google.com> Message-Id: <20210812004258.74318-5-emilyshaffer@google.com> Mime-Version: 1.0 References: <20210812004258.74318-1-emilyshaffer@google.com> X-Mailer: git-send-email 2.32.0.605.g8dce9f2422-goog Subject: [PATCH v2 4/6] hook: allow running non-native hooks From: Emily Shaffer To: git@vger.kernel.org Cc: Emily Shaffer Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org As the hook architecture and 'git hook run' become more featureful, we may find wrappers wanting to use the hook architecture to run their own hooks, thereby getting nice things like parallelism and idiomatic Git configuration for free. Enable this by letting 'git hook run' bypass the known_hooks() check. We do still want to keep known_hooks() around, though - by die()ing when an internal Git call asks for run_hooks("my-new-hook"), we can remind Git developers to update Documentation/githooks.txt with their new hook, which in turn helps Git users discover this new hook. Signed-off-by: Emily Shaffer --- Documentation/git-hook.txt | 8 ++++++++ builtin/hook.c | 4 ++-- hook.c | 32 ++++++++++++++++++++++++++++---- hook.h | 16 +++++++++++++++- 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt index 8bf82b5dd4..11a8b87c60 100644 --- a/Documentation/git-hook.txt +++ b/Documentation/git-hook.txt @@ -18,6 +18,14 @@ This command is an interface to git hooks (see linkgit:githooks[5]). Currently it only provides a convenience wrapper for running hooks for use by git itself. In the future it might gain other functionality. +It's possible to use this command to refer to hooks which are not native to Git, +for example if a wrapper around Git wishes to expose hooks into its own +operation in a way which is already familiar to Git users. However, wrappers +invoking such hooks should be careful to name their hook events something which +Git is unlikely to use for a native hook later on. For example, Git is much less +likely to create a `mytool-validate-commit` hook than it is to create a +`validate-commit` hook. + SUBCOMMANDS ----------- diff --git a/builtin/hook.c b/builtin/hook.c index c36b05376c..3aa65dd791 100644 --- a/builtin/hook.c +++ b/builtin/hook.c @@ -46,7 +46,7 @@ static int list(int argc, const char **argv, const char *prefix) hookname = argv[0]; - head = hook_list(hookname); + head = hook_list(hookname, 1); if (list_empty(head)) { printf(_("no commands configured for hook '%s'\n"), @@ -108,7 +108,7 @@ static int run(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); hook_name = argv[0]; - hooks = hook_list(hook_name); + hooks = hook_list(hook_name, 1); if (list_empty(hooks)) { /* ... act like run_hooks_oneshot() under --ignore-missing */ if (ignore_missing) diff --git a/hook.c b/hook.c index 2714b63473..e5acd02a50 100644 --- a/hook.c +++ b/hook.c @@ -52,12 +52,21 @@ static int known_hook(const char *name) const char *find_hook(const char *name) { - static struct strbuf path = STRBUF_INIT; + const char *hook_path; if (!known_hook(name)) die(_("the hook '%s' is not known to git, should be in hook-list.h via githooks(5)"), name); + hook_path = find_hook_gently(name); + + return hook_path; +} + +const char *find_hook_gently(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) { @@ -93,10 +102,16 @@ int hook_exists(const char *name) return !!find_hook(name); } -struct list_head* hook_list(const char* hookname) +struct hook_config_cb +{ + struct strbuf *hook_key; + struct list_head *list; +}; + +struct list_head* hook_list(const char* hookname, int allow_unknown) { struct list_head *hook_head = xmalloc(sizeof(struct list_head)); - const char *hook_path = find_hook(hookname); + const char *hook_path; INIT_LIST_HEAD(hook_head); @@ -104,6 +119,11 @@ struct list_head* hook_list(const char* hookname) if (!hookname) return NULL; + if (allow_unknown) + hook_path = find_hook_gently(hookname); + else + hook_path = find_hook(hookname); + /* Add the hook from the hookdir */ if (hook_path) { struct hook *to_add = xmalloc(sizeof(*to_add)); @@ -299,7 +319,11 @@ int run_hooks_oneshot(const char *hook_name, struct run_hooks_opt *options) if (options->path_to_stdin && options->feed_pipe) BUG("choose only one method to populate stdin"); - hooks = hook_list(hook_name); + /* + * 'git hooks run ' uses run_found_hooks, so we don't need to + * allow unknown hooknames here. + */ + hooks = hook_list(hook_name, 0); /* * If you need to act on a missing hook, use run_found_hooks() diff --git a/hook.h b/hook.h index 4f90228a0c..ffa96c6e4d 100644 --- a/hook.h +++ b/hook.h @@ -9,8 +9,16 @@ * 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_*. + * + * If the hook is not a native hook (e.g. present in Documentation/githooks.txt) + * find_hook() will die(). find_hook_gently() does not consult the native hook + * list and will check for any hook name in the hooks directory regardless of + * whether it is known. find_hook() should be used by internal calls to hooks, + * and find_hook_gently() should only be used when the hookname was provided by + * the user, such as by 'git hook run my-wrapper-hook'. */ const char *find_hook(const char *name); +const char *find_hook_gently(const char *name); /* * A boolean version of find_hook() @@ -32,8 +40,14 @@ struct hook { /* * Provides a linked list of 'struct hook' detailing commands which should run * in response to the 'hookname' event, in execution order. + * + * If allow_unknown is unset, hooks will be checked against the hook list + * generated from Documentation/githooks.txt. Otherwise, any hook name will be + * allowed. allow_unknown should only be set when the hook name is provided by + * the user; internal calls to hook_list should make sure the hook they are + * invoking is present in Documentation/githooks.txt. */ -struct list_head* hook_list(const char *hookname); +struct list_head* hook_list(const char *hookname, int allow_unknown); struct run_hooks_opt { From patchwork Thu Aug 12 00:42:57 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Emily Shaffer X-Patchwork-Id: 12432137 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-26.3 required=3.0 tests=BAYES_00,DKIMWL_WL_MED, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER,INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS, URIBL_BLOCKED,USER_AGENT_GIT,USER_IN_DEF_DKIM_WL autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id AA586C4338F for ; Thu, 12 Aug 2021 00:43:27 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 8E79260FC3 for ; Thu, 12 Aug 2021 00:43:27 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233167AbhHLAnv (ORCPT ); Wed, 11 Aug 2021 20:43:51 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:58022 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233187AbhHLAnr (ORCPT ); Wed, 11 Aug 2021 20:43:47 -0400 Received: from mail-yb1-xb49.google.com (mail-yb1-xb49.google.com [IPv6:2607:f8b0:4864:20::b49]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A13F6C061798 for ; Wed, 11 Aug 2021 17:43:23 -0700 (PDT) Received: by mail-yb1-xb49.google.com with SMTP id j9-20020a2581490000b02905897d81c63fso4470555ybm.8 for ; Wed, 11 Aug 2021 17:43:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20161025; h=date:in-reply-to:message-id:mime-version:references:subject:from:to :cc; bh=bygr08Nm2GfaI75f8pkYp/y6cMQhW2DyUpgNXwhCLtk=; b=Xd0UXcF6oXHlrJlAhn5JaZMziyZEex0O0Xu2Rg7SPiBT2dvaG6Cdsc1k6DiYeaFYpK G+Hg7iSdFeiDylpK9+kRaShECWVzebNRa0uZ4sSAVwnEHSyFlZvzWT4qjaV6ZPKQAPpl ZO9GlqxwmPOPLgLNI7QR9fsOZgXUqFBihRLxNyvRsCaGtKkIyC8qPtPB/nm5OtnzvdSl 2LqjYT/gpaL5o4B2s7PUTnfIXSQZtHSq4sBTJEkoX1Nn10JodVo7wwujz3x2YoQ2xRr1 rmhxyqWzuohf8Y9Zirhn+MhpcFt7UojieDKdOQESPf3kpbsDwXTSlM21E/uoi180wmUI KYUw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:in-reply-to:message-id:mime-version :references:subject:from:to:cc; bh=bygr08Nm2GfaI75f8pkYp/y6cMQhW2DyUpgNXwhCLtk=; b=RcfGJ+udzngmEzk0PFfSBQFLsqrnyznldUpbpnA5iah4aNqTKdUOuhvxjtkJVqF8Pg fr1zWy15S26OBhvFoDwOxhkXLrye5Hz0oWN6WRukuwnLQ2iwxX9m9FgKXkb1oE6bIlFO AI4frDZGDsiSdCcm61lu1vNwblDtT+yGG7uTe+A8QJx+fNkPB6o2QkvFF+GhTFAh94A1 mY+YIgz/kRzxasYavhWYan0DeRfzvuBHidYIFjACNtobu6P2Msg92twRswy+C0+QQqr2 vyj43tUUA59HOr4po0zXGQrdQ0tg1Rq5NekIk8hTP8CzP6dN8Dr6zW3RFet6F/pGvDFA pNWg== X-Gm-Message-State: AOAM531LTnI5ELwK10LOzttbod+rK3i8YahpgIQJ+YNMvHyJAsWOpcoM r8LG06+V/3xTj/lT7pCu+/VI8M6qMupqTPxrmtzbJ4OgonGM3euoQe/5B9xwJxh0aDLnUK840gJ d6/uL8v28kGSGZXfHxJYZNdw2J16xpRT5aFLIEMubE3TqLtKC6mXep9VijZ/vlkzBYENYoOx+Rg == X-Google-Smtp-Source: ABdhPJxxTv5XV9V/G5n3S2qrRnZQ5xQ9b8vlFG9MZAJ//nXd/K3xYYGVe4WXI3mhPfbXNbHsoR5fiK5vo2slnxDjkac= X-Received: from podkayne.svl.corp.google.com ([2620:15c:2ce:200:1377:1116:63ab:bf4b]) (user=emilyshaffer job=sendgmr) by 2002:a25:dacc:: with SMTP id n195mr933096ybf.283.1628729002862; Wed, 11 Aug 2021 17:43:22 -0700 (PDT) Date: Wed, 11 Aug 2021 17:42:57 -0700 In-Reply-To: <20210812004258.74318-1-emilyshaffer@google.com> Message-Id: <20210812004258.74318-6-emilyshaffer@google.com> Mime-Version: 1.0 References: <20210812004258.74318-1-emilyshaffer@google.com> X-Mailer: git-send-email 2.32.0.605.g8dce9f2422-goog Subject: [PATCH v2 5/6] hook: include hooks from the config From: Emily Shaffer To: git@vger.kernel.org Cc: Emily Shaffer Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org Teach the hook.[hc] library to parse configs to populare the list of hooks to run for a given event. Multiple commands can be specified for a given hook by providing multiple "hook..command = " and "hook..event = " lines. Hooks will be run in config order of the "hook..event" lines. For example: $ git config --list | grep ^hook hook.bar.command=~/bar.sh hook.bar.event=pre-commit $ git hook run # Runs ~/bar.sh # Runs .git/hooks/pre-commit Signed-off-by: Emily Shaffer --- Documentation/config/hook.txt | 5 + Documentation/git-hook.txt | 23 ++++- builtin/hook.c | 5 +- hook.c | 166 +++++++++++++++++++++++++++++----- hook.h | 7 +- t/t1800-hook.sh | 141 ++++++++++++++++++++++++++++- 6 files changed, 318 insertions(+), 29 deletions(-) diff --git a/Documentation/config/hook.txt b/Documentation/config/hook.txt index 96d3d6572c..a97b980cca 100644 --- a/Documentation/config/hook.txt +++ b/Documentation/config/hook.txt @@ -1,3 +1,8 @@ +hook..command:: + A command to execute during the hook event. This can be an + executable on your device, a oneliner for your shell, or the name of a + hookcmd. See linkgit:git-hook[1]. + hook.jobs:: Specifies how many hooks can be run simultaneously during parallelized hook execution. If unspecified, defaults to the number of processors on diff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt index 11a8b87c60..c610ed9583 100644 --- a/Documentation/git-hook.txt +++ b/Documentation/git-hook.txt @@ -26,12 +26,31 @@ Git is unlikely to use for a native hook later on. For example, Git is much less likely to create a `mytool-validate-commit` hook than it is to create a `validate-commit` hook. +This command parses the default configuration files for pairs of configs like +so: + + [hook "linter"] + event = pre-commit + command = ~/bin/linter --c + +Conmmands are run in the order Git encounters their associated +`hook..event` configs during the configuration parse (see +linkgit:git-config[1]). + +In general, when instructions suggest adding a script to +`.git/hooks/`, you can specify it in the config instead by running +`git config --add hook..command && git config --add +hook..event ` - this way you can share the script between +multiple repos. That is, `cp ~/my-script.sh ~/project/.git/hooks/pre-commit` +would become `git config --add hook.my-script.command ~/my-script.sh && git +config --add hook.my-script.event pre-commit`. + SUBCOMMANDS ----------- run:: - Run the `` hook. See linkgit:githooks[5] for - the hook names we support. + Runs hooks configured for ``, in the order they are + discovered during the config parse. + Any positional arguments to the hook should be passed after an optional `--` (or `--end-of-options`, see linkgit:gitcli[7]). The diff --git a/builtin/hook.c b/builtin/hook.c index 3aa65dd791..ea49dc4ef6 100644 --- a/builtin/hook.c +++ b/builtin/hook.c @@ -49,7 +49,7 @@ static int list(int argc, const char **argv, const char *prefix) head = hook_list(hookname, 1); if (list_empty(head)) { - printf(_("no commands configured for hook '%s'\n"), + printf(_("no hooks configured for event '%s'\n"), hookname); return 0; } @@ -58,7 +58,8 @@ static int list(int argc, const char **argv, const char *prefix) struct hook *item = list_entry(pos, struct hook, list); item = list_entry(pos, struct hook, list); if (item) - printf("%s\n", item->hook_path); + printf("%s\n", item->name ? item->name + : _("hook from hookdir")); } clear_hook_list(head); diff --git a/hook.c b/hook.c index e5acd02a50..51ada266bc 100644 --- a/hook.c +++ b/hook.c @@ -12,6 +12,50 @@ static void free_hook(struct hook *ptr) free(ptr); } +/* + * Walks the linked list at 'head' to check if any hook named 'name' + * already exists. Returns a pointer to that hook if so, otherwise returns NULL. + */ +static struct hook *find_hook_by_name(struct list_head *head, + const char *name) +{ + struct list_head *pos = NULL, *tmp = NULL; + struct hook *found = NULL; + + list_for_each_safe(pos, tmp, head) { + struct hook *it = list_entry(pos, struct hook, list); + if (!strcmp(it->name, name)) { + list_del(pos); + found = it; + break; + } + } + return found; +} + +/* + * Adds a hook if it's not already in the list, or moves it to the tail of the + * list if it was already there. name == NULL indicates it's from the hookdir; + * just append it in this case. + */ +static void append_or_move_hook(struct list_head *head, const char *name) +{ + struct hook *to_add = NULL; + + /* if it's not from hookdir, check if the hook is already in the list */ + if (name) + to_add = find_hook_by_name(head, name); + + if (!to_add) { + /* adding a new hook, not moving an old one */ + to_add = xmalloc(sizeof(*to_add)); + to_add->name = name; + to_add->feed_pipe_cb_data = NULL; + } + + list_add_tail(&to_add->list, head); +} + static void remove_hook(struct list_head *to_remove) { struct hook *hook_to_remove = list_entry(to_remove, struct hook, list); @@ -99,38 +143,80 @@ const char *find_hook_gently(const char *name) int hook_exists(const char *name) { - return !!find_hook(name); + return !list_empty(hook_list(name, 0)); } struct hook_config_cb { - struct strbuf *hook_key; + const char *hook_event; struct list_head *list; }; -struct list_head* hook_list(const char* hookname, int allow_unknown) +/* + * Callback for git_config which adds configured hooks to a hook list. Hooks + * can be configured by specifying both hook..command = and + * hook..event = . + */ +static int hook_config_lookup(const char *key, const char *value, void *cb_data) +{ + struct hook_config_cb *data = cb_data; + const char *subsection, *parsed_key; + size_t subsection_len = 0; + struct strbuf subsection_cpy = STRBUF_INIT; + + /* + * Don't bother doing the expensive parse if there's no + * chance that the config matches 'hook.myhook.event = hook_event'. + */ + if (!value || strcmp(value, data->hook_event)) + return 0; + + /* Looking for "hook.friendlyname.event = hook_event" */ + if (parse_config_key(key, + "hook", + &subsection, + &subsection_len, + &parsed_key) || + strcmp(parsed_key, "event")) + return 0; + + /* + * 'subsection' is a pointer to the internals of 'key', which we don't + * own the memory for. Copy it away to the hook list. + */ + strbuf_add(&subsection_cpy, subsection, subsection_len); + + append_or_move_hook(data->list, strbuf_detach(&subsection_cpy, NULL)); + + + return 0; +} + +struct list_head* hook_list(const char *hookname, int allow_unknown) { struct list_head *hook_head = xmalloc(sizeof(struct list_head)); - const char *hook_path; + struct hook_config_cb cb_data = { + .hook_event = hookname, + .list = hook_head, + }; + if (!allow_unknown && !known_hook(hookname)) + die(_("Don't recognize hook event '%s'. " + "Is it documented in 'githooks.txt'?"), + hookname); INIT_LIST_HEAD(hook_head); if (!hookname) return NULL; - if (allow_unknown) - hook_path = find_hook_gently(hookname); - else - hook_path = find_hook(hookname); + /* Add the hooks from the config, e.g. hook.myhook.event = pre-commit */ + git_config(hook_config_lookup, &cb_data); - /* Add the hook from the hookdir */ - if (hook_path) { - struct hook *to_add = xmalloc(sizeof(*to_add)); - to_add->hook_path = hook_path; - to_add->feed_pipe_cb_data = NULL; - list_add_tail(&to_add->list, hook_head); - } + /* Add the hook from the hookdir. The placeholder makes it easier to + * allocate work in pick_next_hook. */ + if (find_hook_gently(hookname)) + append_or_move_hook(hook_head, NULL); return hook_head; } @@ -191,11 +277,43 @@ static int pick_next_hook(struct child_process *cp, cp->trace2_hook_name = hook_cb->hook_name; cp->dir = hook_cb->options->dir; + /* to enable oneliners, let config-specified hooks run in shell. + * config-specified hooks have a name. */ + cp->use_shell = !!run_me->name; + /* add command */ - if (hook_cb->options->absolute_path) - strvec_push(&cp->args, absolute_path(run_me->hook_path)); - else - strvec_push(&cp->args, run_me->hook_path); + if (run_me->name) { + /* ...from config */ + struct strbuf cmd_key = STRBUF_INIT; + char *command = NULL; + + strbuf_addf(&cmd_key, "hook.%s.command", run_me->name); + if (git_config_get_string(cmd_key.buf, &command)) { + /* TODO test me! */ + die(_("'hook.%s.command' must be configured " + "or 'hook.%s.event' must be removed; aborting.\n"), + run_me->name, run_me->name); + } + + strvec_push(&cp->args, command); + } else { + /* ...from hookdir. */ + const char *hook_path = NULL; + /* + * + * At this point we are already running, so don't validate + * whether the hook name is known or not. + */ + hook_path = find_hook_gently(hook_cb->hook_name); + if (!hook_path) + BUG("hookdir hook in hook list but no hookdir hook present in filesystem"); + + if (hook_cb->options->absolute_path) + hook_path = absolute_path(hook_path); + + strvec_push(&cp->args, hook_path); + } + /* * add passed-in argv, without expanding - let the user get back @@ -225,8 +343,11 @@ static int notify_start_failure(struct strbuf *out, hook_cb->rc |= 1; - strbuf_addf(out, _("Couldn't start hook '%s'\n"), - attempted->hook_path); + if (attempted->name) + strbuf_addf(out, _("Couldn't start hook '%s'\n"), + attempted->name); + else + strbuf_addstr(out, _("Couldn't start hook from hooks directory\n")); return 1; } @@ -320,7 +441,8 @@ int run_hooks_oneshot(const char *hook_name, struct run_hooks_opt *options) BUG("choose only one method to populate stdin"); /* - * 'git hooks run ' uses run_found_hooks, so we don't need to + * 'git hooks run ' uses run_found_hooks, and we want to make + * sure internal callers are using known hooks, so we don't need to * allow unknown hooknames here. */ hooks = hook_list(hook_name, 0); diff --git a/hook.h b/hook.h index ffa96c6e4d..a6263864b9 100644 --- a/hook.h +++ b/hook.h @@ -27,8 +27,11 @@ int hook_exists(const char *hookname); struct hook { struct list_head list; - /* The path to the hook */ - const char *hook_path; + /* + * The friendly name of the hook. NULL indicates the hook is from the + * hookdir. + */ + const char *name; /* * Use this to keep state for your feed_pipe_fn if you are using diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh index 217db848b3..ef2432f53a 100755 --- a/t/t1800-hook.sh +++ b/t/t1800-hook.sh @@ -1,13 +1,29 @@ #!/bin/bash -test_description='git-hook command' +test_description='git-hook command and config-managed multihooks' . ./test-lib.sh +setup_hooks () { + test_config hook.ghi.event pre-commit --add + test_config hook.ghi.command "/path/ghi" --add + test_config_global hook.def.event pre-commit --add + test_config_global hook.def.command "/path/def" --add +} + +setup_hookdir () { + mkdir .git/hooks + write_script .git/hooks/pre-commit <<-EOF + echo \"Legacy Hook\" + EOF + test_when_finished rm -rf .git/hooks +} + test_expect_success 'git hook usage' ' test_expect_code 129 git hook && test_expect_code 129 git hook run && test_expect_code 129 git hook run -h && + test_expect_code 129 git hook list -h && test_expect_code 129 git hook run --unknown 2>err && grep "unknown option" err ' @@ -153,4 +169,127 @@ test_expect_success 'stdin to hooks' ' test_cmp expect actual ' +test_expect_success 'git hook list orders by config order' ' + setup_hooks && + + cat >expected <<-EOF && + def + ghi + EOF + + git hook list pre-commit >actual && + test_cmp expected actual +' + +test_expect_success 'git hook list reorders on duplicate event declarations' ' + setup_hooks && + + # 'def' is usually configured globally; move it to the end by + # configuring it locally. + test_config hook.def.event "pre-commit" --add && + + cat >expected <<-EOF && + ghi + def + EOF + + git hook list pre-commit >actual && + test_cmp expected actual +' + +test_expect_success 'git hook list shows hooks from the hookdir' ' + setup_hookdir && + + cat >expected <<-EOF && + hook from hookdir + EOF + + git hook list pre-commit >actual && + test_cmp expected actual +' + +test_expect_success 'inline hook definitions execute oneliners' ' + test_config hook.oneliner.event "pre-commit" && + test_config hook.oneliner.command "echo \"Hello World\"" && + + echo "Hello World" >expected && + + # hooks are run with stdout_to_stderr = 1 + git hook run pre-commit 2>actual && + test_cmp expected actual +' + +test_expect_success 'inline hook definitions resolve paths' ' + write_script sample-hook.sh <<-EOF && + echo \"Sample Hook\" + EOF + + test_when_finished "rm sample-hook.sh" && + + test_config hook.sample-hook.event pre-commit && + test_config hook.sample-hook.command "\"$(pwd)/sample-hook.sh\"" && + + echo \"Sample Hook\" >expected && + + # hooks are run with stdout_to_stderr = 1 + git hook run pre-commit 2>actual && + test_cmp expected actual +' + +test_expect_success 'hookdir hook included in git hook run' ' + setup_hookdir && + + echo \"Legacy Hook\" >expected && + + # hooks are run with stdout_to_stderr = 1 + git hook run pre-commit 2>actual && + test_cmp expected actual +' + +test_expect_success 'stdin to multiple hooks' ' + test_config hook.stdin-a.event "test-hook" --add && + test_config hook.stdin-a.command "xargs -P1 -I% echo a%" --add && + test_config hook.stdin-b.event "test-hook" --add && + test_config hook.stdin-b.command "xargs -P1 -I% echo b%" --add && + + cat >input <<-EOF && + 1 + 2 + 3 + EOF + + cat >expected <<-EOF && + a1 + a2 + a3 + b1 + b2 + b3 + EOF + + git hook run --to-stdin=input test-hook 2>actual && + test_cmp expected actual +' + +test_expect_success 'multiple hooks in series' ' + test_config hook.series-1.event "test-hook" && + test_config hook.series-1.command "echo 1" --add && + test_config hook.series-2.event "test-hook" && + test_config hook.series-2.command "echo 2" --add && + mkdir .git/hooks && + write_script .git/hooks/test-hook <<-EOF && + echo 3 + EOF + + cat >expected <<-EOF && + 1 + 2 + 3 + EOF + + git hook run -j1 test-hook 2>actual && + test_cmp expected actual && + + rm -rf .git/hooks +' test_done From patchwork Thu Aug 12 00:42:58 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Emily Shaffer X-Patchwork-Id: 12432135 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-26.3 required=3.0 tests=BAYES_00,DKIMWL_WL_MED, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER,INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS, URIBL_BLOCKED,USER_AGENT_GIT,USER_IN_DEF_DKIM_WL autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 7A171C4320A for ; Thu, 12 Aug 2021 00:43:28 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 58B6060F21 for ; Thu, 12 Aug 2021 00:43:28 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233193AbhHLAnv (ORCPT ); Wed, 11 Aug 2021 20:43:51 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:58042 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233216AbhHLAnu (ORCPT ); Wed, 11 Aug 2021 20:43:50 -0400 Received: from mail-yb1-xb49.google.com (mail-yb1-xb49.google.com [IPv6:2607:f8b0:4864:20::b49]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id C67E4C061765 for ; Wed, 11 Aug 2021 17:43:25 -0700 (PDT) Received: by mail-yb1-xb49.google.com with SMTP id p71-20020a25424a0000b029056092741626so4401496yba.19 for ; Wed, 11 Aug 2021 17:43:25 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20161025; h=date:in-reply-to:message-id:mime-version:references:subject:from:to :cc; bh=IPOe1lVJUuzg+pKRFhPkoKYvVHa9Jh4RzFXsyTEsGmE=; b=Vi3TkfDHj6Z17uv8XSXQZ0xBIlOZp9HnnewdSa1HxvkA4lITyNJJ3e1cDzlfwWxaL6 md1tI7EYJ/b68Ii+QLNElGxWTuokQS0ZkTwoyxaBskPB+tWlNf2EKN+a1F2/yuB1e0Mu xYzLgFPqMlV2UlS52NDYgJynb9uk9lHMeDlk98x+7E7hcOApARvdoRR9e0cpzOTas9Rv dbf/VyDfYypXmNklAuKf/C4afnAeKQu0Qj9Xa74U1oK/s2nPxQWBg+GvgwyAy5rpIw7o ApprEUzdOMkWKX5X0OOr91Ng9XRyf08DrfE2EHR95R5qMPNH70vDXob2Na3vtoJxiUEX XCPQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:in-reply-to:message-id:mime-version :references:subject:from:to:cc; bh=IPOe1lVJUuzg+pKRFhPkoKYvVHa9Jh4RzFXsyTEsGmE=; b=jKvU5w/ZIFAeuPkKXwYiUkxlSwwyNojJV9sX7Moziy7vfQ5EElpWP/GC6U6gGDyMmL QKTsswoAun+M5BhXHVNuKoD61WqxFaV581QXosY/KYlKqGqcKF+HiRBHSjS34KWAKvFq pUnuuhRl3Gu0x3imWWpW26zrQ+Ssj9EagCyg986WXjpyh8HZ/7EwtQ31cOZpuE0fautc 7ut4SBe7xvyu0S7MQjOCe5KVGXZJeWZow3HzgAVJJScQcICzSFpkCMgZlRnNoirSHD2J 9xoDL4CCmUZ77vsNzdbQRqf8VteaGQ4qveoliVtkNGfOiR0+HHPttBVHyUf94HYM1stR lthA== X-Gm-Message-State: AOAM530e7l/Lx5yB/VrASH/llO/5hOBbDu8txxh0a8GEs/zr2e2MOSDi xxCmCCdAoSJmGMPhLnum7APtU9VKkAPNCP4qKh5+lLXPY77rCMiUoP8mx7K/C3a6PtOSQaaXf1l NnWa4JqxOsMDvbwNxe5C9wH5sM641Qph0TjM8jhpxEq/NnBKrssaW04i7jYfH6GUcM6cSoIqRkw == X-Google-Smtp-Source: ABdhPJytQn3ztoGwgfuFBiNwzQ03q6gFeHsdO/+OX8pWvatJ4usaq070ZmtdyDY+XI0xeWWh7Bk3cc6CS4FF9mJUlLw= X-Received: from podkayne.svl.corp.google.com ([2620:15c:2ce:200:1377:1116:63ab:bf4b]) (user=emilyshaffer job=sendgmr) by 2002:a25:374d:: with SMTP id e74mr975509yba.130.1628729005023; Wed, 11 Aug 2021 17:43:25 -0700 (PDT) Date: Wed, 11 Aug 2021 17:42:58 -0700 In-Reply-To: <20210812004258.74318-1-emilyshaffer@google.com> Message-Id: <20210812004258.74318-7-emilyshaffer@google.com> Mime-Version: 1.0 References: <20210812004258.74318-1-emilyshaffer@google.com> X-Mailer: git-send-email 2.32.0.605.g8dce9f2422-goog Subject: [PATCH v2 6/6] hook: allow out-of-repo 'git hook' invocations From: Emily Shaffer To: git@vger.kernel.org Cc: Emily Shaffer Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org Since hooks can now be supplied via the config, and a config can be present without a gitdir via the global and system configs, we can start to allow 'git hook run' to occur without a gitdir. This enables us to do things like run sendemail-validate hooks when running 'git send-email' from a nongit directory. It still doesn't make sense to look for hooks in the hookdir in nongit repos, though, as there is no hookdir. Signed-off-by: Emily Shaffer --- Notes: For hookdir hooks, do we want to run them in nongit dir when core.hooksPath is set? For example, if someone set core.hooksPath in their global config and then ran 'git hook run sendemail-validate' in a nongit dir? git.c | 2 +- hook.c | 2 +- t/t1800-hook.sh | 20 +++++++++++++++----- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/git.c b/git.c index 540909c391..39988ee3b0 100644 --- a/git.c +++ b/git.c @@ -538,7 +538,7 @@ static struct cmd_struct commands[] = { { "grep", cmd_grep, RUN_SETUP_GENTLY }, { "hash-object", cmd_hash_object }, { "help", cmd_help }, - { "hook", cmd_hook, RUN_SETUP }, + { "hook", cmd_hook, RUN_SETUP_GENTLY }, { "index-pack", cmd_index_pack, RUN_SETUP_GENTLY | NO_PARSEOPT }, { "init", cmd_init_db }, { "init-db", cmd_init_db }, diff --git a/hook.c b/hook.c index 51ada266bc..87d57f4118 100644 --- a/hook.c +++ b/hook.c @@ -215,7 +215,7 @@ struct list_head* hook_list(const char *hookname, int allow_unknown) /* Add the hook from the hookdir. The placeholder makes it easier to * allocate work in pick_next_hook. */ - if (find_hook_gently(hookname)) + if (have_git_dir() && find_hook_gently(hookname)) append_or_move_hook(hook_head, NULL); return hook_head; diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh index ef2432f53a..a7e45c0d16 100755 --- a/t/t1800-hook.sh +++ b/t/t1800-hook.sh @@ -118,15 +118,25 @@ test_expect_success 'git hook run -- pass arguments' ' test_cmp expect actual ' -test_expect_success 'git hook run -- out-of-repo runs excluded' ' - write_script .git/hooks/test-hook <<-EOF && - echo Test hook - EOF +test_expect_success 'git hook run: out-of-repo runs execute global hooks' ' + test_config_global hook.global-hook.event test-hook --add && + test_config_global hook.global-hook.command "echo no repo no problems" --add && + + echo "global-hook" >expect && + nongit git hook list test-hook >actual && + test_cmp expect actual && + + echo "no repo no problems" >expect && - nongit test_must_fail git hook run test-hook + nongit git hook run test-hook 2>actual && + test_cmp expect actual ' test_expect_success 'git -c core.hooksPath= hook run' ' + write_script .git/hooks/test-hook <<-EOF && + echo Test hook + EOF + mkdir my-hooks && write_script my-hooks/test-hook <<-\EOF && echo Hook ran $1 >>actual