From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on dcvr.yhbt.net X-Spam-Level: X-Spam-Status: No, score=-11.5 required=3.0 tests=AWL,BAYES_00,DKIMWL_WL_MED, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS, MAILING_LIST_MULTI,SPF_HELO_PASS,SPF_PASS,USER_IN_DEF_DKIM_WL shortcircuit=no autolearn=ham autolearn_force=no version=3.4.2 Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by dcvr.yhbt.net (Postfix) with ESMTP id D20151F5AE for ; Tue, 28 Jul 2020 22:25:38 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729826AbgG1WZP (ORCPT ); Tue, 28 Jul 2020 18:25:15 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53064 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1729808AbgG1WZO (ORCPT ); Tue, 28 Jul 2020 18:25:14 -0400 Received: from mail-pl1-x64a.google.com (mail-pl1-x64a.google.com [IPv6:2607:f8b0:4864:20::64a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 5AF4AC061794 for ; Tue, 28 Jul 2020 15:25:14 -0700 (PDT) Received: by mail-pl1-x64a.google.com with SMTP id q5so12890013plr.14 for ; Tue, 28 Jul 2020 15:25:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20161025; h=date:in-reply-to:message-id:mime-version:references:subject:from:to :cc; bh=v0BLwiA/9uAYqtO/hRYI3SGgVidfr0QrVZQpy9WB9FE=; b=oNQgVfMS4+pSu1NBhplAo7THQeMBlgkFqbjnf99AnU+56DINSCZzigkEYDEOhx+7AB ENGy1a1DTKZZnSMbyYM+cTC3XpkXZhQghC5RyRjzLq9XluskHTz6A9OPl9rSRUbp+K5j fwY9amZaj1zu+Z2lMi5fiHA9GXj8nPZZPubSbtJCyS+3exXatLhsQGcOu7MaZv/IgwXg sN9INJyELOcHAHhHUgBYnqfHbXc74RKVeDERQuUX5n5kOqrG8d2aZaS2WPiWaB7KgYOf dvRxAj13n2ecx+1eISe5ZYJjEh4/2O5ZscqJeCcOrz7JUkgcAJyTe3zHTEEGd5KgpYCE VbAA== 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=v0BLwiA/9uAYqtO/hRYI3SGgVidfr0QrVZQpy9WB9FE=; b=f+NKHfz7TF/KGJIrER/V5ETl3eFmfd31yQ6TtBkKwxOEec07yfX4a6v4PyNGq75kkO yXt6DqgfKi2OmL+7i+CeBy78eKeT2Jvt4fkjtbCsLnh+Nm0qnbbpPPBN85l/tBAWuQuW 7JkF212K8jXfykELQ2RaxaqjmZdawpDy+qheHMHTY1AFiNp8o1MEeWwRBHp02MX/D/sl 5kiDVNCgqngC0na7aUOBlVa9upjnMIx28cKhXs7LPgyt0HAa4vYgiqA1B/66YD2w7B5g Cf9a01uNeVRlRlHwPuB9MOMcbTLP8GIutpC8qAk/kBrsVqy4mJ9UbaRulHfZ0joEv5hy 3pUA== X-Gm-Message-State: AOAM532OALlw7WnwCFUxf4GoMbfwYyaiSVxV68xldz7nJ9nKUo0lyPJT y/Az8WFOkpRB54hyQm32/pRZFWvDlx1B5juGmROtCUAauhQAH80m9f28ZnoZyBydK4TAdQ2FGFd sdULxdj+Thhvg6FD2oAIWH6KEsOgoCLeq1OmbHDaW/kS1zTVehXeM8XUtzP6VA8Ovy6QSzaeBrw == X-Google-Smtp-Source: ABdhPJz4ARA2RLYZXEmAcSEx3P0TQTlHH6C0aH6iBUcNCnI7wwLXdOslMKsp3N4JlPXBsEwojLtda4oyk7545FZznM8= X-Received: by 2002:a17:90b:349:: with SMTP id fh9mr6391507pjb.73.1595975113758; Tue, 28 Jul 2020 15:25:13 -0700 (PDT) Date: Tue, 28 Jul 2020 15:24:55 -0700 In-Reply-To: <20200728222455.3023400-1-emilyshaffer@google.com> Message-Id: <20200728222455.3023400-7-emilyshaffer@google.com> Mime-Version: 1.0 References: <20200728222455.3023400-1-emilyshaffer@google.com> X-Mailer: git-send-email 2.28.0.rc0.142.g3c755180ce-goog Subject: [RFC PATCH v3 6/6] hook: add 'run' subcommand From: Emily Shaffer To: git@vger.kernel.org Cc: Emily Shaffer Content-Type: text/plain; charset="UTF-8" Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org In order to enable hooks to be run as an external process, by a standalone Git command, or by tools which wrap Git, provide an external means to run all configured hook commands for a given hook event. For now, the hook commands will in config order, in series. As alternate ordering or parallelism is supported in the future, we should add knobs to use those to the command line as well. As with the legacy hook implementation, all stdout generated by hook commands is redirected to stderr. Piping from stdin is not yet supported. Legacy hooks (those present in $GITDIR/hooks) are run at the end of the execution list. For now, there is no way to disable them. Users may wish to provide hook commands like 'git config hook.pre-commit.command "~/linter.sh --pre-commit"'. To enable this, the contents of the 'hook.*.command' and 'hookcmd.*.command' strings are first split by space or quotes into an argv_array, then expanded with 'expand_user_path()'. Signed-off-by: Emily Shaffer --- builtin/hook.c | 30 +++++++++++++++++++++++++ hook.c | 42 +++++++++++++++++++++++++++++++++++ hook.h | 3 +++ t/t1360-config-based-hooks.sh | 28 +++++++++++++++++++++++ 4 files changed, 103 insertions(+) diff --git a/builtin/hook.c b/builtin/hook.c index 0d92124ca6..cd61fad5fb 100644 --- a/builtin/hook.c +++ b/builtin/hook.c @@ -5,9 +5,11 @@ #include "hook.h" #include "parse-options.h" #include "strbuf.h" +#include "argv-array.h" static const char * const builtin_hook_usage[] = { N_("git hook list "), + N_("git hook run [(-e|--env)=...] [(-a|--arg)=...] "), NULL }; @@ -62,6 +64,32 @@ static int list(int argc, const char **argv, const char *prefix) return 0; } +static int run(int argc, const char **argv, const char *prefix) +{ + struct strbuf hookname = STRBUF_INIT; + struct argv_array env_argv = ARGV_ARRAY_INIT; + struct argv_array arg_argv = ARGV_ARRAY_INIT; + + struct option run_options[] = { + OPT_ARGV_ARRAY('e', "env", &env_argv, N_("var"), + N_("environment variables for hook to use")), + OPT_ARGV_ARRAY('a', "arg", &arg_argv, N_("args"), + N_("argument to pass to hook")), + OPT_END(), + }; + + argc = parse_options(argc, argv, prefix, run_options, + builtin_hook_usage, 0); + + if (argc < 1) + usage_msg_opt(_("a hookname must be provided to operate on."), + builtin_hook_usage, run_options); + + strbuf_addstr(&hookname, argv[0]); + + return run_hooks(env_argv.argv, &hookname, &arg_argv); +} + int cmd_hook(int argc, const char **argv, const char *prefix) { struct option builtin_hook_options[] = { @@ -72,6 +100,8 @@ int cmd_hook(int argc, const char **argv, const char *prefix) if (!strcmp(argv[1], "list")) return list(argc - 1, argv + 1, prefix); + if (!strcmp(argv[1], "run")) + return run(argc - 1, argv + 1, prefix); usage_with_options(builtin_hook_usage, builtin_hook_options); } diff --git a/hook.c b/hook.c index 9dfc1a885e..902e213173 100644 --- a/hook.c +++ b/hook.c @@ -2,6 +2,7 @@ #include "hook.h" #include "config.h" +#include "run-command.h" static LIST_HEAD(hook_head); @@ -78,6 +79,7 @@ static int hook_config_lookup(const char *key, const char *value, void *hook_key struct list_head* hook_list(const struct strbuf* hookname) { struct strbuf hook_key = STRBUF_INIT; + const char *legacy_hook_path = NULL; if (!hookname) return NULL; @@ -86,5 +88,45 @@ struct list_head* hook_list(const struct strbuf* hookname) git_config(hook_config_lookup, (void*)hook_key.buf); + legacy_hook_path = find_hook(hookname->buf); + + /* TODO: check hook.runHookDir */ + if (legacy_hook_path) + emplace_hook(&hook_head, legacy_hook_path); + return &hook_head; } + +int run_hooks(const char *const *env, const struct strbuf *hookname, + const struct argv_array *args) +{ + struct list_head *to_run, *pos = NULL, *tmp = NULL; + int rc = 0; + + to_run = hook_list(hookname); + + list_for_each_safe(pos, tmp, to_run) { + struct child_process hook_proc = CHILD_PROCESS_INIT; + struct hook *hook = list_entry(pos, struct hook, list); + + /* add command */ + argv_array_push(&hook_proc.args, hook->command.buf); + + /* + * add passed-in argv, without expanding - let the user get back + * exactly what they put in + */ + if (args) + argv_array_pushv(&hook_proc.args, args->argv); + + hook_proc.env = env; + hook_proc.no_stdin = 1; + hook_proc.stdout_to_stderr = 1; + hook_proc.trace2_hook_name = hook->command.buf; + hook_proc.use_shell = 1; + + rc |= run_command(&hook_proc); + } + + return rc; +} diff --git a/hook.h b/hook.h index aaf6511cff..cf598d6ccb 100644 --- a/hook.h +++ b/hook.h @@ -1,6 +1,7 @@ #include "config.h" #include "list.h" #include "strbuf.h" +#include "argv-array.h" struct hook { @@ -10,6 +11,8 @@ struct hook }; struct list_head* hook_list(const struct strbuf *hookname); +int run_hooks(const char *const *env, const struct strbuf *hookname, + const struct argv_array *args); void free_hook(struct hook *ptr); void clear_hook_list(void); diff --git a/t/t1360-config-based-hooks.sh b/t/t1360-config-based-hooks.sh index ebf8f38d68..ee8114250d 100755 --- a/t/t1360-config-based-hooks.sh +++ b/t/t1360-config-based-hooks.sh @@ -84,4 +84,32 @@ test_expect_success 'git hook list --porcelain prints just the command' ' 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' ' + cat >~/sample-hook.sh <<-EOF && + echo \"Sample Hook\" + EOF + + test_when_finished "rm ~/sample-hook.sh" && + + chmod +x ~/sample-hook.sh && + + test_config hook.pre-commit.command "~/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_done -- 2.28.0.rc0.142.g3c755180ce-goog