From: "René Scharfe" <l.s.r@web.de>
To: "Ævar Arnfjörð Bjarmason" <avarab@gmail.com>
Cc: Git List <git@vger.kernel.org>,
Junio C Hamano <gitster@pobox.com>, Taylor Blau <me@ttaylorr.com>,
Jeff King <peff@peff.net>
Subject: Re: [PATCH 0/8] run-command: remove run_command_v_*()
Date: Fri, 28 Oct 2022 18:04:41 +0200 [thread overview]
Message-ID: <749f6adc-928a-0978-e3a1-2ede9f07def0@web.de> (raw)
In-Reply-To: <221028.86edusan0k.gmgdl@evledraar.gmail.com>
Am 27.10.22 um 23:46 schrieb Ævar Arnfjörð Bjarmason:
>
> On Thu, Oct 27 2022, René Scharfe wrote:
>
>> Replace the convenience functions run_command_v_opt() et. al. and use
>> struct child_process and run_command() directly instead, for an overall
>> code reduction and a simpler and more flexible API that allows creating
>> argument lists without magic numbers and reduced risk of memory leaks.
>>
>> This is a broken-out and polished version of the original scratch at
>> https://lore.kernel.org/git/9d924a5d-5c72-fbe6-270c-a8f6c5fc5850@web.de/
>>
>> Ævar Arnfjörð Bjarmason (1):
>> merge: remove always-the-same "verbose" arguments
>>
>> René Scharfe (7):
>> bisect--helper: factor out do_bisect_run()
>> use child_process members "args" and "env" directly
>> use child_process member "args" instead of string array variable
>> replace and remove run_command_v_opt_cd_env()
>> replace and remove run_command_v_opt_tr2()
>> replace and remove run_command_v_opt_cd_env_tr2()
>> replace and remove run_command_v_opt()
>
> Even though I had a an earlier alternate series series[1] for this I'd
> be happy to see this version go in. I left some comments here and there,
> but with/without a re-roll am happy to give this:
>
> Reviewed-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
>
> I think I would have just gone for this in the first place, but thought
> that people loved the convenience functions too much. It can be hard to
> judge sentiments in advance :)
>
> One reason I hadn't re-submitted something is that there were
> outstanding new conflicts with "seen", and I see from applying this
> topic & merging it that it conflicts somewhat heavily. Junio seems to be
> on-board with this though, so maybe he won't mind.
>
> I didn't see any glaring instances where this made things worse, so
> maybe we didn't need these convenience wrappers in the first place.
Right, and I think this is important.
>
> But from the earlier discussion it does seema bit like we tossed the
> very notion out because some didn't like the duplicating of struct
> members with the flags (which I also doen't like).
>
> So I came up with the below experiment on top, it's not an attempt to
> convert all callers, just a demo.
>
> Maybe you think some ideas here are worth using, I probably won't pursue
> it (but maybe as ideas for some other future API).
>
> It's a combination of stuff, some of which you might like, some not,
> namely:
>
> - Have the API work the same way, but just have a run_commandl(&opt,
> ...) in addition to the run_command(). It's just a trivial wrapper to
> push stuff into &cmd.args for you.
>
> - I saw a few callers that could have perhaps used a similarly trivial
> run_commandv(), but that doesn't benefit from LAST_ARG_MUST_BE_NULL,
> so I didn't bother.
I thought about that as well, but at least for me is probably a just a
case of loss aversion, which I have aplenty. run_command_v_opt() alone
isn't bad and patch 8 on its own probably wouldn't fly, so I mentally
cling to it. But without it the API is untangled and simpler. Only a
single way to specify flags and arguments, no shortcuts for special
combinations. Overall easier to use once we forget the old ways.
>
> - I wish C had a nicer syntax for not just declaring but squashing
> together compile_time bracketed lists (think set operations). But the
> below "CHILD_PROCESS_INIT_LIST" is a pretty good poor man's version.
>
> I see gcc/clang nicely give us safety rails for that with
> "-Woverride-init", for this sort of "opts struct with internal stuff,
> but also user options" I think it works out very nicely.
>
That's a nice and simple macro. I played with a gross variant à la
#define CHILD_PROCESS_INIT_EX(...) { .args = STRVEC_INIT, __VA_ARGS__ }
which would allow e.g.
struct child_process cmd = CHILD_PROCESS_INIT_EX(.git_cmd = 1);
Yours is better, but they share the downside of not actually saving any
lines of code..
> - We have quite a few callers that want "on error, die", so maybe we
> should have something like that "on_error" sooner than later.
We could add a die_on_error bit for that, or start_command_or_die() and
run_command_or_die() variants (there I go again, multiplying APIs..).
They could report the failed command, which a caller can't do because
the internal strvec is already cleared once it learns of the failure.
>
> On clever (but perhaps overly clever) thing I didn't use, but played
> with recently in another context, is that now with C99 you can also do:
>
> int run_commandl(struct child_process *cmd, ...);
> #define run_command(...) run_command_1(__VA_ARGS__, NULL)
>
> I.e. make the API itself support all of:
>
> run_command(&cmd);
> run_command(&cmd, "reboot");
> run_command(&cmd, "reboot", NULL);
>
> I haven't made up my mind on whether that's just overly clever, or
> outright insane :)
Neither, I'd say. By combining two operations it's less flexible than
a pure run method plus direct access to a strvec member. It's a
shortcut that may save a line but requires more effort to extend its
callers, e.g. to conditionally add a new argument.
>
> diff --git a/bisect.c b/bisect.c
> index ec7487e6836..ef4f80650f7 100644
> --- a/bisect.c
> +++ b/bisect.c
> @@ -740,9 +740,8 @@ enum bisect_error bisect_checkout(const struct object_id *bisect_rev,
> struct child_process cmd = CHILD_PROCESS_INIT;
>
> cmd.git_cmd = 1;
> - strvec_pushl(&cmd.args, "checkout", "-q",
> - oid_to_hex(bisect_rev), "--", NULL);
> - if (run_command(&cmd))
> + if (run_commandl(&cmd, "checkout", "-q",
> + oid_to_hex(bisect_rev), "--", NULL))
> /*
> * Errors in `run_command()` itself, signaled by res < 0,
> * and errors in the child process, signaled by res > 0
> diff --git a/builtin/am.c b/builtin/am.c
> index 20aea0d2487..3b7df32ce22 100644
> --- a/builtin/am.c
> +++ b/builtin/am.c
> @@ -2189,10 +2189,9 @@ static int show_patch(struct am_state *state, enum show_patch_type sub_mode)
> if (!is_null_oid(&state->orig_commit)) {
> struct child_process cmd = CHILD_PROCESS_INIT;
>
> - strvec_pushl(&cmd.args, "show", oid_to_hex(&state->orig_commit),
> - "--", NULL);
> cmd.git_cmd = 1;
> - return run_command(&cmd);
> + return run_commandl(&cmd, "show", oid_to_hex(&state->orig_commit),
> + "--", NULL);
> }
>
> switch (sub_mode) {
> diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c
> index 1d2ce8a0e12..087d21c614a 100644
> --- a/builtin/bisect--helper.c
> +++ b/builtin/bisect--helper.c
> @@ -764,12 +764,12 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, const char **a
> strbuf_read_file(&start_head, git_path_bisect_start(), 0);
> strbuf_trim(&start_head);
> if (!no_checkout) {
> - struct child_process cmd = CHILD_PROCESS_INIT;
> -
> - cmd.git_cmd = 1;
> - strvec_pushl(&cmd.args, "checkout", start_head.buf,
> - "--", NULL);
> - if (run_command(&cmd)) {
> + struct child_process cmd = {
> + CHILD_PROCESS_INIT_LIST,
> + .git_cmd = 1,
> + };
> + if (run_commandl(&cmd, "checkout", start_head.buf,
> + "--", NULL)) {
> res = error(_("checking out '%s' failed."
> " Try 'git bisect start "
> "<valid-branch>'."),
> @@ -1147,8 +1147,7 @@ static int do_bisect_run(const char *command)
>
> printf(_("running %s\n"), command);
> cmd.use_shell = 1;
> - strvec_push(&cmd.args, command);
> - return run_command(&cmd);
> + return run_commandl(&cmd, command, NULL);
> }
>
> static int verify_good(const struct bisect_terms *terms, const char *command)
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 0e4348686b6..80d09e0fbf1 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -655,7 +655,6 @@ static int git_sparse_checkout_init(const char *repo)
> {
> struct child_process cmd = CHILD_PROCESS_INIT;
> int result = 0;
> - strvec_pushl(&cmd.args, "-C", repo, "sparse-checkout", "set", NULL);
>
> /*
> * We must apply the setting in the current process
> @@ -664,7 +663,7 @@ static int git_sparse_checkout_init(const char *repo)
> core_apply_sparse_checkout = 1;
>
> cmd.git_cmd = 1;
> - if (run_command(&cmd)) {
> + if (run_commandl(&cmd, "-C", repo, "sparse-checkout", "set", NULL)) {
> error(_("failed to initialize sparse-checkout"));
> result = 1;
> }
> @@ -868,13 +867,14 @@ static void dissociate_from_references(void)
> char *alternates = git_pathdup("objects/info/alternates");
>
> if (!access(alternates, F_OK)) {
> - struct child_process cmd = CHILD_PROCESS_INIT;
> -
> - cmd.git_cmd = 1;
> - cmd.no_stdin = 1;
> - strvec_pushl(&cmd.args, "repack", "-a", "-d", NULL);
> - if (run_command(&cmd))
> - die(_("cannot repack to clean up"));
> + struct child_process cmd = {
> + CHILD_PROCESS_INIT_LIST,
> + .git_cmd = 1,
> + .no_stdin = 1,
> + .on_error = CHILD_PROCESS_ON_ERROR_DIE,
> + };
> +
> + run_commandl(&cmd, "repack", "-a", "-d", NULL);
> if (unlink(alternates) && errno != ENOENT)
> die_errno(_("cannot unlink temporary alternates file"));
> }
> diff --git a/builtin/difftool.c b/builtin/difftool.c
> index d7f08c8a7fa..b4165b5a8ae 100644
> --- a/builtin/difftool.c
> +++ b/builtin/difftool.c
> @@ -44,11 +44,12 @@ static int difftool_config(const char *var, const char *value, void *cb)
>
> static int print_tool_help(void)
> {
> - struct child_process cmd = CHILD_PROCESS_INIT;
> -
> - cmd.git_cmd = 1;
> - strvec_pushl(&cmd.args, "mergetool", "--tool-help=diff", NULL);
> - return run_command(&cmd);
> + struct child_process cmd = {
> + CHILD_PROCESS_INIT_LIST,
> + .git_cmd = 1,
> + };
> +
> + return run_commandl(&cmd, "mergetool", "--tool-help=diff", NULL);
> }
>
> static int parse_index_info(char *p, int *mode1, int *mode2,
> diff --git a/git.c b/git.c
> index 6662548986f..93179f44f78 100644
> --- a/git.c
> +++ b/git.c
> @@ -724,7 +724,13 @@ static void handle_builtin(int argc, const char **argv)
>
> static void execv_dashed_external(const char **argv)
> {
> - struct child_process cmd = CHILD_PROCESS_INIT;
> + struct child_process cmd = {
> + CHILD_PROCESS_INIT_LIST,
> + .clean_on_exit = 1,
> + .wait_after_clean = 1,
> + .silent_exec_failure = 1,
> + .trace2_child_class = "dashed",
> + };
> int status;
>
> if (get_super_prefix())
> @@ -736,10 +742,6 @@ static void execv_dashed_external(const char **argv)
>
> strvec_pushf(&cmd.args, "git-%s", argv[0]);
> strvec_pushv(&cmd.args, argv + 1);
> - cmd.clean_on_exit = 1;
> - cmd.wait_after_clean = 1;
> - cmd.silent_exec_failure = 1;
> - cmd.trace2_child_class = "dashed";
>
> trace2_cmd_name("_run_dashed_");
>
> diff --git a/run-command.c b/run-command.c
> index 23e100dffc4..4b20aa1b577 100644
> --- a/run-command.c
> +++ b/run-command.c
> @@ -993,15 +993,45 @@ int finish_command_in_signal(struct child_process *cmd)
>
> int run_command(struct child_process *cmd)
> {
> - int code;
> + int starting = 1;
> + int code = 0;
>
> if (cmd->out < 0 || cmd->err < 0)
> BUG("run_command with a pipe can cause deadlock");
>
> code = start_command(cmd);
> if (code)
> + goto error;
> + starting = 0;
> + code = finish_command(cmd);
> + if (!code)
> + return 0;
> +error:
> + switch (cmd->on_error) {
> + case CHILD_PROCESS_ON_ERROR_DIE:
> + die(starting ?
> + _("start_command() for '%s' failed (error code %d)") :
> + _("'%s': got non-zero exit code '%d'"),
> + cmd->args.v[0], code);
> + break;
> + case CHILD_PROCESS_ON_ERROR_RETURN:
> return code;
> - return finish_command(cmd);
> + default:
> + BUG("unreachable");
> + }
> +}
> +
> +int run_commandl(struct child_process *cmd, ...)
> +{
> + va_list ap;
> + const char *arg;
> +
> + va_start(ap, cmd);
> + while ((arg = va_arg(ap, const char *)))
> + strvec_push(&cmd->args, arg);
> + va_end(ap);
> +
> + return run_command(cmd);
> }
>
> #ifndef NO_PTHREADS
> diff --git a/run-command.h b/run-command.h
> index fe2717ad11e..71e390350ed 100644
> --- a/run-command.h
> +++ b/run-command.h
> @@ -15,7 +15,22 @@
> * produces in the caller in order to process it.
> */
>
> +enum child_process_on_error {
> + /**
> + * Return a status code from run_command(). Set to 0 so that
> + * it'll be { 0 } init'd. If it's specified in a
> + * CHILD_PROCESS_INIT_LIST initialization we don't want a
> + * "-Woverride-init" warning.
> + */
> + CHILD_PROCESS_ON_ERROR_RETURN = 0,
>
> + /**
> + * die() with some sensible message if run_command() would
> + * have returned a non-zero exit code.
> + */
> + CHILD_PROCESS_ON_ERROR_DIE,
> +};
> +
> /**
> * This describes the arguments, redirections, and environment of a
> * command to run in a sub-process.
> @@ -42,6 +57,10 @@
> * redirected.
> */
> struct child_process {
> + /**
> + * Error behavior, see "enum child_process_on_error" above.
> + */
> + enum child_process_on_error on_error;
>
> /**
> * The .args is a `struct strvec', use that API to manipulate
> @@ -144,10 +163,11 @@ struct child_process {
> void (*clean_on_exit_handler)(struct child_process *process);
> };
>
> -#define CHILD_PROCESS_INIT { \
> +#define CHILD_PROCESS_INIT_LIST \
> + /* .on_error = CHILD_PROCESS_ON_ERROR_RETURN */ \
> .args = STRVEC_INIT, \
> - .env = STRVEC_INIT, \
> -}
> + .env = STRVEC_INIT
> +#define CHILD_PROCESS_INIT { CHILD_PROCESS_INIT_LIST }
>
> /**
> * The functions: child_process_init, start_command, finish_command,
> @@ -218,6 +238,14 @@ int finish_command_in_signal(struct child_process *);
> */
> int run_command(struct child_process *);
>
> +/**
> + * Like run_command() but takes a variable number of arguments, which
> + * will be appended with the equivalent of strvec_pushl(&cmd.args,
> + * ...) before invoking run_command().
> + */
> +LAST_ARG_MUST_BE_NULL
> +int run_commandl(struct child_process *cmd, ...);
> +
> /*
> * Trigger an auto-gc
> */
next prev parent reply other threads:[~2022-10-28 16:06 UTC|newest]
Thread overview: 49+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-10-27 16:30 [PATCH 0/8] run-command: remove run_command_v_*() René Scharfe
2022-10-27 16:35 ` [PATCH 1/8] merge: remove always-the-same "verbose" arguments René Scharfe
2022-10-29 18:12 ` Taylor Blau
2022-10-27 16:35 ` [PATCH 2/8] bisect--helper: factor out do_bisect_run() René Scharfe
2022-10-27 22:26 ` Ævar Arnfjörð Bjarmason
2022-10-29 18:16 ` Taylor Blau
2022-10-27 16:36 ` [PATCH 3/8] use child_process members "args" and "env" directly René Scharfe
2022-10-27 18:28 ` Junio C Hamano
2022-10-27 22:37 ` Ævar Arnfjörð Bjarmason
2022-10-27 16:37 ` [PATCH 4/8] use child_process member "args" instead of string array variable René Scharfe
2022-10-27 21:09 ` Ævar Arnfjörð Bjarmason
2022-10-28 14:23 ` René Scharfe
2022-10-29 18:30 ` Taylor Blau
2022-10-29 18:26 ` Taylor Blau
2022-10-27 16:38 ` [PATCH 5/8] replace and remove run_command_v_opt_cd_env() René Scharfe
2022-10-27 20:16 ` Ævar Arnfjörð Bjarmason
2022-10-29 19:26 ` Taylor Blau
2022-10-27 16:39 ` [PATCH 6/8] replace and remove run_command_v_opt_tr2() René Scharfe
2022-10-27 16:40 ` [PATCH 7/8] replace and remove run_command_v_opt_cd_env_tr2() René Scharfe
2022-10-27 22:46 ` Ævar Arnfjörð Bjarmason
2022-10-28 14:23 ` René Scharfe
2022-10-27 16:41 ` [PATCH 8/8] replace and remove run_command_v_opt() René Scharfe
2022-10-27 22:41 ` Ævar Arnfjörð Bjarmason
2022-10-28 14:23 ` René Scharfe
2022-10-27 20:11 ` [PATCH 0/8] run-command: remove run_command_v_*() Jeff King
2022-10-28 14:23 ` René Scharfe
2022-10-27 21:46 ` Ævar Arnfjörð Bjarmason
2022-10-28 16:04 ` René Scharfe [this message]
2022-10-28 16:11 ` Ævar Arnfjörð Bjarmason
2022-10-28 17:16 ` René Scharfe
2022-10-29 2:17 ` Ævar Arnfjörð Bjarmason
2022-10-29 10:05 ` René Scharfe
2022-10-29 19:32 ` Taylor Blau
2022-10-30 11:40 ` [PATCH v2 0/12] " René Scharfe
2022-10-30 11:42 ` [PATCH v2 01/12] merge: remove always-the-same "verbose" arguments René Scharfe
2022-10-30 11:45 ` [PATCH v2 02/12] run-command: fix return value comment René Scharfe
2022-10-30 11:46 ` [PATCH v2 03/12] am: simplify building "show" argument list René Scharfe
2022-10-30 11:47 ` [PATCH v2 04/12] bisect: simplify building "checkout" " René Scharfe
2022-10-30 11:48 ` [PATCH v2 05/12] bisect--helper: factor out do_bisect_run() René Scharfe
2022-10-30 11:49 ` [PATCH v2 06/12] sequencer: simplify building argument list in do_exec() René Scharfe
2022-10-30 11:50 ` [PATCH v2 07/12] use child_process member "args" instead of string array variable René Scharfe
2022-10-30 11:51 ` [PATCH v2 08/12] use child_process members "args" and "env" directly René Scharfe
2022-10-30 11:51 ` [PATCH v2 09/12] replace and remove run_command_v_opt_cd_env() René Scharfe
2022-10-30 11:52 ` [PATCH v2 10/12] replace and remove run_command_v_opt_tr2() René Scharfe
2022-10-30 11:53 ` [PATCH v2 11/12] replace and remove run_command_v_opt_cd_env_tr2() René Scharfe
2022-10-30 11:55 ` [PATCH v2 12/12] replace and remove run_command_v_opt() René Scharfe
2022-10-30 11:58 ` [PATCH v2 0/12] run-command: remove run_command_v_*() René Scharfe
2022-10-30 18:05 ` Taylor Blau
2022-10-31 13:35 ` Ævar Arnfjörð Bjarmason
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=749f6adc-928a-0978-e3a1-2ede9f07def0@web.de \
--to=l.s.r@web.de \
--cc=avarab@gmail.com \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=me@ttaylorr.com \
--cc=peff@peff.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).