From patchwork Thu Jul 15 23:25: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: 12381225 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 57A7BC636C9 for ; Thu, 15 Jul 2021 23:26:27 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 3677461289 for ; Thu, 15 Jul 2021 23:26:27 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231983AbhGOX3S (ORCPT ); Thu, 15 Jul 2021 19:29:18 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:36752 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229975AbhGOX3F (ORCPT ); Thu, 15 Jul 2021 19:29:05 -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 3E930C06175F for ; Thu, 15 Jul 2021 16:26:11 -0700 (PDT) Received: by mail-yb1-xb49.google.com with SMTP id s186-20020a252cc30000b029055bc7fcfebdso9928366ybs.12 for ; Thu, 15 Jul 2021 16:26:11 -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=7ofVpg7HsIJ3YuHNGTcSLM4Cpps2nJlQ7kSOcQt5A0M=; b=p2ewL5PuDbWQhYZSHe31i8rNmyR0wIiZqlsoBuOOQt/35eDYOiatiCNa7pLHe/C2Nn ECT+NE/pVZ96bndbMrk4e/J0aJpR1FTAV1n/i9PLIg+XrYhOIy6gNI0ZU7ZDe2Y9QQVH o6ve54weLLT5B3H/ivttIpV6HOw0BGFL/7NEhw6aqGyCrWawszQoqmQLnLjhapRKi6ZS f3FnEgl4eowvTvUc2tSIb7UsRWxHdgNPe/JLprJE254ds3GgVUF6t1dRlWVLIBJG1fgu ubbpCY2YIjU5ahUAW+Ijmtuq2b5mdQkumEIEYAx/3b0xSPbBf4B249/3E+hZ9zaD0UHe okUg== 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=7ofVpg7HsIJ3YuHNGTcSLM4Cpps2nJlQ7kSOcQt5A0M=; b=Y9F0b/aHvq5Kgc82h4LrX7rBqe5LWVMBAT1/FIj647ddxC5ROW038HpjnJYNU9I6tC 0sg8cbB3AhnIGKXftaXyasaoZLirPtH+FbTqA7ve7PKQ7nXaPaq/HXjPQ55sB4BPN2X2 172I63MZ+aNn3xo/J3UmQSa4J3CN7aVm8ZhUQD2PyEc9Ub4P77wuum4z8uPnVw/eVgkg J/LGw3z20hxZbSjHu2bC7UTHu/6APxJ2ZUjZFJ0+thoK35emGBYa7iJ6/x60Lb9OO4Am Jlomv4ZmoucdzkHyrR0qGOxB3qNTYYOiamxeqa6vrvqiMkvPRIccvoRkfIL5GJ4xk9/g D9Ng== X-Gm-Message-State: AOAM532xECltmFStM0iAwc3RXIhtDMIKVqu5xQCvQ/hY8dUZBdnO8pvU MgkKbKe29wZNVv8lqQ9pBMz1IrM1E+S3AGeJM4bPKrLuDD4eH1athbRJKUQyQtpD047RViFbufL Exo6rq6UWygPq8WNqFkv8FP/q7y498CLdA0/gNDzapwO3TxXnc/Qx6GdYZm5Vpn0/crFftlvA7w == X-Google-Smtp-Source: ABdhPJxse0sSJAvWSD7M8f/ZqdG0cnSDOAd/cA55o/QGm//tqfKdZWS8ljiLonETRPgkYzyVa75798/8weUBCDbIBmQ= X-Received: from podkayne.svl.corp.google.com ([2620:15c:2ce:200:c795:6596:6e89:ce2]) (user=emilyshaffer job=sendgmr) by 2002:a25:c708:: with SMTP id w8mr9416510ybe.246.1626391570361; Thu, 15 Jul 2021 16:26:10 -0700 (PDT) Date: Thu, 15 Jul 2021 16:25:55 -0700 In-Reply-To: <20210715232603.3415111-1-emilyshaffer@google.com> Message-Id: <20210715232603.3415111-2-emilyshaffer@google.com> Mime-Version: 1.0 References: <20210715232603.3415111-1-emilyshaffer@google.com> X-Mailer: git-send-email 2.32.0.402.g57bb445576-goog Subject: [PATCH 1/9] 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 | 9 +++--- hook.c | 85 ++++++++++++++++++++++++++++++++++++++++---------- hook.h | 15 ++++++++- 3 files changed, 87 insertions(+), 22 deletions(-) diff --git a/builtin/hook.c b/builtin/hook.c index 169a8dd08f..a41ff36da9 100644 --- a/builtin/hook.c +++ b/builtin/hook.c @@ -25,7 +25,7 @@ static int run(int argc, const char **argv, const char *prefix) int rc = 0; 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, @@ -58,15 +58,16 @@ static int run(int argc, const char **argv, const char *prefix) * run_found_hooks() instead... */ hook_name = argv[0]; - hook_path = find_hook(hook_name); - if (!hook_path) { + hooks = hook_list(hook_name); + if (list_empty(hooks)) { /* ... act like run_hooks() under --ignore-missing */ if (ignore_missing) return 0; error("cannot find a hook named %s", hook_name); return 1; } - rc = run_found_hooks(hook_name, hook_path, &opt); + + rc = run_found_hooks(hook_name, hooks, &opt); run_hooks_opt_clear(&opt); diff --git a/hook.c b/hook.c index 31e822bf51..c1dac6982f 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); @@ -108,6 +154,8 @@ static int pick_next_hook(struct child_process *cp, struct hook_cb_data *hook_cb = pp_cb; struct hook *run_me = hook_cb->run_me; + if (!run_me) + return 0; /* reopen the file for stdin; run_command closes it. */ if (hook_cb->options->path_to_stdin) { @@ -126,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 @@ -137,6 +188,13 @@ static int pick_next_hook(struct child_process *cp, /* Provide context for errors if necessary */ *pp_task_cb = run_me; + /* 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; } @@ -170,24 +228,17 @@ static int notify_hook_finished(int result, return 1; } -int run_found_hooks(const char *hook_name, const char *hook_path, +int run_found_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, .options = options, .invoked_hook = options->invoked_hook, }; - 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.run_me = list_first_entry(hooks, struct hook, list); if (options->jobs != 1) BUG("we do not handle %d or any other != 1 job number yet", options->jobs); @@ -201,15 +252,13 @@ int run_found_hooks(const char *hook_name, const char *hook_path, &cb_data, "hook", hook_name); - if (options->absolute_path) - strbuf_release(&abs_path); return cb_data.rc; } int run_hooks(const char *hook_name, struct run_hooks_opt *options) { - const char *hook_path; + struct list_head *hooks; int ret; if (!options) BUG("a struct run_hooks_opt must be provided to run_hooks"); @@ -217,15 +266,17 @@ int run_hooks(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); + hooks = hook_list(hook_name); /* * If you need to act on a missing hook, use run_found_hooks() * instead */ - if (!hook_path) + if (list_empty(hooks)) return 0; - ret = run_found_hooks(hook_name, hook_path, options); + ret = run_found_hooks(hook_name, hooks, options); + + clear_hook_list(hooks); return ret; } diff --git a/hook.h b/hook.h index 9d9171672d..b97237931b 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 */ @@ -103,6 +111,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; @@ -120,6 +129,10 @@ int run_hooks(const char *hook_name, struct run_hooks_opt *options); * Takes an already resolved hook and runs it. Internally the simpler * run_hooks() will call this. */ -int run_found_hooks(const char *hookname, const char *hook_path, +int run_found_hooks(const char *hookname, struct list_head *hooks, 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 Jul 15 23:25: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: 12381227 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 EC62CC636CA for ; Thu, 15 Jul 2021 23:26:27 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id CC229613DA for ; Thu, 15 Jul 2021 23:26:27 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232323AbhGOX3U (ORCPT ); Thu, 15 Jul 2021 19:29:20 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:36768 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232123AbhGOX3I (ORCPT ); Thu, 15 Jul 2021 19:29:08 -0400 Received: from mail-qk1-x74a.google.com (mail-qk1-x74a.google.com [IPv6:2607:f8b0:4864:20::74a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 583E3C06175F for ; Thu, 15 Jul 2021 16:26:13 -0700 (PDT) Received: by mail-qk1-x74a.google.com with SMTP id k63-20020a37a1420000b02903b4fb67f606so4993961qke.10 for ; Thu, 15 Jul 2021 16:26:13 -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=L7NcHwymbQGKVXLQYFClcnEJT/ChRmZ3SYV/JAQ6MGw=; b=Utjp0h0JLcD4QOp1OBrfhrM9B4mR7e43zPZ7WHYGguPgznp2xzvOcEgbWttRJ0UaBS OAmdOTBQxn83nKG67WEV89N+7Q/FF2SqFc8loIsyQNwFucIimXzVoQZnYksIytiULCLT JJNMCDoPBjY9X3o1wB11fxsmKgezuU4a9TqJ/1JMn1LyElNWsuwxN5HRyImOJEw0uM6x 6kO3oOjLl+HxKyb0U8HXWeTe4FDUqavFbtZBX3878zt5JQmXfnwfvX4a+P7ToxKQA1ih hJOHAV1D6iObtiCVGpk1jCsFUbziDpZkgAqWkh72dmAyJQEkRm7H1oLAHABIKKY3TZ2C nC4w== 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=L7NcHwymbQGKVXLQYFClcnEJT/ChRmZ3SYV/JAQ6MGw=; b=DakDpvkZ0Ne7dMyKk+zb9qqzigqbp854UmIm6pqGmgFTQIgyINVsbrV2/u7SsCKAgW FhmGpyNogMzd98TadEpCTxB4WwL6kXRWGhb1sHYgRfazD6svgFC83gJyUkdNCkrGV3dW pVFlfn/dpGw8PoQf8sKC2YqBecu0hMe1f1SOS5xU2trGfFqZwqBpm7JD0gPh3DsbIqt+ I2xvaEMlTkjricXY/X1NzHbCK54fhQ+aJimhDbFbnqJNm6dO9XX/prp2xaXkHnUcbnBP kJPzAQ+y8ALNt0yFnTeoos106VgbfZ3/YFzAqgenlxuXFLraZUe1aGNOQKNvyHP00v+y LSKg== X-Gm-Message-State: AOAM5337Ab8fCrAFJJEkxshM/Z1j78CTLGbYFdXxzoKfiylPQuUtuUHu bxjcup2zM4hQv1iPRBhPWRb8VrHhI1W7s08O9Kt0sAOTkmFwAHcqDA/1ZXb8RamuVBeBariq21/ YiX0qA8Xr5EzhVHLXK7kwNZu35yoizUnIxsLhxq1/7yw9VuaYLvx897+wiWIQsvzsWSvfztNsOA == X-Google-Smtp-Source: ABdhPJx7/sLbztXly105Q37W1PLZzuWKInZBaBqODS6cVXesL/HA23NRssLxuxYbdhCy0fL/w4kwltAz16gzyXBFtTA= X-Received: from podkayne.svl.corp.google.com ([2620:15c:2ce:200:c795:6596:6e89:ce2]) (user=emilyshaffer job=sendgmr) by 2002:ad4:47a8:: with SMTP id a8mr7033367qvz.53.1626391572468; Thu, 15 Jul 2021 16:26:12 -0700 (PDT) Date: Thu, 15 Jul 2021 16:25:56 -0700 In-Reply-To: <20210715232603.3415111-1-emilyshaffer@google.com> Message-Id: <20210715232603.3415111-3-emilyshaffer@google.com> Mime-Version: 1.0 References: <20210715232603.3415111-1-emilyshaffer@google.com> X-Mailer: git-send-email 2.32.0.402.g57bb445576-goog Subject: [PATCH 2/9] hook: allow parallel hook execution From: Emily Shaffer To: git@vger.kernel.org Cc: Emily Shaffer 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 --- Documentation/config/hook.txt | 4 ++++ Documentation/git-hook.txt | 17 ++++++++++++++++- builtin/am.c | 12 ++++++++---- builtin/checkout.c | 3 ++- builtin/clone.c | 3 ++- builtin/gc.c | 3 ++- builtin/hook.c | 6 +++++- builtin/merge.c | 3 ++- builtin/rebase.c | 3 ++- builtin/receive-pack.c | 14 ++++++++++---- builtin/worktree.c | 3 ++- commit.c | 4 +++- hook.c | 32 +++++++++++++++++++++++++++++--- hook.h | 11 +++++------ read-cache.c | 3 ++- refs.c | 3 ++- reset.c | 4 +++- sequencer.c | 7 +++++-- transport.c | 4 +++- 19 files changed, 107 insertions(+), 32 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 6e4f9c8036..bdad38142a 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -445,8 +445,9 @@ 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(&opt); assert(state->msg); strvec_push(&opt.args, am_path(state, "final-commit")); ret = run_hooks("applypatch-msg", &opt); @@ -467,9 +468,10 @@ 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; int ret; + run_hooks_opt_init_async(&opt); strvec_push(&opt.args, "rebase"); opt.path_to_stdin = am_path(state, "rewritten"); @@ -1602,9 +1604,10 @@ static void do_commit(const struct am_state *state) struct commit_list *parents = NULL; const char *reflog_msg, *author, *committer = NULL; struct strbuf sb = STRBUF_INIT; - struct run_hooks_opt hook_opt_pre = RUN_HOOKS_OPT_INIT; - struct run_hooks_opt hook_opt_post = RUN_HOOKS_OPT_INIT; + struct run_hooks_opt hook_opt_pre; + struct run_hooks_opt hook_opt_post; + run_hooks_opt_init_async(&hook_opt_pre); if (run_hooks("pre-applypatch", &hook_opt_pre)) { run_hooks_opt_clear(&hook_opt_pre); exit(1); @@ -1659,6 +1662,7 @@ static void do_commit(const struct am_state *state) fclose(fp); } + run_hooks_opt_init_async(&hook_opt_post); run_hooks("post-applypatch", &hook_opt_post); run_hooks_opt_clear(&hook_opt_pre); diff --git a/builtin/checkout.c b/builtin/checkout.c index 6205ace09f..be4450a433 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -107,9 +107,10 @@ 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; int rc; + run_hooks_opt_init_sync(&opt); /* "new_commit" can be NULL when checking out from the index before a commit exists. */ strvec_pushl(&opt.args, diff --git a/builtin/clone.c b/builtin/clone.c index de57a3119b..87cfbf60e5 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; if (option_no_checkout) return 0; @@ -822,6 +822,7 @@ static int checkout(int submodule_progress) if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); + run_hooks_opt_init_sync(&hook_opt); strvec_pushl(&hook_opt.args, oid_to_hex(null_oid()), oid_to_hex(&oid), "1", NULL); err |= run_hooks("post-checkout", &hook_opt); run_hooks_opt_clear(&hook_opt); diff --git a/builtin/gc.c b/builtin/gc.c index a12641a691..16890b097c 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -349,7 +349,7 @@ static void add_repack_incremental_option(void) static int need_to_gc(void) { - struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT; + struct run_hooks_opt hook_opt; /* * Setting gc.auto to 0 or negative can disable the @@ -397,6 +397,7 @@ static int need_to_gc(void) else return 0; + run_hooks_opt_init_async(&hook_opt); if (run_hooks("pre-auto-gc", &hook_opt)) { run_hooks_opt_clear(&hook_opt); return 0; diff --git a/builtin/hook.c b/builtin/hook.c index a41ff36da9..d196d8498c 100644 --- a/builtin/hook.c +++ b/builtin/hook.c @@ -21,7 +21,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; int rc = 0; int ignore_missing = 0; const char *hook_name; @@ -32,9 +32,13 @@ 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(), }; + run_hooks_opt_init_async(&opt); + argc = parse_options(argc, argv, prefix, run_options, builtin_hook_run_usage, PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH); diff --git a/builtin/merge.c b/builtin/merge.c index 0425c9bf2b..67c2eba053 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; const struct object_id *head = &head_commit->object.oid; if (!msg) @@ -490,6 +490,7 @@ static void finish(struct commit *head_commit, } /* Run a post-merge hook */ + run_hooks_opt_init_async(&opt); strvec_push(&opt.args, squash ? "1" : "0"); run_hooks("post-merge", &opt); run_hooks_opt_clear(&opt); diff --git a/builtin/rebase.c b/builtin/rebase.c index 2081f6fa8d..fe9f144cad 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; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -2024,6 +2024,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } /* If a hook exists, give it a chance to interrupt*/ + run_hooks_opt_init_async(&hook_opt); strvec_pushl(&hook_opt.args, options.upstream_arg, argc ? argv[0] : NULL, NULL); if (!ok_to_skip_pre_rebase && run_hooks("pre-rebase", &hook_opt)) { diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index cd658f41d5..e4726eb211 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -910,7 +910,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; struct receive_hook_feed_context ctx; int rc; struct command *iter = commands; @@ -922,6 +922,7 @@ static int run_receive_hook(struct command *commands, return 0; /* pre-receive hooks should run in series as the hook updates refs */ + run_hooks_opt_init_async(&opt); if (!strcmp(hook_name, "pre-receive")) opt.jobs = 1; @@ -956,9 +957,10 @@ 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; int code; + run_hooks_opt_init_async(&opt); strvec_pushl(&opt.args, cmd->ref_name, oid_to_hex(&cmd->old_oid), @@ -1443,7 +1445,10 @@ 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); opt.invoked_hook = invoked_hook; strvec_pushf(env, "GIT_WORK_TREE=%s", absolute_path(work_tree)); @@ -1642,8 +1647,9 @@ 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(&opt); for (cmd = commands; cmd; cmd = cmd->next) { if (cmd->error_string || cmd->did_not_exist) continue; diff --git a/builtin/worktree.c b/builtin/worktree.c index 2ad26a76f4..5a2c9d1039 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -382,8 +382,9 @@ 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(&opt); strvec_pushl(&opt.env, "GIT_DIR", "GIT_WORK_TREE", NULL); strvec_pushl(&opt.args, oid_to_hex(null_oid()), diff --git a/commit.c b/commit.c index cf62ebceae..58ee2c81bb 100644 --- a/commit.c +++ b/commit.c @@ -1700,10 +1700,12 @@ 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; va_list args; const char *arg; int ret; + + run_hooks_opt_init_sync(&opt); strvec_pushf(&opt.env, "GIT_INDEX_FILE=%s", index_file); /* diff --git a/hook.c b/hook.c index c1dac6982f..935751fa6c 100644 --- a/hook.c +++ b/hook.c @@ -88,6 +88,14 @@ const char *find_hook(const char *name) return path.buf; } +int configured_hook_jobs(void) +{ + int n = online_cpus(); + git_config_get_int("hook.jobs", &n); + + return n; +} + int hook_exists(const char *name) { return !!find_hook(name); @@ -117,6 +125,26 @@ struct list_head* hook_list(const char* hookname) return hook_head; } +void run_hooks_opt_init_sync(struct run_hooks_opt *o) +{ + strvec_init(&o->env); + strvec_init(&o->args); + o->path_to_stdin = NULL; + o->jobs = 1; + o->dir = NULL; + o->feed_pipe = NULL; + o->feed_pipe_ctx = NULL; + o->consume_sideband = NULL; + o->invoked_hook = NULL; + o->absolute_path = 0; +} + +void run_hooks_opt_init_async(struct run_hooks_opt *o) +{ + run_hooks_opt_init_sync(o); + o->jobs = configured_hook_jobs(); +} + void run_hooks_opt_clear(struct run_hooks_opt *o) { strvec_clear(&o->env); @@ -238,11 +266,9 @@ int run_found_hooks(const char *hook_name, struct list_head *hooks, .invoked_hook = options->invoked_hook, }; + cb_data.head = hooks; cb_data.run_me = list_first_entry(hooks, struct hook, list); - if (options->jobs != 1) - BUG("we do not handle %d or any other != 1 job number yet", options->jobs); - run_processes_parallel_tr2(options->jobs, pick_next_hook, notify_start_failure, diff --git a/hook.h b/hook.h index b97237931b..586ddf40bb 100644 --- a/hook.h +++ b/hook.h @@ -35,6 +35,9 @@ struct hook { */ struct list_head* hook_list(const char *hookname); +/* Provides the number of threads to use for parallel hook execution. */ +int configured_hook_jobs(void); + struct run_hooks_opt { /* Environment vars to be set for each hook */ @@ -93,12 +96,6 @@ struct run_hooks_opt int *invoked_hook; }; -#define RUN_HOOKS_OPT_INIT { \ - .jobs = 1, \ - .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()'. @@ -117,6 +114,8 @@ struct hook_cb_data { int *invoked_hook; }; +void run_hooks_opt_init_sync(struct run_hooks_opt *o); +void run_hooks_opt_init_async(struct run_hooks_opt *o); void run_hooks_opt_clear(struct run_hooks_opt *o); /* diff --git a/read-cache.c b/read-cache.c index f801313cc9..e8cbbc6ef2 100644 --- a/read-cache.c +++ b/read-cache.c @@ -3063,7 +3063,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; ret = convert_to_sparse(istate); @@ -3092,6 +3092,7 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l else ret = close_lock_file_gently(lock); + run_hooks_opt_init_async(&hook_opt); strvec_pushl(&hook_opt.args, istate->updated_workdir ? "1" : "0", istate->updated_skipworktree ? "1" : "0", diff --git a/refs.c b/refs.c index 1149e7e7dc..61d0bb2579 100644 --- a/refs.c +++ b/refs.c @@ -2063,7 +2063,7 @@ static int run_transaction_hook(struct ref_transaction *transaction, const char *state) { struct strbuf buf = STRBUF_INIT; - struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + struct run_hooks_opt opt; struct string_list to_stdin = STRING_LIST_INIT_DUP; int ret = 0, i; char o[GIT_MAX_HEXSZ + 1], n[GIT_MAX_HEXSZ + 1]; @@ -2071,6 +2071,7 @@ static int run_transaction_hook(struct ref_transaction *transaction, if (!hook_exists("reference-transaction")) return ret; + run_hooks_opt_init_async(&opt); strvec_push(&opt.args, state); for (i = 0; i < transaction->nr; i++) { diff --git a/reset.c b/reset.c index e6af33b901..48d45f5b79 100644 --- a/reset.c +++ b/reset.c @@ -128,7 +128,9 @@ 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_sync(&opt); strvec_pushl(&opt.args, oid_to_hex(orig ? orig : null_oid()), oid_to_hex(oid), diff --git a/sequencer.c b/sequencer.c index 2440b9dccd..17b93242a7 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1148,11 +1148,13 @@ 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; struct strbuf tmp = STRBUF_INIT; struct string_list to_stdin = STRING_LIST_INIT_DUP; int code; + run_hooks_opt_init_async(&opt); + strvec_push(&opt.args, "amend"); strbuf_addf(&tmp, @@ -4524,7 +4526,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; notes_cp.in = open(rebase_path_rewritten_list(), O_RDONLY); notes_cp.git_cmd = 1; @@ -4534,6 +4536,7 @@ static int pick_commits(struct repository *r, /* we don't care if this copying failed */ run_command(¬es_cp); + run_hooks_opt_init_async(&hook_opt); hook_opt.path_to_stdin = rebase_path_rewritten_list(); strvec_push(&hook_opt.args, "rebase"); run_hooks("post-rewrite", &hook_opt); diff --git a/transport.c b/transport.c index 9969ed2cdd..3381d24225 100644 --- a/transport.c +++ b/transport.c @@ -1200,11 +1200,13 @@ 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; struct strbuf tmp = STRBUF_INIT; struct ref *r; struct string_list to_stdin = STRING_LIST_INIT_DUP; + run_hooks_opt_init_async(&opt); + strvec_push(&opt.args, transport->remote->name); strvec_push(&opt.args, transport->url); From patchwork Thu Jul 15 23:25: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: 12381233 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 856FCC636CA for ; Thu, 15 Jul 2021 23:26:34 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 66CFE613E0 for ; Thu, 15 Jul 2021 23:26:34 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232425AbhGOX31 (ORCPT ); Thu, 15 Jul 2021 19:29:27 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:36774 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232139AbhGOX3J (ORCPT ); Thu, 15 Jul 2021 19:29:09 -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 6E303C061760 for ; Thu, 15 Jul 2021 16:26:15 -0700 (PDT) Received: by mail-yb1-xb49.google.com with SMTP id o11-20020a056902110bb029055b266be219so9924892ybu.13 for ; Thu, 15 Jul 2021 16:26:15 -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=eLhroixyFQJHnlO3hoUj+h+SpT11PKTG3X29biHUDPM=; b=eH/zCKxan4GH7iKS7xcbL5HKNZBPfbit0wwJJ/GCtg0xV52Kx4Dx/KOEEN8KU85bet VL+///k9yv3qahBRg39OYJzs/aM5FJm0ErFvdu/7ebWCaIRYxxFA+byrM7w9LaPWRwsh a/A7OVnqifOS5aWVr0CAV+G85PFY2AYUcqONeTLdznbCeiri+ykXsoXPbF0p+3iCsaW4 PY4lcvUNRMg8rhsVUravCOAA8yBx+wTX6OJ6MrHg3pjFNpwXsdPHs0D/3qkwnLzqdsqK faJZkJB2hls1csqsqh/LUehoYEidTBU0SnT9y+r6Nv+yWVXZOql7XL/8q0AuYUMYW1eX AAiw== 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=eLhroixyFQJHnlO3hoUj+h+SpT11PKTG3X29biHUDPM=; b=SwQWcfGjj3VAFMXRPE0angXaI9V2C9wZFGsOLsGNinEYX+iH2coEpuh531yG+lNpuA 5pYDB9IAF7/6eI8/AF5Pow/IgEUh792B7GVzZpqNphVxNLFrcjApL3iuSjPlAJTl6EMR 4wWk8cH0WItLbwz7fhq2wjg4Bea0PB2L9RwCUlQcY7ABouhuN2KnGbuiczL0qCH5ls0q 0glctAYtJGSQXF40qUcgtWNdl8gJe1CVRxIDa5hl4Yaxg5lac7CaEMMm+wlkptit8doX k91JixHrD4tA//U4+V2xVj6ONUmHr8JJDOIja+/L6HgVPUXYXm7K4HMaRJEGyhZolLti R1hw== X-Gm-Message-State: AOAM531WFtfGhZAlCJHtTUvWBUQGWrYP0A4P3kocXLnklqt3n0y0J7p8 ZT+NWo7wsCOzO8L/nJKbZGsx9OMWhXnhcCYDL94b3xldFV/dW6F9ZY9R6f63HZjz5q0VYqd3wvv N5ScHEjn/W5e+DyJfBw0YscxydToY8PpGAdFrPgKKyMWcOGfvho3bQg1mioBx5QJCHTkyw6fNGA == X-Google-Smtp-Source: ABdhPJyDIi3sZO2VzKXkmbKPlMzO42iWbZ6CrPORfRxWURvS7W3n8/FZjvXOegVQw6t20v4B5pb2ms9/rrMnq6qc32s= X-Received: from podkayne.svl.corp.google.com ([2620:15c:2ce:200:c795:6596:6e89:ce2]) (user=emilyshaffer job=sendgmr) by 2002:a25:aa09:: with SMTP id s9mr8876927ybi.165.1626391574574; Thu, 15 Jul 2021 16:26:14 -0700 (PDT) Date: Thu, 15 Jul 2021 16:25:57 -0700 In-Reply-To: <20210715232603.3415111-1-emilyshaffer@google.com> Message-Id: <20210715232603.3415111-4-emilyshaffer@google.com> Mime-Version: 1.0 References: <20210715232603.3415111-1-emilyshaffer@google.com> X-Mailer: git-send-email 2.32.0.402.g57bb445576-goog Subject: [PATCH 3/9] 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 | 43 +++++++++++++++++++++++++++++++++++++++++++ hook.c | 18 ++++++++---------- 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/builtin/hook.c b/builtin/hook.c index d196d8498c..8340c8c9a8 100644 --- a/builtin/hook.c +++ b/builtin/hook.c @@ -8,6 +8,7 @@ static const char * const builtin_hook_usage[] = { N_("git hook [...]"), + N_("git hook list "), N_("git hook run [] [-- ]"), NULL }; @@ -18,6 +19,46 @@ static const char * const builtin_hook_run_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_usage, 0); + + if (argc < 1) { + usage_msg_opt(_("You must specify a hook event name to list."), + builtin_hook_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 +129,8 @@ int cmd_hook(int argc, const char **argv, const char *prefix) if (!argc) usage_with_options(builtin_hook_usage, builtin_hook_options); + if (!strcmp(argv[0], "list")) + return list(argc, argv, prefix); if (!strcmp(argv[0], "run")) return run(argc, argv, prefix); else diff --git a/hook.c b/hook.c index 935751fa6c..19138a8290 100644 --- a/hook.c +++ b/hook.c @@ -104,22 +104,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 Jul 15 23:25: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: 12381231 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 727C6C636C8 for ; Thu, 15 Jul 2021 23:26:34 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 4C717613DA for ; Thu, 15 Jul 2021 23:26:34 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232499AbhGOX30 (ORCPT ); Thu, 15 Jul 2021 19:29:26 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:36784 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232220AbhGOX3L (ORCPT ); Thu, 15 Jul 2021 19:29:11 -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 C2A37C061764 for ; Thu, 15 Jul 2021 16:26:17 -0700 (PDT) Received: by mail-qt1-x84a.google.com with SMTP id v17-20020a05622a1451b02902533c8b7139so5204755qtx.3 for ; Thu, 15 Jul 2021 16:26:17 -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=P8rrmc24odBA/EU2X/mV0SLuWz46mUvnzj5DrSY/x7g=; b=q6Q1qmu/5zz81xeYCTdL3sjfISZUEEb00eizp1hn5zjey+5knMUEa9vZ82dIlIMD7q IFNV96zXvmgnWHJiRqcAbZDCaH4xY99Ru9w3pyXe18akPp8dbvj0okepJ8mScp9Gxf78 vx1mpme4sA2H8rJ+rFIMxFbPQgeFOOx3F84D7edFsiFCjS7cDU2qYr1JALKVAuMAwUJb sf0VP146j9CGoSjTBORXAc3phfNXgS+Bjdo+KbDinQ5LkTXWl0HXMBj7DNyEqJo4AcDJ lRiFJPkK9drK8/WDTv4bQm1/YVYLVjjN5IyGNCKLX2hpj9Z13/ILXvOl1SrW3/ar2rb2 KwTw== 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=P8rrmc24odBA/EU2X/mV0SLuWz46mUvnzj5DrSY/x7g=; b=C4gid6UIu+LWP0i7AM91/By4PspO/Ia5/A11pXLsLQRUEiIlwwlFANFfeFEAZ7GENT fB+8mCyw93mmwRkXnc1N9ujmdipSxw6zI5o0Ouhw7jr0jrzsZXHvtMAd3/9qBRYuNq7e 2TrVYo+FnWVhtOjSRvIf7NeQjMKRZH2cxSFdlybDGxJtWZlMCEv5rA+ljvpZ/PXYicTJ m7kEDLdnW1npSmc58+LU7zEKt0BgDG20gBXeSt5fFFK9K/YgRzFlcFckTmiU2q6fnBlr hIrrjcj5ef+EU6pzIQl/6KxnGs2pc4iP2GBx8J9VC6ZNEkIVmSQxWEk5ZCTq80mvNsXG Z9ng== X-Gm-Message-State: AOAM531h1nhNafVa6I9hY++5ynYK5t5mLMtCXA+oGqNL2Qn+G/tO7A3V MXncn6h8EGKRFxZcmkoWZleC0a5jwwBUyQGCB3+UwYAQf3dwdMGymz/9f0zkD5+KoRFsXoYz+dV azwkJkr5b3xqwNpjpc3dj2wqfQfPFAhBwimzaF5uR5jxZ7XdGyQv/2aORdne8mirFQSgmTI6lIQ == X-Google-Smtp-Source: ABdhPJycaGcDxH7YMJC8tUQdsyfoQ+JEVKigsYZCYp9xOO4//A8v5xiSILS4a1eeY3R9zhMLO0nqGzCoFERzWEPeErY= X-Received: from podkayne.svl.corp.google.com ([2620:15c:2ce:200:c795:6596:6e89:ce2]) (user=emilyshaffer job=sendgmr) by 2002:a05:6214:4102:: with SMTP id kc2mr6939571qvb.44.1626391576845; Thu, 15 Jul 2021 16:26:16 -0700 (PDT) Date: Thu, 15 Jul 2021 16:25:58 -0700 In-Reply-To: <20210715232603.3415111-1-emilyshaffer@google.com> Message-Id: <20210715232603.3415111-5-emilyshaffer@google.com> Mime-Version: 1.0 References: <20210715232603.3415111-1-emilyshaffer@google.com> X-Mailer: git-send-email 2.32.0.402.g57bb445576-goog Subject: [PATCH 4/9] hook: treat hookdir hook specially From: Emily Shaffer To: git@vger.kernel.org Cc: Emily Shaffer Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org Soon, we will allow users to specify hooks using the config. These config-specified hooks may require different child_process options than hook executables in the gitdir. So, let's differentiate between hooks coming from the gitdir and hooks coming from the config. Signed-off-by: Emily Shaffer --- hook.c | 3 ++- hook.h | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/hook.c b/hook.c index 19138a8290..3a588cb055 100644 --- a/hook.c +++ b/hook.c @@ -117,6 +117,7 @@ struct list_head* hook_list(const char* hookname) struct hook *to_add = xmalloc(sizeof(*to_add)); to_add->hook_path = hook_path; to_add->feed_pipe_cb_data = NULL; + to_add->from_hookdir = 1; list_add_tail(&to_add->list, hook_head); } @@ -200,7 +201,7 @@ static int pick_next_hook(struct child_process *cp, cp->dir = hook_cb->options->dir; /* add command */ - if (hook_cb->options->absolute_path) + if (run_me->from_hookdir && hook_cb->options->absolute_path) strvec_push(&cp->args, absolute_path(run_me->hook_path)); else strvec_push(&cp->args, run_me->hook_path); diff --git a/hook.h b/hook.h index 586ddf40bb..60389cd8cd 100644 --- a/hook.h +++ b/hook.h @@ -22,6 +22,8 @@ struct hook { /* The path to the hook */ const char *hook_path; + unsigned from_hookdir : 1; + /* * Use this to keep state for your feed_pipe_fn if you are using * run_hooks_opt.feed_pipe. Otherwise, do not touch it. From patchwork Thu Jul 15 23:25:59 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Emily Shaffer X-Patchwork-Id: 12381229 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 C0D98C636C9 for ; Thu, 15 Jul 2021 23:26:32 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id A1345613E0 for ; Thu, 15 Jul 2021 23:26:32 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232397AbhGOX3V (ORCPT ); Thu, 15 Jul 2021 19:29:21 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:36794 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232234AbhGOX3O (ORCPT ); Thu, 15 Jul 2021 19:29:14 -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 23611C061765 for ; Thu, 15 Jul 2021 16:26:20 -0700 (PDT) Received: by mail-yb1-xb49.google.com with SMTP id l16-20020a25cc100000b0290558245b7eabso9978935ybf.10 for ; Thu, 15 Jul 2021 16:26:20 -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=7qD2dtDQL5PfOC9r3c8WSXtT4kn85mpZVZ6BF1ffvyg=; b=cnIfQSDoU4ghmsPrzjKQWCWiqaMLRgvl0656ssWomyQTJhHyLGSiVvfE8slzYEJusP eSs83BZdaZaQTC3dS2EiRev8JOPR0AeX3JNLdSr6REiDN0ezKP8tkwUjKnt8mkaX+lmH zLLLIJigqSf4EbgI+mSzdlUGmpAvW677MiROf8J84BJ6cLJ9Jjo+nqMno7jER58eBJUx /dOYm+Z/JMljswJkGG1U9Z+3VlVQuFBygQ3XmcmobbR+yfWFSOaWIOq7BHZXLobMmNVI Kwte54mYFi7iS7vQrQmG1zaahLxplg9xMhdXweRKerklVwCHE2e39hPIuVJEqGcg9jeX ZBog== 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=7qD2dtDQL5PfOC9r3c8WSXtT4kn85mpZVZ6BF1ffvyg=; b=I+2K3fL4To4M7mJ7h8YAEEXOY3YOWnz4lydv5in9h+XOnyXQzZi5Goi4CGhO5bx2LO 3exNo6CX7VX4EmRzfCizJ0nGrEgJiuMtQFoAb4v0VYz8sFyEePeyS3xp9K9tHrUjLwsD +4gqwKpMHeOj8CwPPjLCdEnAWl3R+ACUCsK3crpjUR6cMyxybm9O3N9LK4YRdxAbSH4o LAwUlr2GfI4Yco04IffKpp1gq2xtasuC3KdttoG/lSNJtO9tyGC/7+JGYSGSaaaNa7wJ QPRue7g6YN90ocG2UrAsPYi0UVXcyJowZL4FErfYB/VG86CvxPgBKihhaBfECLZaTZ2k HoQQ== X-Gm-Message-State: AOAM530rqIFJaXC6gFO5RfnvCqQxzkiayHH0yMNqM3j9rJFi9VcyVzN1 DmHrRuJqeR0sP3WbjZlRapjkpJb6coEkJeRsGPy5OnxF08iqbZXtefSl0jHwzjq0nAnNJaeoE8x B5PlyEiZohxgW/IqN0I/BF5oSsv4bOwdoVAgfmTszp67dWEAYxESLcw0hvPterlidx69kZArAVw == X-Google-Smtp-Source: ABdhPJxWDm9WvlfJDf5O4LiULa3mDSWCrJqrOA3khGQTe0fRRfPOFr0nPn1bQiXHrlfZ4ApVLpcW4qI4EvkrF9ULDm8= X-Received: from podkayne.svl.corp.google.com ([2620:15c:2ce:200:c795:6596:6e89:ce2]) (user=emilyshaffer job=sendgmr) by 2002:a25:2484:: with SMTP id k126mr9345976ybk.344.1626391579300; Thu, 15 Jul 2021 16:26:19 -0700 (PDT) Date: Thu, 15 Jul 2021 16:25:59 -0700 In-Reply-To: <20210715232603.3415111-1-emilyshaffer@google.com> Message-Id: <20210715232603.3415111-6-emilyshaffer@google.com> Mime-Version: 1.0 References: <20210715232603.3415111-1-emilyshaffer@google.com> X-Mailer: git-send-email 2.32.0.402.g57bb445576-goog Subject: [PATCH 5/9] 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 --- builtin/hook.c | 4 ++-- hook.c | 32 ++++++++++++++++++++++++++++---- hook.h | 16 +++++++++++++++- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/builtin/hook.c b/builtin/hook.c index 8340c8c9a8..b08f9c9c4f 100644 --- a/builtin/hook.c +++ b/builtin/hook.c @@ -39,7 +39,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"), @@ -103,7 +103,7 @@ static int run(int argc, const char **argv, const char *prefix) * run_found_hooks() instead... */ hook_name = argv[0]; - hooks = hook_list(hook_name); + hooks = hook_list(hook_name, 1); if (list_empty(hooks)) { /* ... act like run_hooks() under --ignore-missing */ if (ignore_missing) diff --git a/hook.c b/hook.c index 3a588cb055..e1cf035022 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) { @@ -101,10 +110,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); @@ -112,6 +127,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)); @@ -291,7 +311,11 @@ int run_hooks(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 60389cd8cd..2559232880 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() @@ -34,8 +42,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); /* Provides the number of threads to use for parallel hook execution. */ int configured_hook_jobs(void); From patchwork Thu Jul 15 23:26:00 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Emily Shaffer X-Patchwork-Id: 12381235 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 2E612C636C8 for ; Thu, 15 Jul 2021 23:26:37 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id D02F8613DA for ; Thu, 15 Jul 2021 23:26:36 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232169AbhGOX33 (ORCPT ); Thu, 15 Jul 2021 19:29:29 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:36810 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232268AbhGOX3R (ORCPT ); Thu, 15 Jul 2021 19:29:17 -0400 Received: from mail-qk1-x74a.google.com (mail-qk1-x74a.google.com [IPv6:2607:f8b0:4864:20::74a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 36ECEC061766 for ; Thu, 15 Jul 2021 16:26:22 -0700 (PDT) Received: by mail-qk1-x74a.google.com with SMTP id c3-20020a37b3030000b02903ad0001a2e8so5025381qkf.3 for ; Thu, 15 Jul 2021 16:26:22 -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=KLpyPwtcgHUj2eR1HHgJggCvuoorFa+Vxzr+i5GK83w=; b=n7vP4D41zEpadhs9CSNs6uDob/3AaAG23Gk1fhBUTZbT0WDx2F8STQ2+IjZQJ56G/2 nhoQgKuZMh0jpnB9IULYPjygk5AdG50nvj+QNAxgjybX74I4jfJ5nc0+jAj/j3mw9RCL GhkWEzEZy/rUX/fyBn7HJ2r2Hr28GBIlzQx8TsuLCXpJLIylXfaOUu158JWZrPqwklwJ OeHTS02aAsXfiPSrELOGuXhdXRQHcaEXJbQE1pZSdXnhrjv4jKbwHBJkUBysO0ZDLiNM ufka/Yod+n6CM/dawn5LriRJYJZHA5r7r7gdrtrZGCQdXrXJ3XD1NjTdC6G+PPaTHnw9 S+iQ== 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=KLpyPwtcgHUj2eR1HHgJggCvuoorFa+Vxzr+i5GK83w=; b=OKn9i9MyLteUys8FEtbZDUabBR5/3ZNMAdaKXwxqTAZeEcrDeS7FhakvZDyD/Bp/ZP VBjIPu230Eu1J6Gz6P6wE9Ip3t30EWS5hc3QFtF9qxWPOJAJUO8SGT63WGL2PZXaXdWJ uVqPub/1lw9S3TXnrFtNQhU5BnXwzz4HnRgoKznjYBL44oyx2lzzHR9ViwQOylPDT1KM MlkJ7JaDvc03JIzco1ceEYTl+HQMUeUwdFgehd5PpiIVzjHduT/XhIZYQyXpjishcwy/ YZmNTbkIJ6ITFF7NSC6ViO1T63RY9S5dJzUm91Mk1s0RFOt0gKyRQoCdGdQMRk04XaeA zf7g== X-Gm-Message-State: AOAM531eYz4B77XVNkD9BuBtZo5FVOZ314NKPfy8Gm5Q2KAowtKodzf2 nTluO8HMGHCpNZumorUeQBim7ptRjM/xbzBmrXI0QSQ3BuIjYH11uwex+a0LYlt/nLLnwo2+MJ9 k1+Y3xR3w842K6jSTagJsQwVzMTEbpqV195XFOFUfvrzvKXKWBwGijApVErS5Gh8+klmETvh4Kg == X-Google-Smtp-Source: ABdhPJzTDpZIAxhPEGnnnFLgzzCGNLKtadWNrtugZoD0z5FiZ6rRtBaOh7AhfCyGztqYeHFWd9eoEYQGj3adgbu7TYE= X-Received: from podkayne.svl.corp.google.com ([2620:15c:2ce:200:c795:6596:6e89:ce2]) (user=emilyshaffer job=sendgmr) by 2002:ad4:5bec:: with SMTP id k12mr7057371qvc.5.1626391581355; Thu, 15 Jul 2021 16:26:21 -0700 (PDT) Date: Thu, 15 Jul 2021 16:26:00 -0700 In-Reply-To: <20210715232603.3415111-1-emilyshaffer@google.com> Message-Id: <20210715232603.3415111-7-emilyshaffer@google.com> Mime-Version: 1.0 References: <20210715232603.3415111-1-emilyshaffer@google.com> X-Mailer: git-send-email 2.32.0.402.g57bb445576-goog Subject: [PATCH 6/9] 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 = " lines. Hooks will be run in config order. For example: $ git config --list | grep ^hook hook.pre-commit.command=~/bar.sh $ 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 | 16 +++- builtin/hook.c | 2 +- hook.c | 109 ++++++++++++++++++++--- hook.h | 2 +- t/t1360-config-based-hooks.sh | 159 ++++++++++++++++++++++++++++++++++ 6 files changed, 278 insertions(+), 15 deletions(-) create mode 100755 t/t1360-config-based-hooks.sh 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 8bf82b5dd4..8e2ab724e8 100644 --- a/Documentation/git-hook.txt +++ b/Documentation/git-hook.txt @@ -18,12 +18,24 @@ 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. +This command parses the default configuration files for sections like `hook +""`. `hook` is used to describe the commands which will be run during +a particular hook event; commands are run in the order Git encounters them +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 ` - 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.pre-commit.command ~/my-script.sh`. + 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 b08f9c9c4f..c54b0a4d13 100644 --- a/builtin/hook.c +++ b/builtin/hook.c @@ -51,7 +51,7 @@ 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->command); } clear_hook_list(head); diff --git a/hook.c b/hook.c index e1cf035022..ed90edcad7 100644 --- a/hook.c +++ b/hook.c @@ -12,6 +12,51 @@ static void free_hook(struct hook *ptr) free(ptr); } +/* + * Walks the linked list at 'head' to check if any hook running 'command' + * already exists. Returns a pointer to that hook if so, otherwise returns NULL. + */ +static struct hook * find_hook_by_command(struct list_head *head, const char *command) +{ + 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->command, command)) { + 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. + * Returns a handle to the hook in case more modification is needed. Do not free + * the returned handle. + */ +static struct hook * append_or_move_hook(struct list_head *head, const char *command) +{ + /* check if the hook is already in the list */ + struct hook *to_add = find_hook_by_command(head, command); + + if (!to_add) { + /* adding a new hook, not moving an old one */ + to_add = xmalloc(sizeof(*to_add)); + to_add->command = command; + to_add->feed_pipe_cb_data = NULL; + /* This gets overwritten in hook_list() for hookdir hooks. */ + to_add->from_hookdir = 0; + } + + list_add_tail(&to_add->list, head); + + return to_add; +} + static void remove_hook(struct list_head *to_remove) { struct hook *hook_to_remove = list_entry(to_remove, struct hook, list); @@ -116,30 +161,69 @@ struct hook_config_cb struct list_head *list; }; +/* + * Callback for git_config which adds configured hooks to a hook list. + * Hooks can be configured by specifying hook..command, for example, + * hook.pre-commit.command = echo "pre-commit hook!" + */ +static int hook_config_lookup(const char *key, const char *value, void *cb_data) +{ + struct hook_config_cb *data = cb_data; + const char *hook_key = data->hook_key->buf; + struct list_head *head = data->list; + + if (!strcmp(key, hook_key)) { + const char *command = value; + struct strbuf hookcmd_name = STRBUF_INIT; + + + if (!command) { + strbuf_release(&hookcmd_name); + BUG("git_config_get_value overwrote a string it shouldn't have"); + } + + /* TODO: implement skipping hooks */ + + /* TODO: immplement hook aliases */ + + /* + * TODO: implement an option-getting callback, e.g. + * get configs by pattern hookcmd.$value.* + * for each key+value, do_callback(key, value, cb_data) + */ + append_or_move_hook(head, command); + + strbuf_release(&hookcmd_name); + } + + 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 strbuf hook_key = STRBUF_INIT; + struct hook_config_cb cb_data = { &hook_key, hook_head }; INIT_LIST_HEAD(hook_head); if (!hookname) return NULL; + /* Add the hooks from the config, e.g. hook.pre-commit.command */ + strbuf_addf(&hook_key, "hook.%s.command", hookname); + git_config(hook_config_lookup, &cb_data); + + 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)); - to_add->hook_path = hook_path; - to_add->feed_pipe_cb_data = NULL; - to_add->from_hookdir = 1; - list_add_tail(&to_add->list, hook_head); - } + if (hook_path) + append_or_move_hook(hook_head, hook_path)->from_hookdir = 1; return hook_head; } @@ -220,11 +304,14 @@ 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 */ + cp->use_shell = !run_me->from_hookdir; + /* add command */ if (run_me->from_hookdir && hook_cb->options->absolute_path) - strvec_push(&cp->args, absolute_path(run_me->hook_path)); + strvec_push(&cp->args, absolute_path(run_me->command)); else - strvec_push(&cp->args, run_me->hook_path); + strvec_push(&cp->args, run_me->command); /* * add passed-in argv, without expanding - let the user get back @@ -255,7 +342,7 @@ static int notify_start_failure(struct strbuf *out, hook_cb->rc |= 1; strbuf_addf(out, _("Couldn't start hook '%s'\n"), - attempted->hook_path); + attempted->command); return 1; } diff --git a/hook.h b/hook.h index 2559232880..e8cd6b7c67 100644 --- a/hook.h +++ b/hook.h @@ -28,7 +28,7 @@ int hook_exists(const char *hookname); struct hook { struct list_head list; /* The path to the hook */ - const char *hook_path; + const char *command; unsigned from_hookdir : 1; diff --git a/t/t1360-config-based-hooks.sh b/t/t1360-config-based-hooks.sh new file mode 100755 index 0000000000..12fca516ec --- /dev/null +++ b/t/t1360-config-based-hooks.sh @@ -0,0 +1,159 @@ +#!/bin/bash + +test_description='config-managed multihooks, including git-hook command' + +. ./test-lib.sh + +ROOT= +if test_have_prereq MINGW +then + # In Git for Windows, Unix-like paths work only in shell scripts; + # `git.exe`, however, will prefix them with the pseudo root directory + # (of the Unix shell). Let's accommodate for that. + ROOT="$(cd / && pwd)" +fi + +setup_hooks () { + test_config hook.pre-commit.command "/path/ghi" --add + test_config_global hook.pre-commit.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 rejects commands without a mode' ' + test_must_fail git hook pre-commit +' + +test_expect_success 'git hook rejects commands without a hookname' ' + test_must_fail git hook list +' + +test_expect_success 'git hook list orders by config order' ' + setup_hooks && + + cat >expected <<-EOF && + $ROOT/path/def + $ROOT/path/ghi + EOF + + git hook list pre-commit >actual && + test_cmp expected actual +' + +test_expect_success 'git hook list reorders on duplicate commands' ' + setup_hooks && + + test_config hook.pre-commit.command "/path/def" --add && + + cat >expected <<-EOF && + $ROOT/path/ghi + $ROOT/path/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 && + .git/hooks/pre-commit + EOF + + git hook list pre-commit >actual && + test_cmp expected actual +' + + +test_expect_success 'inline hook definitions execute oneliners' ' + test_config hook.pre-commit.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.pre-commit.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 'git hook run can pass args' ' + write_script sample-hook.sh <<-\EOF && + echo $1 + echo $2 + EOF + + test_config hook.pre-commit.command "\"$(pwd)/sample-hook.sh\"" && + + cat >expected <<-EOF && + arg1 + arg2 + EOF + + git hook run pre-commit -- arg1 arg2 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 'out-of-repo runs excluded' ' + setup_hooks && + + nongit test_must_fail git hook run pre-commit +' + +test_expect_success 'stdin to multiple hooks' ' + git config --add hook.test.command "xargs -P1 -I% echo a%" && + git config --add hook.test.command "xargs -P1 -I% echo b%" && + test_when_finished "test_unconfig hook.test.command" && + + cat >input <<-EOF && + 1 + 2 + 3 + EOF + + cat >expected <<-EOF && + a1 + a2 + a3 + b1 + b2 + b3 + EOF + + git hook run --to-stdin=input test 2>actual && + test_cmp expected actual +' + +test_done From patchwork Thu Jul 15 23:26:01 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Emily Shaffer X-Patchwork-Id: 12381241 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 CEA14C636CB for ; Thu, 15 Jul 2021 23:26:39 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id B65AB613DA for ; Thu, 15 Jul 2021 23:26:39 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232427AbhGOX3b (ORCPT ); Thu, 15 Jul 2021 19:29:31 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:36816 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231887AbhGOX3T (ORCPT ); Thu, 15 Jul 2021 19:29:19 -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 5177BC061767 for ; Thu, 15 Jul 2021 16:26:24 -0700 (PDT) Received: by mail-yb1-xb49.google.com with SMTP id j186-20020a25d2c30000b029055ed6ffbea6so9873718ybg.14 for ; Thu, 15 Jul 2021 16:26:24 -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=wS6wYXsXJOg3Yo6b2J3wujqVfHfRd2gSIngVFmoCgmU=; b=jBDIsN5/2CYTgWpcNtxAKBk/JxA7u32E6dk2Q4tO9I2KdZmfCEmhiR/fDbXhKiOZ7K dH1nOBMUrVvzP/b/2WJmOumAAH1ru8Ov9wa0YlnaIE3Oec+a5IuZmVbl3IvrOHEakAAW fYTe/1EOThivxXUHMkrin3eRxaGe0+uswzNSSwKSR4YHA1zi+bYC6Ntw0Zix+xsPsy3n qpS/opTpRpB2Y+K+EM7f7BO2S0+Pw9QYBp57duKFj+VT3r67doj6nIPAE/y/ylDqhepL IB6RVn8FtVAkJZBuep0xZOKPDUrT5KCgVEr/+LEOaypHBBIWGSuOOGLiHbpoXFV5LQe0 PbHg== 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=wS6wYXsXJOg3Yo6b2J3wujqVfHfRd2gSIngVFmoCgmU=; b=G1MOZohgAPCwuOFdR9QJPqzHjpDUE6Fgwp5pfGfHUrQKsVP+j0PLWNf8qGLWsLpLLN Pkrr2Qfmx2ajcuv6ionXsXoU7EjjezeSrMFAoD4Lr0pNPGRlV3z30V3KO1vjNIQOA/rF nf3Jt32u/g3bVRoEi03JnseD/jHb/Bk1tMoAcI2Itxp4V4yiwH3rH2Frexy0GOIiYtpU 4LLCwLOlgZTwPaxqkgsVTm1R9/vn9IA414Hvw5pKmDItk83g1b3lRhrKcJQ3lAbPzDNs NQfj5UqWLVAN5O6W93VcJlXdBQ2I5OV9bjwgfKJwtH1G4l56dibBTTz1ttS0ZMzh7Dfs 45qg== X-Gm-Message-State: AOAM5304Ip/bo8RI3CSJgPBGB7A5+q+I5VxL9sFrd4j1IiZLOdKF32NB +m4eDbVgVb1PZPmmxetlWByaKobgpBDVvZqT+bL6oW+CxXF+QH0fgHwPHwCcSdUnsHbXNTdtqSm +wZk4qCOgtgRWcEY5ceGGXrWe0R4psB5Kt5mINpzqbP9ieJSdY9s2SUzdg9cUm2VZtXPMXWlh2Q == X-Google-Smtp-Source: ABdhPJzTOQS1J2QXnB3qlZqDFikELTJ/RSD+oYPYsXJZxfyT82O88LOXbLWCscohxpxEClgDSbCB2zAY2jBXSR+EYLI= X-Received: from podkayne.svl.corp.google.com ([2620:15c:2ce:200:c795:6596:6e89:ce2]) (user=emilyshaffer job=sendgmr) by 2002:a25:bd4f:: with SMTP id p15mr8879313ybm.338.1626391583498; Thu, 15 Jul 2021 16:26:23 -0700 (PDT) Date: Thu, 15 Jul 2021 16:26:01 -0700 In-Reply-To: <20210715232603.3415111-1-emilyshaffer@google.com> Message-Id: <20210715232603.3415111-8-emilyshaffer@google.com> Mime-Version: 1.0 References: <20210715232603.3415111-1-emilyshaffer@google.com> X-Mailer: git-send-email 2.32.0.402.g57bb445576-goog Subject: [PATCH 7/9] 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 | 18 ++++++++++-------- t/t1360-config-based-hooks.sh | 13 +++++++++++++ 3 files changed, 24 insertions(+), 9 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 ed90edcad7..b08b876d5d 100644 --- a/hook.c +++ b/hook.c @@ -202,7 +202,6 @@ static int hook_config_lookup(const char *key, const char *value, void *cb_data) 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 strbuf hook_key = STRBUF_INIT; struct hook_config_cb cb_data = { &hook_key, hook_head }; @@ -216,14 +215,17 @@ struct list_head* hook_list(const char* hookname, int allow_unknown) git_config(hook_config_lookup, &cb_data); - if (allow_unknown) - hook_path = find_hook_gently(hookname); - else - hook_path = find_hook(hookname); + if (have_git_dir()) { + const char *hook_path; + if (allow_unknown) + hook_path = find_hook_gently(hookname); + else + hook_path = find_hook(hookname); - /* Add the hook from the hookdir */ - if (hook_path) - append_or_move_hook(hook_head, hook_path)->from_hookdir = 1; + /* Add the hook from the hookdir */ + if (hook_path) + append_or_move_hook(hook_head, hook_path)->from_hookdir = 1; + } return hook_head; } diff --git a/t/t1360-config-based-hooks.sh b/t/t1360-config-based-hooks.sh index 12fca516ec..e4a7b06ad1 100755 --- a/t/t1360-config-based-hooks.sh +++ b/t/t1360-config-based-hooks.sh @@ -34,6 +34,19 @@ test_expect_success 'git hook rejects commands without a hookname' ' test_must_fail git hook list ' +test_expect_success 'git hook runs outside of a repo' ' + setup_hooks && + + cat >expected <<-EOF && + $ROOT/path/def + EOF + + nongit git config --list --global && + + nongit git hook list pre-commit >actual && + test_cmp expected actual +' + test_expect_success 'git hook list orders by config order' ' setup_hooks && From patchwork Thu Jul 15 23:26:02 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Emily Shaffer X-Patchwork-Id: 12381237 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 A40D5C636C9 for ; Thu, 15 Jul 2021 23:26:37 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 8BC36613DC for ; Thu, 15 Jul 2021 23:26:37 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232492AbhGOX3a (ORCPT ); Thu, 15 Jul 2021 19:29:30 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:36810 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232269AbhGOX3U (ORCPT ); Thu, 15 Jul 2021 19:29:20 -0400 Received: from mail-yb1-xb4a.google.com (mail-yb1-xb4a.google.com [IPv6:2607:f8b0:4864:20::b4a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 82EB3C061760 for ; Thu, 15 Jul 2021 16:26:26 -0700 (PDT) Received: by mail-yb1-xb4a.google.com with SMTP id x15-20020a25ce0f0000b029055bb0981111so9986171ybe.7 for ; Thu, 15 Jul 2021 16:26:26 -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=eKB7ODy10YLtEQDpCWNy+G5xovtxFYrb/dABv3sbur0=; b=F/8PUE7FVblM83+eqYzfK3t5CsaBHNBlU/QbAttbNvVuswqrDR0ySY+xUuJSyfRRA4 fHd9FlhRu1C1V7mDMmJI0mudFP5tdz0eXmfFOeUlx2DAsKtRyukUgeTe7U+iQMyLVOTP lnI7rHKvWYIw1tiGDfjjqRMfgE3KgA8EzX1x5HsEU+Uw9ejZtW41A17jdrP2hKkkKzSn GlsOdT//ETwFFz4bhs2A99Q022azLUA8IQ47L7l0m6cZa+71ygC7XfJjKl6zOaHe9smW 4CRIrUyyhC5DBkGII7r9kSYSW7vyG9etMaKDdu0DorAt1KtxpTEJXTDe0Q8E/53LPn1J doJg== 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=eKB7ODy10YLtEQDpCWNy+G5xovtxFYrb/dABv3sbur0=; b=PY53YVhOGL6pz9Caoe8A565z4LPqDpcockr1jguGKmngSEo/vPS2VuZhnB+CPamInj wIt9kYGnD2KP3HA4FnxzgQSyzqgyLo9mwBuFAi8IDI4qsNZwnKemAjJbz53pi3iQn8zw czPVaLvQJhW55FEs7mxChJWy11FMJXBVjU7at349egNY9SueFxsvUYNekpOI1murZJdQ AtlXpuZiieAwr3g6Iwb9V98QpqE71UUNnESm6n1lx2MYXqj+YZper2qrZcNi9jNhp/Xn qqcpO8TcbPEbANUAwKcPyc52U+g9lFZXgkOoHB/euTVKz3v4ge9rfB8Y1P6/mfjnstrH z2ng== X-Gm-Message-State: AOAM530//m5enzMEe2du/4YT0MRX+4qr+44Bo7FTrLWJLX/Dy/tokHDM d/pANfh79kqw2UidU+AVd/66p9bkqxGgkNJ/wqwaNWVUSOBFJCXcjoulDKLfh92tw8ngHgcsaWR QlTcFurLV5JbdZkgu2bunMkqQvU4NgVqfxh3jkNL5zuub1et/T+B/f0CN0drygxrA+vcpVaZ4Ng == X-Google-Smtp-Source: ABdhPJz3Wpwh9z6ZHBlOBRdKWP+Rz82WqqIr9CS5rlYcwynXzMq1kHKYalZL+IN9cJO8oxmdyMY4+m94ec+XKVVzmxg= X-Received: from podkayne.svl.corp.google.com ([2620:15c:2ce:200:c795:6596:6e89:ce2]) (user=emilyshaffer job=sendgmr) by 2002:a25:2c8a:: with SMTP id s132mr9652405ybs.148.1626391585639; Thu, 15 Jul 2021 16:26:25 -0700 (PDT) Date: Thu, 15 Jul 2021 16:26:02 -0700 In-Reply-To: <20210715232603.3415111-1-emilyshaffer@google.com> Message-Id: <20210715232603.3415111-9-emilyshaffer@google.com> Mime-Version: 1.0 References: <20210715232603.3415111-1-emilyshaffer@google.com> X-Mailer: git-send-email 2.32.0.402.g57bb445576-goog Subject: [PATCH 8/9] hook: teach 'hookcmd' config to alias hook scripts From: Emily Shaffer To: git@vger.kernel.org Cc: Emily Shaffer Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org To enable fine-grained options which apply to a single hook executable, and to make it easier for a single executable to be run on multiple hook events, teach "hookcmd..config". These can be configured as follows: [hookcmd.my-linter] command = ~/my-linter.sh [hook.pre-commit] command = my-linter During the config parse, we can attempt to dereference the 'hook.pre-commit.command' string 'my-linter' and check if it matches any hookcmd names; if so, we can run the command associated with that hookcmd alias instead. Signed-off-by: Emily Shaffer --- Documentation/config/hook.txt | 5 +++++ Documentation/git-hook.txt | 42 +++++++++++++++++++++++++++++++---- hook.c | 9 +++++++- t/t1360-config-based-hooks.sh | 19 ++++++++++++++++ 4 files changed, 70 insertions(+), 5 deletions(-) diff --git a/Documentation/config/hook.txt b/Documentation/config/hook.txt index a97b980cca..5b35170664 100644 --- a/Documentation/config/hook.txt +++ b/Documentation/config/hook.txt @@ -3,6 +3,11 @@ hook..command:: executable on your device, a oneliner for your shell, or the name of a hookcmd. See linkgit:git-hook[1]. +hookcmd..command:: + A command to execute during a hook for which has been specified + as a command. This can be an executable on your device or a oneliner for + your shell. 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 8e2ab724e8..1a4d22fd90 100644 --- a/Documentation/git-hook.txt +++ b/Documentation/git-hook.txt @@ -18,10 +18,44 @@ 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. -This command parses the default configuration files for sections like `hook -""`. `hook` is used to describe the commands which will be run during -a particular hook event; commands are run in the order Git encounters them -during the configuration parse (see linkgit:git-config[1]). +This command parses the default configuration files for sections `hook` and +`hookcmd`. `hook` is used to describe the commands which will be run during a +particular hook event; commands are run in the order Git encounters them during +the configuration parse (see linkgit:git-config[1]). `hookcmd` is used to +describe attributes of a specific command. If additional attributes don't need +to be specified, a command to run can be specified directly in the `hook` +section; if a `hookcmd` by that name isn't found, Git will attempt to run the +provided value directly. For example: + +Global config +---- + [hook "post-commit"] + command = "linter" + command = "~/typocheck.sh" + + [hookcmd "linter"] + command = "/bin/linter --c" +---- + +Local config +---- + [hook "prepare-commit-msg"] + command = "linter" + [hook "post-commit"] + command = "python ~/run-test-suite.py" +---- + +With these configs, you'd then run post-commit hooks in this order: + + /bin/linter --c + ~/typocheck.sh + python ~/run-test-suite.py + .git/hooks/post-commit (if present) + +and prepare-commit-msg hooks in this order: + + /bin/linter --c + .git/hooks/prepare-commit-msg (if present) In general, when instructions suggest adding a script to `.git/hooks/`, you can specify it in the config instead by running diff --git a/hook.c b/hook.c index b08b876d5d..21904d90f6 100644 --- a/hook.c +++ b/hook.c @@ -184,7 +184,14 @@ static int hook_config_lookup(const char *key, const char *value, void *cb_data) /* TODO: implement skipping hooks */ - /* TODO: immplement hook aliases */ + /* + * Check if a hookcmd with that name exists. If it doesn't, + * 'git_config_get_value()' is documented not to touch &command, + * so we don't need to do anything. + */ + strbuf_reset(&hookcmd_name); + strbuf_addf(&hookcmd_name, "hookcmd.%s.command", command); + git_config_get_value(hookcmd_name.buf, &command); /* * TODO: implement an option-getting callback, e.g. diff --git a/t/t1360-config-based-hooks.sh b/t/t1360-config-based-hooks.sh index e4a7b06ad1..50ee824f05 100755 --- a/t/t1360-config-based-hooks.sh +++ b/t/t1360-config-based-hooks.sh @@ -18,6 +18,11 @@ setup_hooks () { test_config_global hook.pre-commit.command "/path/def" --add } +setup_hookcmd () { + test_config hook.pre-commit.command "abc" --add + test_config_global hookcmd.abc.command "/path/abc" --add +} + setup_hookdir () { mkdir .git/hooks write_script .git/hooks/pre-commit <<-EOF @@ -59,6 +64,20 @@ test_expect_success 'git hook list orders by config order' ' test_cmp expected actual ' +test_expect_success 'git hook list dereferences a hookcmd' ' + setup_hooks && + setup_hookcmd && + + cat >expected <<-EOF && + $ROOT/path/def + $ROOT/path/ghi + $ROOT/path/abc + EOF + + git hook list pre-commit >actual && + test_cmp expected actual +' + test_expect_success 'git hook list reorders on duplicate commands' ' setup_hooks && From patchwork Thu Jul 15 23:26:03 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Emily Shaffer X-Patchwork-Id: 12381239 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 7B7CBC636CD for ; Thu, 15 Jul 2021 23:26:40 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 60E24613DF for ; Thu, 15 Jul 2021 23:26:40 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232437AbhGOX3d (ORCPT ); Thu, 15 Jul 2021 19:29:33 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:36838 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232419AbhGOX3W (ORCPT ); Thu, 15 Jul 2021 19:29:22 -0400 Received: from mail-yb1-xb4a.google.com (mail-yb1-xb4a.google.com [IPv6:2607:f8b0:4864:20::b4a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id C584BC061762 for ; Thu, 15 Jul 2021 16:26:28 -0700 (PDT) Received: by mail-yb1-xb4a.google.com with SMTP id k32-20020a25b2a00000b0290557cf3415f8so10057302ybj.1 for ; Thu, 15 Jul 2021 16:26:28 -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=sfGN5CTRkFopdvhgJTzA+sL1lypZEXjczBizc0Qv1sE=; b=nbZHoVlzouecjnUrSdSzeCr6sXr8Ax14DjhIQPmqMgklMkYn1yFo+sNkRz0x3mBh0m PnGSLwI3lxe+N82T1Bpix7As1VB/RmCQZAO0ow+bXcaqaOwHspKYIZfKaWLnozie3oBn 4cFukCCFR/e72K6rXhnSf6FUbP6n69e4hMdNJqCGnKAdb1wqba+VEXudZWXGI45Aqrrm Trg9A7ogERhjvB6bQ6l1Z7W3NoRWSkrdi8Z0yCvCSzMIaI69GwHD0D4bZeh4h/DbrDgJ CUvWqoUpkP3u1ft4u3X2Qlncfidupewga16uy8XKcgzEsO0FxXpDE6e4Eb7bogJLd86K sRBg== 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=sfGN5CTRkFopdvhgJTzA+sL1lypZEXjczBizc0Qv1sE=; b=joLTfQoMZ0JPbxZgFdi3myqDPqTWlVcJc7GCK/XvZ+0EvQ/ZmYkDYlTIYnSxxM1mWW UrOtweouCS4Wr9k+61uct3OOULVl3JsjI+hye7KYrDtuxI/AuBGsHRdrW1TRuCMaG990 WG4SXpBqt7suO8JN4VZ/b8SqYA7TCSGTBFEQlS/gH9OywkuHzSeO7PS6ZnvOpf2KwyOi 7lBM/mb17VIaXDM7nWChMdos7LHPCCuiotVHSJVjN1SR8h/zLR++qfF6jQ2ZS5wfMPEm puasKe6KDW//B0bxLoN30bh+n1gCk0cCLBF3WWORAFz7vV8uYjy8A0DMlTpGIJH8khvU wFLA== X-Gm-Message-State: AOAM530QyCiw4zoodBmqDOgGlHAzAWMS1HczIfwa9ka3C7zVnvM3eS9A yOyCPmtkXz3QVHN69ssEwUMIKkAoFN2DR5x0u6N9VjYAN3NH6ykCMzEhUQVjD+DbCt/qg5Uwne5 1Z0XOvFLfJjjVGtSwYQcgj1aJWK8nSfurmRRiKSVk+LGap7KsWkGh2e5GJc+DmWuSVaNw/Fz7dA == X-Google-Smtp-Source: ABdhPJwhBzYUSWN6ZtQ2+Xo7xPVmsSzbaKl8OKtAx/cDK6jdCOeSqW4yHKbr5VYSw7gfBWuvalNl9JlKhAB1KQLzBWk= X-Received: from podkayne.svl.corp.google.com ([2620:15c:2ce:200:c795:6596:6e89:ce2]) (user=emilyshaffer job=sendgmr) by 2002:a25:c054:: with SMTP id c81mr9103095ybf.26.1626391587934; Thu, 15 Jul 2021 16:26:27 -0700 (PDT) Date: Thu, 15 Jul 2021 16:26:03 -0700 In-Reply-To: <20210715232603.3415111-1-emilyshaffer@google.com> Message-Id: <20210715232603.3415111-10-emilyshaffer@google.com> Mime-Version: 1.0 References: <20210715232603.3415111-1-emilyshaffer@google.com> X-Mailer: git-send-email 2.32.0.402.g57bb445576-goog Subject: [PATCH 9/9] hook: implement hookcmd..skip From: Emily Shaffer To: git@vger.kernel.org Cc: Emily Shaffer Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org If a user wants a specific repo to skip execution of a hook which is set at a global or system level, they will be able to do so by specifying 'skip' in their repo config: ~/.gitconfig [hook.pre-commit] command = skippable-oneliner command = skippable-hookcmd [hookcmd.skippable-hookcmd] command = foo.sh $GIT_DIR/.git/config [hookcmd.skippable-oneliner] skip = true [hookcmd.skippable-hookcmd] skip = true Later it may make sense to add an option like "hookcmd..-skip" - but for simplicity, let's start with a universal skip setting like this. Signed-off-by: Emily Shaffer --- Documentation/config/hook.txt | 8 ++++++++ Documentation/git-hook.txt | 30 +++++++++++++++++++++++++++++ hook.c | 31 +++++++++++++++++++++--------- t/t1360-config-based-hooks.sh | 36 +++++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 9 deletions(-) diff --git a/Documentation/config/hook.txt b/Documentation/config/hook.txt index 5b35170664..6b3776f06f 100644 --- a/Documentation/config/hook.txt +++ b/Documentation/config/hook.txt @@ -8,6 +8,14 @@ hookcmd..command:: as a command. This can be an executable on your device or a oneliner for your shell. See linkgit:git-hook[1]. +hookcmd..skip:: + Specify this boolean to remove a command from earlier in the execution + order. Useful if you want to make a single repo an exception to hook + configured at the system or global scope. If there is no hookcmd + specified for the command you want to skip, you can use the value of + `hook..command` as as a shortcut. The "skip" setting + must be specified after the "hook..command" to have an effect. + 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 1a4d22fd90..fcd13da4ff 100644 --- a/Documentation/git-hook.txt +++ b/Documentation/git-hook.txt @@ -57,6 +57,36 @@ and prepare-commit-msg hooks in this order: /bin/linter --c .git/hooks/prepare-commit-msg (if present) +If there is a command you wish to run in most cases but have one or two +exceptional repos where it should be skipped, you can specify +`hookcmd..skip`, for example: + +System config +---- + [hook "post-commit"] + command = check-for-secrets + + [hookcmd "check-for-secrets"] + command = /bin/secret-checker --aggressive +---- + +Local config +---- + [hookcmd "check-for-secrets"] + skip = true + # This works for inlined hook commands, too: + [hookcmd "~/typocheck.sh"] + skip = true +---- + +After these configs are added, and including the earlier example configs, the +hook list becomes: + +post-commit: + /bin/linter --c + python ~/run-test-suite.py + .git/hooks/post-commit (if present) + 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 ` - this way you can diff --git a/hook.c b/hook.c index 21904d90f6..5faa1690e4 100644 --- a/hook.c +++ b/hook.c @@ -18,6 +18,7 @@ static void free_hook(struct hook *ptr) */ static struct hook * find_hook_by_command(struct list_head *head, const char *command) { + /* check if the hook is already in the list */ struct list_head *pos = NULL, *tmp = NULL; struct hook *found = NULL; @@ -40,7 +41,6 @@ static struct hook * find_hook_by_command(struct list_head *head, const char *co */ static struct hook * append_or_move_hook(struct list_head *head, const char *command) { - /* check if the hook is already in the list */ struct hook *to_add = find_hook_by_command(head, command); if (!to_add) { @@ -175,14 +175,15 @@ static int hook_config_lookup(const char *key, const char *value, void *cb_data) if (!strcmp(key, hook_key)) { const char *command = value; struct strbuf hookcmd_name = STRBUF_INIT; + int skip = 0; - - if (!command) { - strbuf_release(&hookcmd_name); - BUG("git_config_get_value overwrote a string it shouldn't have"); - } - - /* TODO: implement skipping hooks */ + /* + * Check if we're removing that hook instead. Hookcmds are + * removed by name, and inlined hooks are removed by command + * content. + */ + strbuf_addf(&hookcmd_name, "hookcmd.%s.skip", command); + git_config_get_bool(hookcmd_name.buf, &skip); /* * Check if a hookcmd with that name exists. If it doesn't, @@ -193,12 +194,24 @@ static int hook_config_lookup(const char *key, const char *value, void *cb_data) strbuf_addf(&hookcmd_name, "hookcmd.%s.command", command); git_config_get_value(hookcmd_name.buf, &command); + if (!command) { + strbuf_release(&hookcmd_name); + BUG("git_config_get_value overwrote a string it shouldn't have"); + } + /* * TODO: implement an option-getting callback, e.g. * get configs by pattern hookcmd.$value.* * for each key+value, do_callback(key, value, cb_data) */ - append_or_move_hook(head, command); + + if (skip) { + struct hook *to_remove = find_hook_by_command(head, command); + if (to_remove) + remove_hook(&(to_remove->list)); + } else { + append_or_move_hook(head, command); + } strbuf_release(&hookcmd_name); } diff --git a/t/t1360-config-based-hooks.sh b/t/t1360-config-based-hooks.sh index 50ee824f05..30dc7b6054 100755 --- a/t/t1360-config-based-hooks.sh +++ b/t/t1360-config-based-hooks.sh @@ -103,6 +103,42 @@ test_expect_success 'git hook list shows hooks from the hookdir' ' test_cmp expected actual ' +test_expect_success 'git hook list removes skipped hookcmd' ' + setup_hookcmd && + test_config hookcmd.abc.skip "true" --add && + + cat >expected <<-EOF && + no commands configured for hook '\''pre-commit'\'' + EOF + + git hook list pre-commit >actual && + test_cmp expected actual +' + +test_expect_success 'git hook list ignores skip referring to unused hookcmd' ' + test_config hookcmd.abc.command "/path/abc" --add && + test_config hookcmd.abc.skip "true" --add && + + cat >expected <<-EOF && + no commands configured for hook '\''pre-commit'\'' + EOF + + git hook list pre-commit >actual && + test_cmp expected actual +' + +test_expect_success 'git hook list removes skipped inlined hook' ' + setup_hooks && + test_config hookcmd."$ROOT/path/ghi".skip "true" --add && + + cat >expected <<-EOF && + $ROOT/path/def + EOF + + git hook list pre-commit >actual && + test_cmp expected actual +' + test_expect_success 'inline hook definitions execute oneliners' ' test_config hook.pre-commit.command "echo \"Hello World\"" &&