{"thread":{"id":"56951","subject":"[PATCH v2] pager: fix crash when pager program doesn't exist","startedAt":"2021-11-20T19:41:07Z","lastAt":"2021-11-26T09:50:27Z","messageCount":71,"participants":["Enzo Matsumiya","Jeff King","Junio C Hamano","Ævar Arnfjörð Bjarmason","Eric Sunshine"],"isPatch":true,"patchVersion":2,"patchTotal":null},"messages":[{"id":"441860","messageId":"20211120194048.12125-1-ematsumiya@suse.de","threadId":"56951","inReplyTo":null,"subject":"[PATCH v2] pager: fix crash when pager program doesn't exist","fromName":"Enzo Matsumiya","fromEmail":"ematsumiya@suse.de","sentAt":"2021-11-20T19:40:48Z","receivedAt":"2021-11-20T19:41:07Z","isPatch":true,"sender":{"key":"ematsumiya@suse.de","avatar":"https://avatars.githubusercontent.com/u/45605263?v=4"},"body":"When prepare_cmd() fails for, e.g., pager process setup,\nchild_process_clear() frees the memory in pager_process.args, but .argv\nstill points to the previously location.\n\nWhen setup_pager() is called a second time, from cmd_log_init_finish()\nin this case, its strvec operations (i.e. using pager_process.argv) will\nlead to a use-after-free.\n\nThis patch makes sure that further uses of the child_process cleared by\nchild_process_clear() gets a properly initialized struct.\n\nReproducer:\n$ git config pager.show INVALID_PAGER\n$ git show $VALID_COMMIT\nerror: cannot run INVALID_PAGER: No such file or directory\n[1]    3619 segmentation fault (core dumped)  git show $VALID_COMMIT\n\nSigned-off-by: Enzo Matsumiya <ematsumiya@suse.de>\nReviewed-by: Jeff King <peff@peff.net>\n---\nChanges to v1:\n * Implement all of Jeff's suggestions:\n   - remove double frees to .argv\n   - discard the idea of falling back to DEFAULT_PAGER\n   - replace memset() in child_process_clear() by child_process_init()\n   - update/improve commit message\n\n run-command.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/run-command.c b/run-command.c\nindex f329391154ae..a7bf81025afb 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -19,6 +19,7 @@ void child_process_clear(struct child_process *child)\n {\n \tstrvec_clear(&child->args);\n \tstrvec_clear(&child->env_array);\n+\tchild_process_init(child);\n }\n \n struct child_to_clean {\n-- \n2.33.1\n\n"},{"id":"441888","messageId":"YZqSBlvzz2KgOMnJ@coredump.intra.peff.net","threadId":"56951","inReplyTo":"20211120194048.12125-1-ematsumiya@suse.de","subject":"Re: [PATCH v2] pager: fix crash when pager program doesn't exist","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-11-21T18:37:58Z","receivedAt":"2021-11-21T18:38:00Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sat, Nov 20, 2021 at 04:40:48PM -0300, Enzo Matsumiya wrote:\n\n> When prepare_cmd() fails for, e.g., pager process setup,\n> child_process_clear() frees the memory in pager_process.args, but .argv\n> still points to the previously location.\n\nMakes sense to introduce the root of the problem (.argv pointing to the\nwrong place). minor grammo: s/previously/previous/\n\n> When setup_pager() is called a second time, from cmd_log_init_finish()\n> in this case, its strvec operations (i.e. using pager_process.argv) will\n> lead to a use-after-free.\n\nAnd then this shows how the root problem triggers. There's one minor\ninaccuracy here. It's not the strvec operations which fail. It's that\nstart_command() will prefer an already-set pager_process.argv to looking\nat pager_process.args at all.\n\n> This patch makes sure that further uses of the child_process cleared by\n> child_process_clear() gets a properly initialized struct.\n> \n> Reproducer:\n> $ git config pager.show INVALID_PAGER\n> $ git show $VALID_COMMIT\n> error: cannot run INVALID_PAGER: No such file or directory\n> [1]    3619 segmentation fault (core dumped)  git show $VALID_COMMIT\n\nYep, this all makes sense.\n\nI think it may be squashing in a test, like this:\n\ndiff --git a/t/t7006-pager.sh b/t/t7006-pager.sh\nindex 0e7cf75435..013e5e35ca 100755\n--- a/t/t7006-pager.sh\n+++ b/t/t7006-pager.sh\n@@ -786,4 +786,9 @@ test_expect_success TTY 'git returns SIGPIPE on propagated signals from pager' '\n \ttest_path_is_file pager-used\n '\n \n+test_expect_success TTY 'handle multiple failed attempts to run pager' '\n+\ttest_config pager.log does-not-exist &&\n+\ttest_terminal git log\n+'\n+\n test_done\n\nThat shows that the fix works, and will help catch any regressions. Note\nthat checking for a successful exit code contradicts an earlier test in\nt7006. That's because that earlier test is wrong. There's some\ndiscussion in this thread:\n\n  https://lore.kernel.org/git/xmqq1r4b8ezp.fsf@gitster.g/\n\nI think we can ignore that for now and just add our new test.\n\n> Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>\n> Reviewed-by: Jeff King <peff@peff.net>\n\nWe'd usually leave of \"Reviewed-by\" until the reviewer has had a chance\nto see _this_ version of the patch. I.e., usually it would not be added\nby the submitter, but by the maintainer (unless you are resending\nverbatim a patch that already got review).\n\n> diff --git a/run-command.c b/run-command.c\n> index f329391154ae..a7bf81025afb 100644\n> --- a/run-command.c\n> +++ b/run-command.c\n> @@ -19,6 +19,7 @@ void child_process_clear(struct child_process *child)\n>  {\n>  \tstrvec_clear(&child->args);\n>  \tstrvec_clear(&child->env_array);\n> +\tchild_process_init(child);\n>  }\n\nAnd naturally I agree that the patch itself looks good. :)\n\nI like this is a minimal fix, but note there is one extra curiosity.\nWhen you run the test above (or your reproduction recipe), you should\nsee that it complains to stderr twice about being unable to run the\npager. That is perhaps a sign that setup_pager() should make sure it is\nnever run twice (because either it succeeds, in which case we don't want\nto run a second pager, or it failed, in which case trying again is\npointless).\n\nIf we fixed changed that, then that would also fix the bug you found,\nregardless of this child_process_clear() change.\n\n-Peff\n"},{"id":"441894","messageId":"xmqqfsrplz3z.fsf@gitster.g","threadId":"56951","inReplyTo":"YZqSBlvzz2KgOMnJ@coredump.intra.peff.net","subject":"Re: [PATCH v2] pager: fix crash when pager program doesn't exist","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-22T02:10:08Z","receivedAt":"2021-11-22T02:10:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> We'd usually leave of \"Reviewed-by\" until the reviewer has had a chance\n> to see _this_ version of the patch. I.e., usually it would not be added\n> by the submitter, but by the maintainer (unless you are resending\n> verbatim a patch that already got review).\n>\n>> diff --git a/run-command.c b/run-command.c\n>> index f329391154ae..a7bf81025afb 100644\n>> --- a/run-command.c\n>> +++ b/run-command.c\n>> @@ -19,6 +19,7 @@ void child_process_clear(struct child_process *child)\n>>  {\n>>  \tstrvec_clear(&child->args);\n>>  \tstrvec_clear(&child->env_array);\n>> +\tchild_process_init(child);\n>>  }\n>\n> And naturally I agree that the patch itself looks good. :)\n\nWell, not to me X-<.  This is way too aggressive a change to be made\nlightly without auditing the current users of run_command API.\n\nIt is rather common for us to reuse \"struct child_process\" in a code\npath, e.g. builtin/worktree.c::add_worktree() prepares a single\ninstance of such a struct, sets cp.git_cmd to true, and runs either\n\"update-ref\" or \"symbolic-ref\" to update \"HEAD\".  After successful\ninvocation of such a git subcommand, it then runs \"git reset --hard\",\nwith this piece of code:\n\n\tcp.git_cmd = 1;\n\n\tif (!is_branch)\n\t\tstrvec_pushl(&cp.args, \"update-ref\", \"HEAD\",\n\t\t\t     oid_to_hex(&commit->object.oid), NULL);\n\telse {\n\t\tstrvec_pushl(&cp.args, \"symbolic-ref\", \"HEAD\",\n\t\t\t     symref.buf, NULL);\n\t\tif (opts->quiet)\n\t\t\tstrvec_push(&cp.args, \"--quiet\");\n\t}\n\n\tcp.env = child_env.v;\n\tret = run_command(&cp);\n\tif (ret)\n\t\tgoto done;\n\n\tif (opts->checkout) {\n\t\tcp.argv = NULL;\n\t\tstrvec_clear(&cp.args);\n\t\tstrvec_pushl(&cp.args, \"reset\", \"--hard\", \"--no-recurse-submodules\", NULL);\n\t\tif (opts->quiet)\n\t\t\tstrvec_push(&cp.args, \"--quiet\");\n\t\tcp.env = child_env.v;\n\t\tret = run_command(&cp);\n\t\tif (ret)\n\t\t\tgoto done;\n\t}\n\nNow, we could argue that this existing caller is too lazy to assume\nthat cp.git_cmd bit will be retained after run_command()\nsuccessfully finishes and can reuse the structure without setting\nthe bit again, and it should be more defensive.  And \"successful\nrun_command() clears the child process so that you'll get a clean\nslate\" may even be a better API in the longer term.\n\nBut then a change like this one that changes the world order to make\nit a better place is also responsible to ensure that the callers are\nalready following the new world order.\n\nWhen merged to 'seen', this literally destroys tons of tests (the\nfirst and easiest one to observe may be t0002).\n\nThanks.\n\n\n\n"},{"id":"441902","messageId":"YZseJ4jOVIK3+bUD@coredump.intra.peff.net","threadId":"56951","inReplyTo":"xmqqfsrplz3z.fsf@gitster.g","subject":"Re: [PATCH v2] pager: fix crash when pager program doesn't exist","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-11-22T04:35:51Z","receivedAt":"2021-11-22T04:35:54Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Nov 21, 2021 at 06:10:08PM -0800, Junio C Hamano wrote:\n\n> Jeff King <peff@peff.net> writes:\n> \n> > We'd usually leave of \"Reviewed-by\" until the reviewer has had a chance\n> > to see _this_ version of the patch. I.e., usually it would not be added\n> > by the submitter, but by the maintainer (unless you are resending\n> > verbatim a patch that already got review).\n> >\n> >> diff --git a/run-command.c b/run-command.c\n> >> index f329391154ae..a7bf81025afb 100644\n> >> --- a/run-command.c\n> >> +++ b/run-command.c\n> >> @@ -19,6 +19,7 @@ void child_process_clear(struct child_process *child)\n> >>  {\n> >>  \tstrvec_clear(&child->args);\n> >>  \tstrvec_clear(&child->env_array);\n> >> +\tchild_process_init(child);\n> >>  }\n> >\n> > And naturally I agree that the patch itself looks good. :)\n> \n> Well, not to me X-<.  This is way too aggressive a change to be made\n> lightly without auditing the current users of run_command API.\n\nYikes. Thanks for a dose of sanity. I was looking too much at just the\npager tests.\n\n> It is rather common for us to reuse \"struct child_process\" in a code\n> path, e.g. builtin/worktree.c::add_worktree() prepares a single\n> instance of such a struct, sets cp.git_cmd to true, and runs either\n> \"update-ref\" or \"symbolic-ref\" to update \"HEAD\".  After successful\n> invocation of such a git subcommand, it then runs \"git reset --hard\",\n> with this piece of code:\n> \n> \tcp.git_cmd = 1;\n> \n> \tif (!is_branch)\n> \t\tstrvec_pushl(&cp.args, \"update-ref\", \"HEAD\",\n> \t\t\t     oid_to_hex(&commit->object.oid), NULL);\n> \telse {\n> \t\tstrvec_pushl(&cp.args, \"symbolic-ref\", \"HEAD\",\n> \t\t\t     symref.buf, NULL);\n> \t\tif (opts->quiet)\n> \t\t\tstrvec_push(&cp.args, \"--quiet\");\n> \t}\n> \n> \tcp.env = child_env.v;\n> \tret = run_command(&cp);\n> \tif (ret)\n> \t\tgoto done;\n> \n> \tif (opts->checkout) {\n> \t\tcp.argv = NULL;\n> \t\tstrvec_clear(&cp.args);\n> \t\tstrvec_pushl(&cp.args, \"reset\", \"--hard\", \"--no-recurse-submodules\", NULL);\n> \t\tif (opts->quiet)\n> \t\t\tstrvec_push(&cp.args, \"--quiet\");\n> \t\tcp.env = child_env.v;\n> \t\tret = run_command(&cp);\n> \t\tif (ret)\n> \t\t\tgoto done;\n> \t}\n\nThis is a pretty horrid interface, in that the caller has to understand\nwhich bits of \"cp\" need to be adjusted: setting cp.argv to NULL, but\nalso potentially cp.env (if cp.env_array was used), and clearing any\nstdin/out/err descriptors created as pipes in the previous command. And\nprobably more; that's just off the top of my head.\n\nBut clearly there's a bunch of code relying on the current state of\naffairs.\n\n> Now, we could argue that this existing caller is too lazy to assume\n> that cp.git_cmd bit will be retained after run_command()\n> successfully finishes and can reuse the structure without setting\n> the bit again, and it should be more defensive.  And \"successful\n> run_command() clears the child process so that you'll get a clean\n> slate\" may even be a better API in the longer term.\n> \n> But then a change like this one that changes the world order to make\n> it a better place is also responsible to ensure that the callers are\n> already following the new world order.\n\nYep. But I do worry a bit about changing the interface in such a subtle\nway, as nothing would catch topics in flight.\n\n> When merged to 'seen', this literally destroys tons of tests (the\n> first and easiest one to observe may be t0002).\n\nForget 'seen'. Applying it on master shows plenty of breakages. :)\n\nI think we should probably punt on this direction, and just make sure\nthat setup_pager() either reinitializes the child_process as appropriate\n(as in the patch I showed in the earlier thread) or just refuses to try\nrunning the pager twice (I didn't show a patch, but it should just be a\nmatter of setting a static flag).\n\n-Peff\n"},{"id":"441954","messageId":"20211122145222.m2zrmtbaeu5kzbtt@cyberdelia","threadId":"56951","inReplyTo":"YZseJ4jOVIK3+bUD@coredump.intra.peff.net","subject":"Re: [PATCH v2] pager: fix crash when pager program doesn't exist","fromName":"Enzo Matsumiya","fromEmail":"ematsumiya@suse.de","sentAt":"2021-11-22T14:52:22Z","receivedAt":"2021-11-22T14:52:27Z","isPatch":true,"sender":{"key":"ematsumiya@suse.de","avatar":"https://avatars.githubusercontent.com/u/45605263?v=4"},"body":"On 11/21, Jeff King wrote:\n>On Sun, Nov 21, 2021 at 06:10:08PM -0800, Junio C Hamano wrote:\n>> Well, not to me X-<.  This is way too aggressive a change to be made\n>> lightly without auditing the current users of run_command API.\n>\n>Yikes. Thanks for a dose of sanity. I was looking too much at just the\n>pager tests.\n\nThanks for your input, Junio. I was, too, focused only on pager side and\neven more so on the bug itself.\n\n<snip> as I have no familiarity with other parts of the code </snip>\n\n>> When merged to 'seen', this literally destroys tons of tests (the\n>> first and easiest one to observe may be t0002).\n>\n>Forget 'seen'. Applying it on master shows plenty of breakages. :)\n\nI should *probably* run the full test suite next time...\n\n>I think we should probably punt on this direction, and just make sure\n>that setup_pager() either reinitializes the child_process as appropriate\n>(as in the patch I showed in the earlier thread) or just refuses to try\n>running the pager twice (I didn't show a patch, but it should just be a\n>matter of setting a static flag).\n\nI'm preparing v3 with the above suggestions in mind.\n\n\nCheers,\n\nEnzo\n"},{"id":"441956","messageId":"20211122153119.h2t2ti3lkiycd7pb@cyberdelia","threadId":"56951","inReplyTo":"xmqqfsrplz3z.fsf@gitster.g","subject":"Re: [PATCH v2] pager: fix crash when pager program doesn't exist","fromName":"Enzo Matsumiya","fromEmail":"ematsumiya@suse.de","sentAt":"2021-11-22T15:31:19Z","receivedAt":"2021-11-22T15:31:25Z","isPatch":true,"sender":{"key":"ematsumiya@suse.de","avatar":"https://avatars.githubusercontent.com/u/45605263?v=4"},"body":"On 11/21, Junio C Hamano wrote:\n>It is rather common for us to reuse \"struct child_process\" in a code\n>path, e.g. builtin/worktree.c::add_worktree() prepares a single\n>instance of such a struct, sets cp.git_cmd to true, and runs either\n>\"update-ref\" or \"symbolic-ref\" to update \"HEAD\".  After successful\n>invocation of such a git subcommand, it then runs \"git reset --hard\",\n>with this piece of code:\n\nDo you agree that at least NULLing .argv and .env could be part of\nchild_process_clear()?\n\ndiff --git a/run-command.c b/run-command.c\nindex a7bf81025afb..3839a26eff11 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -18,8 +18,9 @@ void child_process_init(struct child_process *child)\n  void child_process_clear(struct child_process *child)\n  {\n         strvec_clear(&child->args);\n+       child->argv = NULL;\n         strvec_clear(&child->env_array);\n+       child->env = NULL;\n  }\n\nWith this change on main, all tests are successful.\n\n\nCheers,\n\nEnzo\n"},{"id":"441957","messageId":"cover-0.5-00000000000-20211122T153605Z-avarab@gmail.com","threadId":"56951","inReplyTo":"YZseJ4jOVIK3+bUD@coredump.intra.peff.net","subject":"[PATCH 0/5] run-command API: get rid of \"argv\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-22T16:04:02Z","receivedAt":"2021-11-22T16:04:15Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"This series is an alternate but more thorough way to solve the pager\nsegfault reported by Enzo Matsumiya[1], and more generally avoids\nsimilar issues in the future.\n\nThat the run-command API exposed two subtly different ways of doing\nthe same thing wouldn't only lead to the sort of bug reported in [1],\nbut also made memory management around it rather painful. As noted by\nJeff King in[2]:\n\n    I'd like to eventually get rid of the argv interface entirely\n    because it has memory-ownership semantics that are easy to get\n    wrong.\n\nThere's probably never going to be a perfect time to do this as a\nchange to this widely used API will probably impact things\nin-flight.\n\nNow seems to be a particularly good time though, merging this to\n\"seen\" only conflicts with my ab/config-based-hooks-2 in\nbuiltin/worktree.c. The resolution is trivial (just use the new hook\nAPI added in that topic).\n\nSince this series removes the \"argv\" member we're not going to have\nany semantic conflicts that aren't obvious as a compile-time error\n(and merging with seen both compiles & passes all tests).\n\nAs noted in 5/5 we've still got a similar issue with \"env\" and\n\"env_array\". I've got a follow-up series that similarly removes \"env\"\nwhich we can do at some point (it's much smaller than this one), but\nfor now let's focus on \"argv\".\n\n1. https://lore.kernel.org/git/20211120194048.12125-1-ematsumiya@suse.de/\n2. https://lore.kernel.org/git/YT6BnnXeAWn8BycF@coredump.intra.peff.net/\n\nÆvar Arnfjörð Bjarmason (5):\n  archive-tar: use our own cmd.buf in error message\n  upload-archive: use regular \"struct child_process\" pattern\n  run-command API users: use strvec_pushv(), not argv assignment\n  run-command API users: use strvec_pushl(), not argv construction\n  run-command API: remove \"argv\" member, always use \"args\"\n\n add-patch.c                 |  4 +--\n archive-tar.c               |  9 +++----\n builtin/add.c               |  6 +----\n builtin/fsck.c              | 12 +++------\n builtin/help.c              |  3 +--\n builtin/merge.c             |  3 +--\n builtin/notes.c             |  2 +-\n builtin/receive-pack.c      | 16 +++++------\n builtin/replace.c           |  3 +--\n builtin/upload-archive.c    |  5 +++-\n builtin/worktree.c          |  2 --\n daemon.c                    | 17 +++---------\n diff.c                      |  7 +----\n editor.c                    |  2 +-\n http-backend.c              |  2 +-\n http.c                      |  5 ++--\n prompt.c                    |  7 +----\n remote-curl.c               |  2 +-\n run-command.c               | 53 ++++++++++++++++++++-----------------\n run-command.h               | 20 ++++++--------\n sequencer.c                 |  2 +-\n sub-process.c               |  2 +-\n t/helper/test-run-command.c | 10 ++++---\n t/helper/test-subprocess.c  |  2 +-\n t/t7006-pager.sh            |  4 +++\n trace2/tr2_tgt_event.c      |  2 +-\n trace2/tr2_tgt_normal.c     |  2 +-\n trace2/tr2_tgt_perf.c       |  4 +--\n transport.c                 |  2 +-\n upload-pack.c               |  5 +---\n 30 files changed, 94 insertions(+), 121 deletions(-)\n\n-- \n2.34.0.822.gb876f875f1b\n\n"},{"id":"441958","messageId":"patch-1.5-1d8cab554bb-20211122T153605Z-avarab@gmail.com","threadId":"56951","inReplyTo":"cover-0.5-00000000000-20211122T153605Z-avarab@gmail.com","subject":"[PATCH 1/5] archive-tar: use our own cmd.buf in error message","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-22T16:04:03Z","receivedAt":"2021-11-22T16:04:21Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Use the \"cmd.buf\" we just created in this function, instead of the\nargv[0], which we assigned \"cmd.buf\" for. This is in preparation for\ngetting rid of the use of \"argv\" in this function.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n archive-tar.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/archive-tar.c b/archive-tar.c\nindex 05d2455870d..4154d9a0953 100644\n--- a/archive-tar.c\n+++ b/archive-tar.c\n@@ -447,7 +447,7 @@ static int write_tar_filter_archive(const struct archiver *ar,\n \tfilter.in = -1;\n \n \tif (start_command(&filter) < 0)\n-\t\tdie_errno(_(\"unable to start '%s' filter\"), argv[0]);\n+\t\tdie_errno(_(\"unable to start '%s' filter\"), cmd.buf);\n \tclose(1);\n \tif (dup2(filter.in, 1) < 0)\n \t\tdie_errno(_(\"unable to redirect descriptor\"));\n@@ -457,7 +457,7 @@ static int write_tar_filter_archive(const struct archiver *ar,\n \n \tclose(1);\n \tif (finish_command(&filter) != 0)\n-\t\tdie(_(\"'%s' filter reported error\"), argv[0]);\n+\t\tdie(_(\"'%s' filter reported error\"), cmd.buf);\n \n \tstrbuf_release(&cmd);\n \treturn r;\n-- \n2.34.0.822.gb876f875f1b\n\n"},{"id":"441959","messageId":"patch-2.5-a411098699d-20211122T153605Z-avarab@gmail.com","threadId":"56951","inReplyTo":"cover-0.5-00000000000-20211122T153605Z-avarab@gmail.com","subject":"[PATCH 2/5] upload-archive: use regular \"struct child_process\" pattern","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-22T16:04:04Z","receivedAt":"2021-11-22T16:04:24Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"This pattern added [1] in seems to have been intentional, but since\n[2] and [3] we've wanted do initialization of what's now the \"struct\nstrvec\" \"args\" and \"env_array\" members. Let's not trample on that\ninitialization here.\n\n1. 1bc01efed17 (upload-archive: use start_command instead of fork,\n   2011-11-19)\n2. c460c0ecdca (run-command: store an optional argv_array, 2014-05-15)\n3. 9a583dc39e (run-command: add env_array, an optional argv_array for\n   env, 2014-10-19)\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/upload-archive.c | 5 ++++-\n 1 file changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/upload-archive.c b/builtin/upload-archive.c\nindex 24654b4c9bf..b4b9b3a6262 100644\n--- a/builtin/upload-archive.c\n+++ b/builtin/upload-archive.c\n@@ -77,7 +77,7 @@ static ssize_t process_input(int child_fd, int band)\n \n int cmd_upload_archive(int argc, const char **argv, const char *prefix)\n {\n-\tstruct child_process writer = { argv };\n+\tstruct child_process writer = CHILD_PROCESS_INIT;\n \n \tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n \t\tusage(upload_archive_usage);\n@@ -92,6 +92,9 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix)\n \targv[0] = \"upload-archive--writer\";\n \twriter.out = writer.err = -1;\n \twriter.git_cmd = 1;\n+\tstrvec_push(&writer.args, \"upload-archive--writer\");\n+\tif (argc > 1)\n+\t\tstrvec_pushv(&writer.args, &argv[1]);\n \tif (start_command(&writer)) {\n \t\tint err = errno;\n \t\tpacket_write_fmt(1, \"NACK unable to spawn subprocess\\n\");\n-- \n2.34.0.822.gb876f875f1b\n\n"},{"id":"441960","messageId":"patch-3.5-fd6c4c960ba-20211122T153605Z-avarab@gmail.com","threadId":"56951","inReplyTo":"cover-0.5-00000000000-20211122T153605Z-avarab@gmail.com","subject":"[PATCH 3/5] run-command API users: use strvec_pushv(), not argv assignment","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-22T16:04:05Z","receivedAt":"2021-11-22T16:04:25Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Migrate those run-command API users that assign directly to the \"argv\"\nmember to use a strvec_pushv() of \"args\" instead, but exclude those\ncases where we can't easily get rid of the construction of the \"argv\"\nvariable being given to the resulting \"strvec_pushv()\".\n\nThis is in preparation for getting rid of the \"argv\" member from the\nrun-command API itself.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n add-patch.c                |  4 ++--\n builtin/notes.c            |  2 +-\n builtin/receive-pack.c     | 16 ++++++++--------\n daemon.c                   |  2 +-\n editor.c                   |  2 +-\n http-backend.c             |  2 +-\n http.c                     |  5 +++--\n remote-curl.c              |  2 +-\n run-command.c              |  2 +-\n sequencer.c                |  2 +-\n t/helper/test-subprocess.c |  2 +-\n transport.c                |  2 +-\n 12 files changed, 22 insertions(+), 21 deletions(-)\n\ndiff --git a/add-patch.c b/add-patch.c\nindex 8c41cdfe39b..573eef0cc4a 100644\n--- a/add-patch.c\n+++ b/add-patch.c\n@@ -413,7 +413,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n \t\tstrvec_push(&args, ps->items[i].original);\n \n \tsetup_child_process(s, &cp, NULL);\n-\tcp.argv = args.v;\n+\tstrvec_pushv(&cp.args, args.v);\n \tres = capture_command(&cp, plain, 0);\n \tif (res) {\n \t\tstrvec_clear(&args);\n@@ -431,7 +431,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n \n \t\tsetup_child_process(s, &colored_cp, NULL);\n \t\txsnprintf((char *)args.v[color_arg_index], 8, \"--color\");\n-\t\tcolored_cp.argv = args.v;\n+\t\tstrvec_pushv(&colored_cp.args, args.v);\n \t\tcolored = &s->colored;\n \t\tres = capture_command(&colored_cp, colored, 0);\n \t\tstrvec_clear(&args);\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 71c59583a17..2b2bac43f31 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -141,7 +141,7 @@ static void write_commented_object(int fd, const struct object_id *object)\n \tstruct strbuf cbuf = STRBUF_INIT;\n \n \t/* Invoke \"git show --stat --no-notes $object\" */\n-\tshow.argv = show_args;\n+\tstrvec_pushv(&show.args, show_args);\n \tshow.no_stdin = 1;\n \tshow.out = -1;\n \tshow.err = 0;\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 49b846d9605..33815631010 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -821,7 +821,7 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,\n \n \targv[1] = NULL;\n \n-\tproc.argv = argv;\n+\tstrvec_pushv(&proc.args, argv);\n \tproc.in = -1;\n \tproc.stdout_to_stderr = 1;\n \tproc.trace2_hook_name = hook_name;\n@@ -959,7 +959,7 @@ static int run_update_hook(struct command *cmd)\n \tproc.no_stdin = 1;\n \tproc.stdout_to_stderr = 1;\n \tproc.err = use_sideband ? -1 : 0;\n-\tproc.argv = argv;\n+\tstrvec_pushv(&proc.args, argv);\n \tproc.trace2_hook_name = \"update\";\n \n \tcode = start_command(&proc);\n@@ -1132,7 +1132,7 @@ static int run_proc_receive_hook(struct command *commands,\n \t}\n \targv[1] = NULL;\n \n-\tproc.argv = argv;\n+\tstrvec_pushv(&proc.args, argv);\n \tproc.in = -1;\n \tproc.out = -1;\n \tproc.trace2_hook_name = \"proc-receive\";\n@@ -1385,7 +1385,7 @@ static const char *push_to_deploy(unsigned char *sha1,\n \t};\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \n-\tchild.argv = update_refresh;\n+\tstrvec_pushv(&child.args, update_refresh);\n \tchild.env = env->v;\n \tchild.dir = work_tree;\n \tchild.no_stdin = 1;\n@@ -1396,7 +1396,7 @@ static const char *push_to_deploy(unsigned char *sha1,\n \n \t/* run_command() does not clean up completely; reinitialize */\n \tchild_process_init(&child);\n-\tchild.argv = diff_files;\n+\tstrvec_pushv(&child.args, diff_files);\n \tchild.env = env->v;\n \tchild.dir = work_tree;\n \tchild.no_stdin = 1;\n@@ -1409,7 +1409,7 @@ static const char *push_to_deploy(unsigned char *sha1,\n \tdiff_index[4] = head_has_history() ? \"HEAD\" : empty_tree_oid_hex();\n \n \tchild_process_init(&child);\n-\tchild.argv = diff_index;\n+\tstrvec_pushv(&child.args, diff_index);\n \tchild.env = env->v;\n \tchild.no_stdin = 1;\n \tchild.no_stdout = 1;\n@@ -1420,7 +1420,7 @@ static const char *push_to_deploy(unsigned char *sha1,\n \n \tread_tree[3] = hash_to_hex(sha1);\n \tchild_process_init(&child);\n-\tchild.argv = read_tree;\n+\tstrvec_pushv(&child.args, read_tree);\n \tchild.env = env->v;\n \tchild.dir = work_tree;\n \tchild.no_stdin = 1;\n@@ -2584,7 +2584,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\t\tproc.stdout_to_stderr = 1;\n \t\t\tproc.err = use_sideband ? -1 : 0;\n \t\t\tproc.git_cmd = proc.close_object_store = 1;\n-\t\t\tproc.argv = argv_gc_auto;\n+\t\t\tstrvec_pushv(&proc.args, argv_gc_auto);\n \n \t\t\tif (!start_command(&proc)) {\n \t\t\t\tif (use_sideband)\ndiff --git a/daemon.c b/daemon.c\nindex b1fcbe0d6fa..8df21f2130c 100644\n--- a/daemon.c\n+++ b/daemon.c\n@@ -922,7 +922,7 @@ static void handle(int incoming, struct sockaddr *addr, socklen_t addrlen)\n #endif\n \t}\n \n-\tcld.argv = cld_argv.v;\n+\tstrvec_pushv(&cld.args, cld_argv.v);\n \tcld.in = incoming;\n \tcld.out = dup(incoming);\n \ndiff --git a/editor.c b/editor.c\nindex 674309eed8b..90f3379285c 100644\n--- a/editor.c\n+++ b/editor.c\n@@ -82,7 +82,7 @@ static int launch_specified_editor(const char *editor, const char *path,\n \t\tstrbuf_realpath(&realpath, path, 1);\n \t\targs[1] = realpath.buf;\n \n-\t\tp.argv = args;\n+\t\tstrvec_pushv(&p.args, args);\n \t\tp.env = env;\n \t\tp.use_shell = 1;\n \t\tp.trace2_child_class = \"editor\";\ndiff --git a/http-backend.c b/http-backend.c\nindex 3d6e2ff17f8..4dd4d939f8a 100644\n--- a/http-backend.c\n+++ b/http-backend.c\n@@ -480,7 +480,7 @@ static void run_service(const char **argv, int buffer_input)\n \t\tstrvec_pushf(&cld.env_array,\n \t\t\t     \"GIT_COMMITTER_EMAIL=%s@http.%s\", user, host);\n \n-\tcld.argv = argv;\n+\tstrvec_pushv(&cld.args, argv);\n \tif (buffer_input || gzipped_request || req_len >= 0)\n \t\tcld.in = -1;\n \tcld.git_cmd = 1;\ndiff --git a/http.c b/http.c\nindex f92859f43fa..229da4d1488 100644\n--- a/http.c\n+++ b/http.c\n@@ -2126,8 +2126,9 @@ int finish_http_pack_request(struct http_pack_request *preq)\n \n \tip.git_cmd = 1;\n \tip.in = tmpfile_fd;\n-\tip.argv = preq->index_pack_args ? preq->index_pack_args\n-\t\t\t\t\t: default_index_pack_args;\n+\tstrvec_pushv(&ip.args, preq->index_pack_args ?\n+\t\t     preq->index_pack_args :\n+\t\t     default_index_pack_args);\n \n \tif (preq->preserve_index_pack_stdout)\n \t\tip.out = 0;\ndiff --git a/remote-curl.c b/remote-curl.c\nindex d69156312bd..0dabef2dd7c 100644\n--- a/remote-curl.c\n+++ b/remote-curl.c\n@@ -1061,7 +1061,7 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads,\n \tclient.in = -1;\n \tclient.out = -1;\n \tclient.git_cmd = 1;\n-\tclient.argv = client_argv;\n+\tstrvec_pushv(&client.args, client_argv);\n \tif (start_command(&client))\n \t\texit(1);\n \twrite_or_die(client.in, preamble->buf, preamble->len);\ndiff --git a/run-command.c b/run-command.c\nindex f40df01c772..620a06ca2f5 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -1039,7 +1039,7 @@ int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,\n \t\t\t\t const char *const *env, const char *tr2_class)\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n-\tcmd.argv = argv;\n+\tstrvec_pushv(&cmd.args, argv);\n \tcmd.no_stdin = opt & RUN_COMMAND_NO_STDIN ? 1 : 0;\n \tcmd.git_cmd = opt & RUN_GIT_CMD ? 1 : 0;\n \tcmd.stdout_to_stderr = opt & RUN_COMMAND_STDOUT_TO_STDERR ? 1 : 0;\ndiff --git a/sequencer.c b/sequencer.c\nindex ea96837cde3..60ede96461c 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1175,7 +1175,7 @@ static int run_rewrite_hook(const struct object_id *oldoid,\n \targv[1] = \"amend\";\n \targv[2] = NULL;\n \n-\tproc.argv = argv;\n+\tstrvec_pushv(&proc.args, argv);\n \tproc.in = -1;\n \tproc.stdout_to_stderr = 1;\n \tproc.trace2_hook_name = \"post-rewrite\";\ndiff --git a/t/helper/test-subprocess.c b/t/helper/test-subprocess.c\nindex 92b69de6352..ff22f2fa2c5 100644\n--- a/t/helper/test-subprocess.c\n+++ b/t/helper/test-subprocess.c\n@@ -15,6 +15,6 @@ int cmd__subprocess(int argc, const char **argv)\n \t\targv++;\n \t}\n \tcp.git_cmd = 1;\n-\tcp.argv = (const char **)argv + 1;\n+\tstrvec_pushv(&cp.args, (const char **)argv + 1);\n \treturn run_command(&cp);\n }\ndiff --git a/transport.c b/transport.c\nindex e4f1decae20..2361f54bd48 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1213,7 +1213,7 @@ static int run_pre_push_hook(struct transport *transport,\n \targv[2] = transport->url;\n \targv[3] = NULL;\n \n-\tproc.argv = argv;\n+\tstrvec_pushv(&proc.args, argv);\n \tproc.in = -1;\n \tproc.trace2_hook_name = \"pre-push\";\n \n-- \n2.34.0.822.gb876f875f1b\n\n"},{"id":"441961","messageId":"patch-5.5-ea1011f7473-20211122T153605Z-avarab@gmail.com","threadId":"56951","inReplyTo":"cover-0.5-00000000000-20211122T153605Z-avarab@gmail.com","subject":"[PATCH 5/5] run-command API: remove \"argv\" member, always use \"args\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-22T16:04:07Z","receivedAt":"2021-11-22T16:04:26Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Remove the \"argv\" member from the run-command API, ever since \"args\"\nwas added in c460c0ecdca (run-command: store an optional argv_array,\n2014-05-15) being able to provide either \"argv\" or \"args\" has led to\nsome confusion and bugs.\n\nIf we hadn't gone in that direction and only had an \"argv\" our\nproblems wouldn't have been solved either, as noted in [1] (and in the\ndocumentation amended here) it comes with inherent memory management\nissues: The caller would have to hang on to the \"argv\" until the\nrun-command API was finished. If the \"argv\" was an argument to main()\nthis wasn't an issue, but if it it was manually constructed using the\nAPI might be painful.\n\nWe also have a recent report[2] of a user of the API segfaulting,\nwhich is a direct result of it being complex to use. A test being\nadded here would segfault before this change.\n\nThis change is larger than I'd like, but there's no easy way to avoid\nit that wouldn't involve even more verbose intermediate steps. We use\nthe \"argv\" as the source of truth over the \"args\", so we need to\nchange all parts of run-command.[ch] itself, as well as the trace2\nlogging at the same time.\n\nWe can also similarly follow-up and remove \"env\" member in favor of\n\"env_array\", which is a much smaller change than this series removing\n\"argv\", but let's stop at \"argv\" for now. We can always do \"env\" as a\nfollow-up.\n\nI have not carefully tested the Windows-specific code in\nstart_command() as I don't have a Windows system, but it seems to pass\nCI + runs the tests there.\n\n1. http://lore.kernel.org/git/YT6BnnXeAWn8BycF@coredump.intra.peff.net\n2. https://lore.kernel.org/git/20211120194048.12125-1-ematsumiya@suse.de/\n\nReported-by: Enzo Matsumiya <ematsumiya@suse.de>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/worktree.c          |  2 --\n run-command.c               | 51 ++++++++++++++++++++-----------------\n run-command.h               | 20 ++++++---------\n sub-process.c               |  2 +-\n t/helper/test-run-command.c | 10 +++++---\n t/t7006-pager.sh            |  4 +++\n trace2/tr2_tgt_event.c      |  2 +-\n trace2/tr2_tgt_normal.c     |  2 +-\n trace2/tr2_tgt_perf.c       |  4 +--\n 9 files changed, 51 insertions(+), 46 deletions(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex d22ece93e1a..7264a5b5de0 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -355,7 +355,6 @@ static int add_worktree(const char *path, const char *refname,\n \t\tgoto done;\n \n \tif (opts->checkout) {\n-\t\tcp.argv = NULL;\n \t\tstrvec_clear(&cp.args);\n \t\tstrvec_pushl(&cp.args, \"reset\", \"--hard\", \"--no-recurse-submodules\", NULL);\n \t\tif (opts->quiet)\n@@ -390,7 +389,6 @@ static int add_worktree(const char *path, const char *refname,\n \t\t\tcp.stdout_to_stderr = 1;\n \t\t\tcp.dir = path;\n \t\t\tcp.env = env;\n-\t\t\tcp.argv = NULL;\n \t\t\tcp.trace2_hook_name = \"post-checkout\";\n \t\t\tstrvec_pushl(&cp.args, absolute_path(hook),\n \t\t\t\t     oid_to_hex(null_oid()),\ndiff --git a/run-command.c b/run-command.c\nindex 620a06ca2f5..6158807ed13 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -380,7 +380,7 @@ static void child_err_spew(struct child_process *cmd, struct child_err *cerr)\n \tswitch (cerr->err) {\n \tcase CHILD_ERR_CHDIR:\n \t\terror_errno(\"exec '%s': cd to '%s' failed\",\n-\t\t\t    cmd->argv[0], cmd->dir);\n+\t\t\t    cmd->args.v[0], cmd->dir);\n \t\tbreak;\n \tcase CHILD_ERR_DUP2:\n \t\terror_errno(\"dup2() in child failed\");\n@@ -392,12 +392,12 @@ static void child_err_spew(struct child_process *cmd, struct child_err *cerr)\n \t\terror_errno(\"sigprocmask failed restoring signals\");\n \t\tbreak;\n \tcase CHILD_ERR_ENOENT:\n-\t\terror_errno(\"cannot run %s\", cmd->argv[0]);\n+\t\terror_errno(\"cannot run %s\", cmd->args.v[0]);\n \t\tbreak;\n \tcase CHILD_ERR_SILENT:\n \t\tbreak;\n \tcase CHILD_ERR_ERRNO:\n-\t\terror_errno(\"cannot exec '%s'\", cmd->argv[0]);\n+\t\terror_errno(\"cannot exec '%s'\", cmd->args.v[0]);\n \t\tbreak;\n \t}\n \tset_error_routine(old_errfn);\n@@ -405,7 +405,7 @@ static void child_err_spew(struct child_process *cmd, struct child_err *cerr)\n \n static int prepare_cmd(struct strvec *out, const struct child_process *cmd)\n {\n-\tif (!cmd->argv[0])\n+\tif (!cmd->args.v[0])\n \t\tBUG(\"command is empty\");\n \n \t/*\n@@ -415,11 +415,11 @@ static int prepare_cmd(struct strvec *out, const struct child_process *cmd)\n \tstrvec_push(out, SHELL_PATH);\n \n \tif (cmd->git_cmd) {\n-\t\tprepare_git_cmd(out, cmd->argv);\n+\t\tprepare_git_cmd(out, cmd->args.v);\n \t} else if (cmd->use_shell) {\n-\t\tprepare_shell_cmd(out, cmd->argv);\n+\t\tprepare_shell_cmd(out, cmd->args.v);\n \t} else {\n-\t\tstrvec_pushv(out, cmd->argv);\n+\t\tstrvec_pushv(out, cmd->args.v);\n \t}\n \n \t/*\n@@ -663,7 +663,7 @@ static void trace_run_command(const struct child_process *cp)\n \t\ttrace_add_env(&buf, cp->env);\n \tif (cp->git_cmd)\n \t\tstrbuf_addstr(&buf, \" git\");\n-\tsq_quote_argv_pretty(&buf, cp->argv);\n+\tsq_quote_argv_pretty(&buf, cp->args.v);\n \n \ttrace_printf(\"%s\", buf.buf);\n \tstrbuf_release(&buf);\n@@ -676,8 +676,6 @@ int start_command(struct child_process *cmd)\n \tint failed_errno;\n \tchar *str;\n \n-\tif (!cmd->argv)\n-\t\tcmd->argv = cmd->args.v;\n \tif (!cmd->env)\n \t\tcmd->env = cmd->env_array.v;\n \n@@ -729,7 +727,7 @@ int start_command(struct child_process *cmd)\n \t\t\tstr = \"standard error\";\n fail_pipe:\n \t\t\terror(\"cannot create %s pipe for %s: %s\",\n-\t\t\t\tstr, cmd->argv[0], strerror(failed_errno));\n+\t\t\t\tstr, cmd->args.v[0], strerror(failed_errno));\n \t\t\tchild_process_clear(cmd);\n \t\t\terrno = failed_errno;\n \t\t\treturn -1;\n@@ -758,7 +756,7 @@ int start_command(struct child_process *cmd)\n \t\tfailed_errno = errno;\n \t\tcmd->pid = -1;\n \t\tif (!cmd->silent_exec_failure)\n-\t\t\terror_errno(\"cannot run %s\", cmd->argv[0]);\n+\t\t\terror_errno(\"cannot run %s\", cmd->args.v[0]);\n \t\tgoto end_of_spawn;\n \t}\n \n@@ -868,7 +866,7 @@ int start_command(struct child_process *cmd)\n \t}\n \tatfork_parent(&as);\n \tif (cmd->pid < 0)\n-\t\terror_errno(\"cannot fork() for %s\", cmd->argv[0]);\n+\t\terror_errno(\"cannot fork() for %s\", cmd->args.v[0]);\n \telse if (cmd->clean_on_exit)\n \t\tmark_child_for_cleanup(cmd->pid, cmd);\n \n@@ -885,7 +883,7 @@ int start_command(struct child_process *cmd)\n \t\t * At this point we know that fork() succeeded, but exec()\n \t\t * failed. Errors have been reported to our stderr.\n \t\t */\n-\t\twait_or_whine(cmd->pid, cmd->argv[0], 0);\n+\t\twait_or_whine(cmd->pid, cmd->args.v[0], 0);\n \t\tchild_err_spew(cmd, &cerr);\n \t\tfailed_errno = errno;\n \t\tcmd->pid = -1;\n@@ -902,8 +900,9 @@ int start_command(struct child_process *cmd)\n #else\n {\n \tint fhin = 0, fhout = 1, fherr = 2;\n-\tconst char **sargv = cmd->argv;\n+\tconst char **sargv = strvec_detach(&cmd->args);\n \tstruct strvec nargv = STRVEC_INIT;\n+\tconst char **temp_argv = NULL;\n \n \tif (cmd->no_stdin)\n \t\tfhin = open(\"/dev/null\", O_RDWR);\n@@ -929,20 +928,26 @@ int start_command(struct child_process *cmd)\n \t\tfhout = dup(cmd->out);\n \n \tif (cmd->git_cmd)\n-\t\tcmd->argv = prepare_git_cmd(&nargv, cmd->argv);\n+\t\ttemp_argv = prepare_git_cmd(&nargv, sargv);\n \telse if (cmd->use_shell)\n-\t\tcmd->argv = prepare_shell_cmd(&nargv, cmd->argv);\n+\t\ttemp_argv = prepare_shell_cmd(&nargv, sargv);\n+\telse\n+\t\ttemp_argv = sargv;\n+\tif (!temp_argv)\n+\t\tBUG(\"should have some cmd->args to set\");\n+\tstrvec_pushv(&cmd->args, temp_argv);\n+\tstrvec_clear(&nargv);\n \n-\tcmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, (char**) cmd->env,\n+\tcmd->pid = mingw_spawnvpe(cmd->args.v[0], cmd->args.v, (char**) cmd->env,\n \t\t\tcmd->dir, fhin, fhout, fherr);\n \tfailed_errno = errno;\n \tif (cmd->pid < 0 && (!cmd->silent_exec_failure || errno != ENOENT))\n-\t\terror_errno(\"cannot spawn %s\", cmd->argv[0]);\n+\t\terror_errno(\"cannot spawn %s\", cmd->args.v[0]);\n \tif (cmd->clean_on_exit && cmd->pid >= 0)\n \t\tmark_child_for_cleanup(cmd->pid, cmd);\n \n-\tstrvec_clear(&nargv);\n-\tcmd->argv = sargv;\n+\tstrvec_pushv(&cmd->args, sargv);;\n+\tfree(sargv);\n \tif (fhin != 0)\n \t\tclose(fhin);\n \tif (fhout != 1)\n@@ -992,7 +997,7 @@ int start_command(struct child_process *cmd)\n \n int finish_command(struct child_process *cmd)\n {\n-\tint ret = wait_or_whine(cmd->pid, cmd->argv[0], 0);\n+\tint ret = wait_or_whine(cmd->pid, cmd->args.v[0], 0);\n \ttrace2_child_exit(cmd, ret);\n \tchild_process_clear(cmd);\n \tinvalidate_lstat_cache();\n@@ -1001,7 +1006,7 @@ int finish_command(struct child_process *cmd)\n \n int finish_command_in_signal(struct child_process *cmd)\n {\n-\tint ret = wait_or_whine(cmd->pid, cmd->argv[0], 1);\n+\tint ret = wait_or_whine(cmd->pid, cmd->args.v[0], 1);\n \ttrace2_child_exit(cmd, ret);\n \treturn ret;\n }\ndiff --git a/run-command.h b/run-command.h\nindex 49878262584..c0d1210cc63 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -44,21 +44,17 @@\n struct child_process {\n \n \t/**\n-\t * The .argv member is set up as an array of string pointers (NULL\n-\t * terminated), of which .argv[0] is the program name to run (usually\n-\t * without a path). If the command to run is a git command, set argv[0] to\n-\t * the command name without the 'git-' prefix and set .git_cmd = 1.\n+\t * The .args is a `struct strvec', use that API to manipulate\n+\t * it, e.g. strvec_pushv() to add an existing \"const char **\"\n+\t * vector.\n \t *\n-\t * Note that the ownership of the memory pointed to by .argv stays with the\n-\t * caller, but it should survive until `finish_command` completes. If the\n-\t * .argv member is NULL, `start_command` will point it at the .args\n-\t * `strvec` (so you may use one or the other, but you must use exactly\n-\t * one). The memory in .args will be cleaned up automatically during\n-\t * `finish_command` (or during `start_command` when it is unsuccessful).\n+\t * If the command to run is a git command, set the first\n+\t * element in the strvec to the command name without the\n+\t * 'git-' prefix and set .git_cmd = 1.\n \t *\n+\t * The memory in .args will be cleaned up automatically during\n+\t * `finish_command` (or during `start_command` when it is unsuccessful).\n \t */\n-\tconst char **argv;\n-\n \tstruct strvec args;\n \tstruct strvec env_array;\n \tpid_t pid;\ndiff --git a/sub-process.c b/sub-process.c\nindex dfa790d3ff9..cae56ae6b80 100644\n--- a/sub-process.c\n+++ b/sub-process.c\n@@ -187,7 +187,7 @@ static int handshake_capabilities(struct child_process *process,\n \t\t\t\t*supported_capabilities |= capabilities[i].flag;\n \t\t} else {\n \t\t\tdie(\"subprocess '%s' requested unsupported capability '%s'\",\n-\t\t\t    process->argv[0], p);\n+\t\t\t    process->args.v[0], p);\n \t\t}\n \t}\n \ndiff --git a/t/helper/test-run-command.c b/t/helper/test-run-command.c\nindex 3c4fb862234..b5519f92a19 100644\n--- a/t/helper/test-run-command.c\n+++ b/t/helper/test-run-command.c\n@@ -31,7 +31,7 @@ static int parallel_next(struct child_process *cp,\n \tif (number_callbacks >= 4)\n \t\treturn 0;\n \n-\tstrvec_pushv(&cp->args, d->argv);\n+\tstrvec_pushv(&cp->args, d->args.v);\n \tstrbuf_addstr(err, \"preloaded output of a child\\n\");\n \tnumber_callbacks++;\n \treturn 1;\n@@ -274,7 +274,7 @@ static int quote_stress_test(int argc, const char **argv)\n \t\tif (i < skip)\n \t\t\tcontinue;\n \n-\t\tcp.argv = args.v;\n+\t\tstrvec_pushv(&cp.args, args.v);\n \t\tstrbuf_reset(&out);\n \t\tif (pipe_command(&cp, NULL, 0, &out, 0, NULL, 0) < 0)\n \t\t\treturn error(\"Failed to spawn child process\");\n@@ -396,7 +396,8 @@ int cmd__run_command(int argc, const char **argv)\n \t}\n \tif (argc < 3)\n \t\treturn 1;\n-\tproc.argv = (const char **)argv + 2;\n+\tstrvec_clear(&proc.args);\n+\tstrvec_pushv(&proc.args, (const char **)argv + 2);\n \n \tif (!strcmp(argv[1], \"start-command-ENOENT\")) {\n \t\tif (start_command(&proc) < 0 && errno == ENOENT)\n@@ -408,7 +409,8 @@ int cmd__run_command(int argc, const char **argv)\n \t\texit(run_command(&proc));\n \n \tjobs = atoi(argv[2]);\n-\tproc.argv = (const char **)argv + 3;\n+\tstrvec_clear(&proc.args);\n+\tstrvec_pushv(&proc.args, (const char **)argv + 3);\n \n \tif (!strcmp(argv[1], \"run-command-parallel\"))\n \t\texit(run_processes_parallel(jobs, parallel_next,\ndiff --git a/t/t7006-pager.sh b/t/t7006-pager.sh\nindex 0e7cf75435e..d75a874b5be 100755\n--- a/t/t7006-pager.sh\n+++ b/t/t7006-pager.sh\n@@ -16,6 +16,10 @@ test_expect_success 'setup' '\n \ttest_commit initial\n '\n \n+test_expect_success 'non-existent pager does not segfault' '\n+\tgit -c pager.show=INVALID show\n+'\n+\n test_expect_success TTY 'some commands use a pager' '\n \trm -f paginated.out &&\n \ttest_terminal git log &&\ndiff --git a/trace2/tr2_tgt_event.c b/trace2/tr2_tgt_event.c\nindex 3a0014417cc..bd17ecdc321 100644\n--- a/trace2/tr2_tgt_event.c\n+++ b/trace2/tr2_tgt_event.c\n@@ -354,7 +354,7 @@ static void fn_child_start_fl(const char *file, int line,\n \tjw_object_inline_begin_array(&jw, \"argv\");\n \tif (cmd->git_cmd)\n \t\tjw_array_string(&jw, \"git\");\n-\tjw_array_argv(&jw, cmd->argv);\n+\tjw_array_argv(&jw, cmd->args.v);\n \tjw_end(&jw);\n \tjw_end(&jw);\n \ndiff --git a/trace2/tr2_tgt_normal.c b/trace2/tr2_tgt_normal.c\nindex 58d9e430f05..6e429a3fb9e 100644\n--- a/trace2/tr2_tgt_normal.c\n+++ b/trace2/tr2_tgt_normal.c\n@@ -232,7 +232,7 @@ static void fn_child_start_fl(const char *file, int line,\n \tstrbuf_addch(&buf_payload, ' ');\n \tif (cmd->git_cmd)\n \t\tstrbuf_addstr(&buf_payload, \"git \");\n-\tsq_append_quote_argv_pretty(&buf_payload, cmd->argv);\n+\tsq_append_quote_argv_pretty(&buf_payload, cmd->args.v);\n \n \tnormal_io_write_fl(file, line, &buf_payload);\n \tstrbuf_release(&buf_payload);\ndiff --git a/trace2/tr2_tgt_perf.c b/trace2/tr2_tgt_perf.c\nindex e4acca13d64..2ff9cf70835 100644\n--- a/trace2/tr2_tgt_perf.c\n+++ b/trace2/tr2_tgt_perf.c\n@@ -335,10 +335,10 @@ static void fn_child_start_fl(const char *file, int line,\n \tstrbuf_addstr(&buf_payload, \" argv:[\");\n \tif (cmd->git_cmd) {\n \t\tstrbuf_addstr(&buf_payload, \"git\");\n-\t\tif (cmd->argv[0])\n+\t\tif (cmd->args.nr)\n \t\t\tstrbuf_addch(&buf_payload, ' ');\n \t}\n-\tsq_append_quote_argv_pretty(&buf_payload, cmd->argv);\n+\tsq_append_quote_argv_pretty(&buf_payload, cmd->args.v);\n \tstrbuf_addch(&buf_payload, ']');\n \n \tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n-- \n2.34.0.822.gb876f875f1b\n\n"},{"id":"441962","messageId":"patch-4.5-61751f55ff2-20211122T153605Z-avarab@gmail.com","threadId":"56951","inReplyTo":"cover-0.5-00000000000-20211122T153605Z-avarab@gmail.com","subject":"[PATCH 4/5] run-command API users: use strvec_pushl(), not argv construction","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-22T16:04:06Z","receivedAt":"2021-11-22T16:04:27Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Migrate those run-command API users that assign directly to the \"argv\"\nmember to use a strvec_pushl() of a list instead, this gets rid of the\nintermediate \"const char *args[]\" these callers were using.\n\nThis is in preparation for getting rid of the \"argv\" member from the\nrun-command API itself.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n archive-tar.c     |  5 +----\n builtin/add.c     |  6 +-----\n builtin/fsck.c    | 12 ++++--------\n builtin/help.c    |  3 +--\n builtin/merge.c   |  3 +--\n builtin/replace.c |  3 +--\n daemon.c          | 15 +++------------\n diff.c            |  7 +------\n prompt.c          |  7 +------\n upload-pack.c     |  5 +----\n 10 files changed, 15 insertions(+), 51 deletions(-)\n\ndiff --git a/archive-tar.c b/archive-tar.c\nindex 4154d9a0953..3c74db17468 100644\n--- a/archive-tar.c\n+++ b/archive-tar.c\n@@ -430,7 +430,6 @@ static int write_tar_filter_archive(const struct archiver *ar,\n {\n \tstruct strbuf cmd = STRBUF_INIT;\n \tstruct child_process filter = CHILD_PROCESS_INIT;\n-\tconst char *argv[2];\n \tint r;\n \n \tif (!ar->data)\n@@ -440,9 +439,7 @@ static int write_tar_filter_archive(const struct archiver *ar,\n \tif (args->compression_level >= 0)\n \t\tstrbuf_addf(&cmd, \" -%d\", args->compression_level);\n \n-\targv[0] = cmd.buf;\n-\targv[1] = NULL;\n-\tfilter.argv = argv;\n+\tstrvec_push(&filter.args, cmd.buf);\n \tfilter.use_shell = 1;\n \tfilter.in = -1;\n \ndiff --git a/builtin/add.c b/builtin/add.c\nindex ef6b619c45e..6357c0c3f9a 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -302,15 +302,11 @@ int interactive_add(const char **argv, const char *prefix, int patch)\n static int edit_patch(int argc, const char **argv, const char *prefix)\n {\n \tchar *file = git_pathdup(\"ADD_EDIT.patch\");\n-\tconst char *apply_argv[] = { \"apply\", \"--recount\", \"--cached\",\n-\t\tNULL, NULL };\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \tstruct rev_info rev;\n \tint out;\n \tstruct stat st;\n \n-\tapply_argv[3] = file;\n-\n \tgit_config(git_diff_basic_config, NULL); /* no \"diff\" UI options */\n \n \tif (read_cache() < 0)\n@@ -338,7 +334,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix)\n \t\tdie(_(\"Empty patch. Aborted.\"));\n \n \tchild.git_cmd = 1;\n-\tchild.argv = apply_argv;\n+\tstrvec_pushl(&child.args, \"apply\", \"--recount\", \"--cached\", file, NULL);\n \tif (run_command(&child))\n \t\tdie(_(\"Could not apply '%s'\"), file);\n \ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 27b9e78094d..9e54892311d 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -944,15 +944,13 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)\n \n \tif (the_repository->settings.core_commit_graph) {\n \t\tstruct child_process commit_graph_verify = CHILD_PROCESS_INIT;\n-\t\tconst char *verify_argv[] = { \"commit-graph\", \"verify\", NULL, NULL, NULL };\n \n \t\tprepare_alt_odb(the_repository);\n \t\tfor (odb = the_repository->objects->odb; odb; odb = odb->next) {\n \t\t\tchild_process_init(&commit_graph_verify);\n-\t\t\tcommit_graph_verify.argv = verify_argv;\n \t\t\tcommit_graph_verify.git_cmd = 1;\n-\t\t\tverify_argv[2] = \"--object-dir\";\n-\t\t\tverify_argv[3] = odb->path;\n+\t\t\tstrvec_pushl(&commit_graph_verify.args, \"commit-graph\",\n+\t\t\t\t     \"verify\", \"--object-dir\", odb->path, NULL);\n \t\t\tif (run_command(&commit_graph_verify))\n \t\t\t\terrors_found |= ERROR_COMMIT_GRAPH;\n \t\t}\n@@ -960,15 +958,13 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)\n \n \tif (the_repository->settings.core_multi_pack_index) {\n \t\tstruct child_process midx_verify = CHILD_PROCESS_INIT;\n-\t\tconst char *midx_argv[] = { \"multi-pack-index\", \"verify\", NULL, NULL, NULL };\n \n \t\tprepare_alt_odb(the_repository);\n \t\tfor (odb = the_repository->objects->odb; odb; odb = odb->next) {\n \t\t\tchild_process_init(&midx_verify);\n-\t\t\tmidx_verify.argv = midx_argv;\n \t\t\tmidx_verify.git_cmd = 1;\n-\t\t\tmidx_argv[2] = \"--object-dir\";\n-\t\t\tmidx_argv[3] = odb->path;\n+\t\t\tstrvec_pushl(&midx_verify.args, \"multi-pack-index\",\n+\t\t\t\t     \"verify\", \"--object-dir\", odb->path, NULL);\n \t\t\tif (run_command(&midx_verify))\n \t\t\t\terrors_found |= ERROR_MULTI_PACK_INDEX;\n \t\t}\ndiff --git a/builtin/help.c b/builtin/help.c\nindex 75cd2fb407f..d387131dd83 100644\n--- a/builtin/help.c\n+++ b/builtin/help.c\n@@ -212,11 +212,10 @@ static int check_emacsclient_version(void)\n {\n \tstruct strbuf buffer = STRBUF_INIT;\n \tstruct child_process ec_process = CHILD_PROCESS_INIT;\n-\tconst char *argv_ec[] = { \"emacsclient\", \"--version\", NULL };\n \tint version;\n \n \t/* emacsclient prints its version number on stderr */\n-\tec_process.argv = argv_ec;\n+\tstrvec_pushl(&ec_process.args, \"emacsclient\", \"--version\", NULL);\n \tec_process.err = -1;\n \tec_process.stdout_to_stderr = 1;\n \tif (start_command(&ec_process))\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex ea3112e0c0b..5f0476b0b76 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -310,10 +310,9 @@ static int save_state(struct object_id *stash)\n \tint len;\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n \tstruct strbuf buffer = STRBUF_INIT;\n-\tconst char *argv[] = {\"stash\", \"create\", NULL};\n \tint rc = -1;\n \n-\tcp.argv = argv;\n+\tstrvec_pushl(&cp.args, \"stash\", \"create\", NULL);\n \tcp.out = -1;\n \tcp.git_cmd = 1;\n \ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 946938d011e..6ff1734d587 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -258,11 +258,10 @@ static int import_object(struct object_id *oid, enum object_type type,\n \t\treturn error_errno(_(\"unable to open %s for reading\"), filename);\n \n \tif (!raw && type == OBJ_TREE) {\n-\t\tconst char *argv[] = { \"mktree\", NULL };\n \t\tstruct child_process cmd = CHILD_PROCESS_INIT;\n \t\tstruct strbuf result = STRBUF_INIT;\n \n-\t\tcmd.argv = argv;\n+\t\tstrvec_push(&cmd.args, \"mktree\");\n \t\tcmd.git_cmd = 1;\n \t\tcmd.in = fd;\n \t\tcmd.out = -1;\ndiff --git a/daemon.c b/daemon.c\nindex 8df21f2130c..cc278077d2f 100644\n--- a/daemon.c\n+++ b/daemon.c\n@@ -326,22 +326,13 @@ static int run_access_hook(struct daemon_service *service, const char *dir,\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n-\tconst char *argv[8];\n-\tconst char **arg = argv;\n \tchar *eol;\n \tint seen_errors = 0;\n \n-\t*arg++ = access_hook;\n-\t*arg++ = service->name;\n-\t*arg++ = path;\n-\t*arg++ = hi->hostname.buf;\n-\t*arg++ = get_canon_hostname(hi);\n-\t*arg++ = get_ip_address(hi);\n-\t*arg++ = hi->tcp_port.buf;\n-\t*arg = NULL;\n-\n \tchild.use_shell = 1;\n-\tchild.argv = argv;\n+\tstrvec_pushl(&child.args, access_hook, service->name, path,\n+\t\t     hi->hostname.buf, get_canon_hostname(hi),\n+\t\t     get_ip_address(hi), hi->tcp_port.buf, NULL);\n \tchild.no_stdin = 1;\n \tchild.no_stderr = 1;\n \tchild.out = -1;\ndiff --git a/diff.c b/diff.c\nindex 861282db1c3..70711090b29 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -6921,19 +6921,14 @@ static char *run_textconv(struct repository *r,\n \t\t\t  size_t *outsize)\n {\n \tstruct diff_tempfile *temp;\n-\tconst char *argv[3];\n-\tconst char **arg = argv;\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n \tint err = 0;\n \n \ttemp = prepare_temp_file(r, spec->path, spec);\n-\t*arg++ = pgm;\n-\t*arg++ = temp->name;\n-\t*arg = NULL;\n \n \tchild.use_shell = 1;\n-\tchild.argv = argv;\n+\tstrvec_pushl(&child.args, pgm, temp->name, NULL);\n \tchild.out = -1;\n \tif (start_command(&child)) {\n \t\tremove_tempfile();\ndiff --git a/prompt.c b/prompt.c\nindex 5ded21a017f..aff9d7b65b2 100644\n--- a/prompt.c\n+++ b/prompt.c\n@@ -8,15 +8,10 @@\n static char *do_askpass(const char *cmd, const char *prompt)\n {\n \tstruct child_process pass = CHILD_PROCESS_INIT;\n-\tconst char *args[3];\n \tstatic struct strbuf buffer = STRBUF_INIT;\n \tint err = 0;\n \n-\targs[0] = cmd;\n-\targs[1]\t= prompt;\n-\targs[2] = NULL;\n-\n-\tpass.argv = args;\n+\tstrvec_pushl(&pass.args, cmd, prompt, NULL);\n \tpass.out = -1;\n \n \tif (start_command(&pass))\ndiff --git a/upload-pack.c b/upload-pack.c\nindex c78d55bc674..9b5db32623f 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -596,14 +596,11 @@ static int do_reachable_revlist(struct child_process *cmd,\n \t\t\t\tstruct object_array *reachable,\n \t\t\t\tenum allow_uor allow_uor)\n {\n-\tstatic const char *argv[] = {\n-\t\t\"rev-list\", \"--stdin\", NULL,\n-\t};\n \tstruct object *o;\n \tFILE *cmd_in = NULL;\n \tint i;\n \n-\tcmd->argv = argv;\n+\tstrvec_pushl(&cmd->args, \"rev-list\", \"--stdin\", NULL);\n \tcmd->git_cmd = 1;\n \tcmd->no_stderr = 1;\n \tcmd->in = -1;\n-- \n2.34.0.822.gb876f875f1b\n\n"},{"id":"441964","messageId":"211122.86a6hwyx1b.gmgdl@evledraar.gmail.com","threadId":"56951","inReplyTo":"20211122153119.h2t2ti3lkiycd7pb@cyberdelia","subject":"Re: [PATCH v2] pager: fix crash when pager program doesn't exist","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-22T16:22:34Z","receivedAt":"2021-11-22T16:28:54Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Nov 22 2021, Enzo Matsumiya wrote:\n\n> On 11/21, Junio C Hamano wrote:\n>>It is rather common for us to reuse \"struct child_process\" in a code\n>>path, e.g. builtin/worktree.c::add_worktree() prepares a single\n>>instance of such a struct, sets cp.git_cmd to true, and runs either\n>>\"update-ref\" or \"symbolic-ref\" to update \"HEAD\".  After successful\n>>invocation of such a git subcommand, it then runs \"git reset --hard\",\n>>with this piece of code:\n>\n> Do you agree that at least NULLing .argv and .env could be part of\n> child_process_clear()?\n>\n> diff --git a/run-command.c b/run-command.c\n> index a7bf81025afb..3839a26eff11 100644\n> --- a/run-command.c\n> +++ b/run-command.c\n> @@ -18,8 +18,9 @@ void child_process_init(struct child_process *child)\n>  void child_process_clear(struct child_process *child)\n>  {\n>         strvec_clear(&child->args);\n> +       child->argv = NULL;\n>         strvec_clear(&child->env_array);\n> +       child->env = NULL;\n>  }\n>\n> With this change on main, all tests are successful.\n\nThat's what your patch that calls child_process_init() is trying to do,\nbut it fails tests on \"master\". The \"child\" pointer here is valid, so\nwhat's going on with the memcpy() not working, but these two assignments\ndoing the job?\n\nI haven't looked into why, but I'd think whatever the difference is\nshould be moved into making child_process_init() handle that, and\ncontinuing to call that in child_process_clear(), if we go for the\napproach in your patch.\n\nI think an alternate direction of simply getting rid of \"argv\" is better\nin this case, and I've just submitted a topic to do that:\nhttps://lore.kernel.org/git/cover-0.5-00000000000-20211122T153605Z-avarab@gmail.com/\n\nIt includes the test you noted to make the pager crash.\n\nIt still leave us with this oddity:\n\n    $ ~/g/git/git -c pager.show=INVALID_PAGER show  HEAD \n    error: cannot run INVALID_PAGER: No such file or directory\n    error: cannot run INVALID_PAGER: No such file or directory\n\nBut that was the case before that topic (if we hadn't\ncrashed/segfaulted), and with your proposed change here.\n"},{"id":"441965","messageId":"YZvFkwivicJ/tFAo@coredump.intra.peff.net","threadId":"56951","inReplyTo":"20211122153119.h2t2ti3lkiycd7pb@cyberdelia","subject":"Re: [PATCH v2] pager: fix crash when pager program doesn't exist","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-11-22T16:30:11Z","receivedAt":"2021-11-22T16:30:18Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Nov 22, 2021 at 12:31:19PM -0300, Enzo Matsumiya wrote:\n\n> On 11/21, Junio C Hamano wrote:\n> > It is rather common for us to reuse \"struct child_process\" in a code\n> > path, e.g. builtin/worktree.c::add_worktree() prepares a single\n> > instance of such a struct, sets cp.git_cmd to true, and runs either\n> > \"update-ref\" or \"symbolic-ref\" to update \"HEAD\".  After successful\n> > invocation of such a git subcommand, it then runs \"git reset --hard\",\n> > with this piece of code:\n> \n> Do you agree that at least NULLing .argv and .env could be part of\n> child_process_clear()?\n> \n> diff --git a/run-command.c b/run-command.c\n> index a7bf81025afb..3839a26eff11 100644\n> --- a/run-command.c\n> +++ b/run-command.c\n> @@ -18,8 +18,9 @@ void child_process_init(struct child_process *child)\n>  void child_process_clear(struct child_process *child)\n>  {\n>         strvec_clear(&child->args);\n> +       child->argv = NULL;\n>         strvec_clear(&child->env_array);\n> +       child->env = NULL;\n>  }\n\nIt's still potentially dangerous, as callers which do not use\nchild->args could be pointing child->argv to to a more stable array, and\nexpecting it to be reused. It's definitely _less_ dangerous, and maybe\neven OK, but you'd still have to look at the callers.\n\nWhat would be safe is NULLing them if we know they point to the strvecs\nthat are about to be cleared, like so:\n\ndiff --git a/run-command.c b/run-command.c\nindex f40df01c77..60c7331213 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -19,6 +19,10 @@ void child_process_init(struct child_process *child)\n \n void child_process_clear(struct child_process *child)\n {\n+\tif (child->argv == child->args.v)\n+\t\tchild->argv = NULL;\n+\tif (child->env == child->env_array.v)\n+\t\tchild->env = NULL;\n \tstrvec_clear(&child->args);\n \tstrvec_clear(&child->env_array);\n }\n\n\n> With this change on main, all tests are successful.\n\nThat gives us some confidence, but I don't fully trust our tests to\ncover every corner case (the bug you found being a good example :) ).\n\n-Peff\n"},{"id":"441967","messageId":"20211122164635.6zrqjqow4xa7idnn@cyberdelia","threadId":"56951","inReplyTo":"211122.86a6hwyx1b.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v2] pager: fix crash when pager program doesn't exist","fromName":"Enzo Matsumiya","fromEmail":"ematsumiya@suse.de","sentAt":"2021-11-22T16:46:35Z","receivedAt":"2021-11-22T16:46:41Z","isPatch":true,"sender":{"key":"ematsumiya@suse.de","avatar":"https://avatars.githubusercontent.com/u/45605263?v=4"},"body":"On 11/22, Ævar Arnfjörð Bjarmason wrote:\n>I think an alternate direction of simply getting rid of \"argv\" is better\n>in this case, and I've just submitted a topic to do that:\n>https://lore.kernel.org/git/cover-0.5-00000000000-20211122T153605Z-avarab@gmail.com/\n\nWell, this is my first interaction with git's source, so I can't say for\nsure, but that solution seems more complete indeed.\n\n>It still leave us with this oddity:\n>\n>    $ ~/g/git/git -c pager.show=INVALID_PAGER show  HEAD\n>    error: cannot run INVALID_PAGER: No such file or directory\n>    error: cannot run INVALID_PAGER: No such file or directory\n>\n>But that was the case before that topic (if we hadn't\n>crashed/segfaulted), and with your proposed change here.\n\nYes, I did find it weird on my initial debugging, but didn't care at\nfirst.\n\nNow, looking again, it's because git (main command) have a higher\nprecedence on pager preference, so it tries to setup/run the pager\nbefore running subcommands.\n\nAn issue I've hit now is, if we don't want to fallback to any other\nsetting, this works:\n\ndiff --git a/pager.c b/pager.c\nindex d93304527d62..b528bbd644b5 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -110,6 +110,14 @@ void setup_pager(void)\n         if (!pager)\n                 return;\n\n+       /*\n+        * There's already a pager set up and running.\n+        * Regardless if it was successful or not, we shouldn't try running\n+        * it again.\n+        */\n+       if (pager_in_use())\n+               return;\n+\n         /*\n          * After we redirect standard output, we won't be able to use an ioctl\n          * to get the terminal size. Let's grab it now, and then set $COLUMNS\n\nHowever, IMHO it would make sense to try pager.<subcommand> if a\nprevious attempt failed, e.g.:\n\n$ git config pager.show my-valid-pager\n$ GIT_PAGER=invalid-pager git -p show\n\nSo this will first try invalid-pager, fail, and not try again, with the\nabove patch. As a user, I would expect that my-valid-pager to be run in\ncase invalid-pager failed.\n\nWhat do you think?\n\n\nCheers,\n\nEnzo\n"},{"id":"441970","messageId":"YZvNFhpHDImF8g19@coredump.intra.peff.net","threadId":"56951","inReplyTo":"patch-2.5-a411098699d-20211122T153605Z-avarab@gmail.com","subject":"Re: [PATCH 2/5] upload-archive: use regular \"struct child_process\" pattern","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-11-22T17:02:14Z","receivedAt":"2021-11-22T17:02:17Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Nov 22, 2021 at 05:04:04PM +0100, Ævar Arnfjörð Bjarmason wrote:\n\n> This pattern added [1] in seems to have been intentional, but since\n> [2] and [3] we've wanted do initialization of what's now the \"struct\n> strvec\" \"args\" and \"env_array\" members. Let's not trample on that\n> initialization here.\n\nYes, I came across this one, too, while looking at the same topic. I\nthink this is a good change, but:\n\n>  int cmd_upload_archive(int argc, const char **argv, const char *prefix)\n>  {\n> -\tstruct child_process writer = { argv };\n> +\tstruct child_process writer = CHILD_PROCESS_INIT;\n>  \n>  \tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n>  \t\tusage(upload_archive_usage);\n> @@ -92,6 +92,9 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix)\n>  \targv[0] = \"upload-archive--writer\";\n\nYou can drop this assignment over argv[0] now.\n\n>  \twriter.out = writer.err = -1;\n>  \twriter.git_cmd = 1;\n> +\tstrvec_push(&writer.args, \"upload-archive--writer\");\n> +\tif (argc > 1)\n> +\t\tstrvec_pushv(&writer.args, &argv[1]);\n\nThe \"argc > 1\" isn't necessary. If it is not true, strvec_pushv() will\nsee the terminating NULL in the list and just become a noop.\n\n(I'd also spell it \"argv + 1\" to make it more obvious that we are\ninterested in the list and not a single element, but that may be a\nmatter of taste).\n\n-Peff\n"},{"id":"441972","messageId":"xmqqczmsi0i5.fsf@gitster.g","threadId":"56951","inReplyTo":"20211122145222.m2zrmtbaeu5kzbtt@cyberdelia","subject":"Re: [PATCH v2] pager: fix crash when pager program doesn't exist","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-22T17:05:54Z","receivedAt":"2021-11-22T17:05:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Enzo Matsumiya <ematsumiya@suse.de> writes:\n\n> I'm preparing v3 with the above suggestions in mind.\n\nThanks for an update, and thanks for working on this one.\n\n\n"},{"id":"441974","messageId":"xmqq8rxgi0ej.fsf@gitster.g","threadId":"56951","inReplyTo":"211122.86a6hwyx1b.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v2] pager: fix crash when pager program doesn't exist","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-22T17:08:04Z","receivedAt":"2021-11-22T17:08:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> I think an alternate direction of simply getting rid of \"argv\" is better\n> in this case, and I've just submitted a topic to do that:\n> https://lore.kernel.org/git/cover-0.5-00000000000-20211122T153605Z-avarab@gmail.com/\n\nI see you just submitted, but I am more curious on when you started\nworking on it.\n\nThanks.\n"},{"id":"441975","messageId":"211122.861r38yuun.gmgdl@evledraar.gmail.com","threadId":"56951","inReplyTo":"20211122164635.6zrqjqow4xa7idnn@cyberdelia","subject":"Re: [PATCH v2] pager: fix crash when pager program doesn't exist","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-22T17:10:47Z","receivedAt":"2021-11-22T17:16:04Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Nov 22 2021, Enzo Matsumiya wrote:\n\n> On 11/22, Ævar Arnfjörð Bjarmason wrote:\n>>I think an alternate direction of simply getting rid of \"argv\" is better\n>>in this case, and I've just submitted a topic to do that:\n>>https://lore.kernel.org/git/cover-0.5-00000000000-20211122T153605Z-avarab@gmail.com/\n>\n> Well, this is my first interaction with git's source, so I can't say for\n> sure, but that solution seems more complete indeed.\n>\n>>It still leave us with this oddity:\n>>\n>>    $ ~/g/git/git -c pager.show=INVALID_PAGER show  HEAD\n>>    error: cannot run INVALID_PAGER: No such file or directory\n>>    error: cannot run INVALID_PAGER: No such file or directory\n>>\n>>But that was the case before that topic (if we hadn't\n>>crashed/segfaulted), and with your proposed change here.\n>\n> Yes, I did find it weird on my initial debugging, but didn't care at\n> first.\n>\n> Now, looking again, it's because git (main command) have a higher\n> precedence on pager preference, so it tries to setup/run the pager\n> before running subcommands.\n>\n> An issue I've hit now is, if we don't want to fallback to any other\n> setting, this works:\n>\n> diff --git a/pager.c b/pager.c\n> index d93304527d62..b528bbd644b5 100644\n> --- a/pager.c\n> +++ b/pager.c\n> @@ -110,6 +110,14 @@ void setup_pager(void)\n>         if (!pager)\n>                 return;\n>\n> +       /*\n> +        * There's already a pager set up and running.\n> +        * Regardless if it was successful or not, we shouldn't try running\n> +        * it again.\n> +        */\n> +       if (pager_in_use())\n> +               return;\n> +\n>         /*\n>          * After we redirect standard output, we won't be able to use an ioctl\n>          * to get the terminal size. Let's grab it now, and then set $COLUMNS\n>\n> However, IMHO it would make sense to try pager.<subcommand> if a\n> previous attempt failed, e.g.:\n>\n> $ git config pager.show my-valid-pager\n> $ GIT_PAGER=invalid-pager git -p show\n>\n> So this will first try invalid-pager, fail, and not try again, with the\n> above patch. As a user, I would expect that my-valid-pager to be run in\n> case invalid-pager failed.\n>\n> What do you think?\n\nI think a better approach is to just die early and not fallback if the\nuser's pager setting is broken. I.e. let's not second-guess their\nexplicit configuration, trust it, and if it doesn't work die() or\nerror().\n\nWe do fallback on emitting to stdout, so perhaps there's a reason to do\nmore exhaustive fallbacks here, I'm not really sure about the above.\n\nThe use of pager_in_use() in the above patch smells iffy though, we're\nable to do that because we did the setenv() of GIT_PAGER_IN_USE=true.\n\nWe then use that to check if we set it up ourselves and skip setting it\nup, but any other program invoked by us will be fooled by\nGIT_PAGER_IN_USE=true. Maybe that's intentional, but won't we then\nexpect a working pager in some cases (maybe not)...\n\nIt seems non-obvious at best, perhaps we should push a list of failed\npagers, or just the one failed one if we're not doing fallbacks..\n\nPresumably we can invoke N git <something>, where those <something> have\ndifferent pager.<something> config...\n\n\n\n"},{"id":"441977","messageId":"YZvUN0kuTpmf9Q7P@coredump.intra.peff.net","threadId":"56951","inReplyTo":"patch-5.5-ea1011f7473-20211122T153605Z-avarab@gmail.com","subject":"Re: [PATCH 5/5] run-command API: remove \"argv\" member, always use \"args\"","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-11-22T17:32:39Z","receivedAt":"2021-11-22T17:32:47Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Nov 22, 2021 at 05:04:07PM +0100, Ævar Arnfjörð Bjarmason wrote:\n\n> This change is larger than I'd like, but there's no easy way to avoid\n> it that wouldn't involve even more verbose intermediate steps. We use\n> the \"argv\" as the source of truth over the \"args\", so we need to\n> change all parts of run-command.[ch] itself, as well as the trace2\n> logging at the same time.\n\nYeah, agreed most of this needs to come in a bundle. But at least few spots\ncould be split out into the earlier patches:\n\n>  builtin/worktree.c          |  2 --\n>  t/helper/test-run-command.c | 10 +++++---\n\nas they are just users of the API.\n\n> diff --git a/t/helper/test-run-command.c b/t/helper/test-run-command.c\n> index 3c4fb862234..b5519f92a19 100644\n> --- a/t/helper/test-run-command.c\n> +++ b/t/helper/test-run-command.c\n> [...]\n> @@ -396,7 +396,8 @@ int cmd__run_command(int argc, const char **argv)\n>  \t}\n>  \tif (argc < 3)\n>  \t\treturn 1;\n> -\tproc.argv = (const char **)argv + 2;\n> +\tstrvec_clear(&proc.args);\n> +\tstrvec_pushv(&proc.args, (const char **)argv + 2);\n>  \n>  \tif (!strcmp(argv[1], \"start-command-ENOENT\")) {\n>  \t\tif (start_command(&proc) < 0 && errno == ENOENT)\n> @@ -408,7 +409,8 @@ int cmd__run_command(int argc, const char **argv)\n>  \t\texit(run_command(&proc));\n>  \n>  \tjobs = atoi(argv[2]);\n> -\tproc.argv = (const char **)argv + 3;\n> +\tstrvec_clear(&proc.args);\n> +\tstrvec_pushv(&proc.args, (const char **)argv + 3);\n>  \n>  \tif (!strcmp(argv[1], \"run-command-parallel\"))\n>  \t\texit(run_processes_parallel(jobs, parallel_next,\n\nThese two in particular are tricky. This second strvec_clear() is\nnecessary because we are overwriting what was put into proc.args by the\nearlier strvec_pushv(). I think this is one of two interesting ways\nthese kinds of trivial conversions can fail:\n\n  - somebody is relying on the fact that \"argv = whatever\" is an\n    assignment, not an append (which is the case here)\n\n  - somebody is relying on the fact that assignment of the pointer is\n    shallow, whereas strvec_push() is doing a deep copy. So converting:\n\n      cp.argv = argv;\n      argv[1] = \"foo\";\n\n    to:\n\n      strvec_pushv(&cp.args, argv);\n      argv[1] = \"foo\";\n\n    is not correct. We wouldn't use the updated \"foo\". I didn't notice\n    any cases like this during my rough own rough conversion, but they\n    could be lurking.\n\nThe strvec_clear() in the first hunk above is also not necessary (nobody\nhas written anything before it), but I don't mind it for consistency /\nbeing defensive.\n\n> @@ -902,8 +900,9 @@ int start_command(struct child_process *cmd)\n>  #else\n>  {\n>  \tint fhin = 0, fhout = 1, fherr = 2;\n> -\tconst char **sargv = cmd->argv;\n> +\tconst char **sargv = strvec_detach(&cmd->args);\n>  \tstruct strvec nargv = STRVEC_INIT;\n> +\tconst char **temp_argv = NULL;\n\nI wondered about detaching here, because other parts of the code\n(finish_command(), tracing, etc) will be looking at cmd->args.v[0] for\nthe command name.\n\nBut we eventually overwrite it with munged results:\n\n> @@ -929,20 +928,26 @@ int start_command(struct child_process *cmd)\n>  \t\tfhout = dup(cmd->out);\n>  \n>  \tif (cmd->git_cmd)\n> -\t\tcmd->argv = prepare_git_cmd(&nargv, cmd->argv);\n> +\t\ttemp_argv = prepare_git_cmd(&nargv, sargv);\n>  \telse if (cmd->use_shell)\n> -\t\tcmd->argv = prepare_shell_cmd(&nargv, cmd->argv);\n> +\t\ttemp_argv = prepare_shell_cmd(&nargv, sargv);\n> +\telse\n> +\t\ttemp_argv = sargv;\n> +\tif (!temp_argv)\n> +\t\tBUG(\"should have some cmd->args to set\");\n> +\tstrvec_pushv(&cmd->args, temp_argv);\n> +\tstrvec_clear(&nargv);\n\nSo we have to do some replacement. I think the memory management gets\nconfusing here, though.\n\nAt this point \"temp_argv\" might point to nargv.v (in which case it is a\ndangling pointer after we clear nargv) or to the original sargv (in\nwhich case it is memory owned by us that must be freed). The former is\nOK, because we never look at it again. And the latter we eventually\nreattach into &cmd->args, but:\n\n> -\tstrvec_clear(&nargv);\n> -\tcmd->argv = sargv;\n> +\tstrvec_pushv(&cmd->args, sargv);;\n> +\tfree(sargv);\n\nI think we still have all the entries we pushed into cmd->args from\ntemp_argv earlier. So we'd need to strvec_clear() it before pushing\nsargv again.\n\nAnd then the free(sargv) is too shallow. It's just freeing the outer\narray, but sargv[0], etc, are leaked.\n\nI think what you really want is the equivalent of strvec_attach(). We\ndon't have that, but it's basically just:\n\n  void strvec_attach(struct strvec *s, const char **v)\n  {\n\t/*\n\t * this is convenient for our caller, but if we wanted to find\n\t * potential misuses, we could also BUG() if s.nr > 0\n\t */\n\tstrvec_clear(&s);\n\n        /* take over ownership */\n\ts->v = v;\n\n\t/* v must be NULL-terminated; count up to set number */\n\ts->nr = 0;\n\tfor (; *v; v++)\n\t\ts->nr++;\n\t/*\n\t * we don't know how big the original allocation was, so we can\n\t * assume only nr. But add 1 to account for the NULL.\n\t */\n\ts->alloc = s->nr + 1;\n  }\n\nI do think this whole thing would be easier to read if we left cmd->argv\nintact, and just operated on a separate argv strvec. That's what the\nnon-Windows side does. But that distinction is nothing new in your\npatch, and I'm not sure if there is a reason that the Windows code does\nit differently.\n\n-Peff\n"},{"id":"441982","messageId":"YZvWO6MaZwTA86gc@coredump.intra.peff.net","threadId":"56951","inReplyTo":"211122.861r38yuun.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v2] pager: fix crash when pager program doesn't exist","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-11-22T17:41:15Z","receivedAt":"2021-11-22T17:41:19Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Nov 22, 2021 at 06:10:47PM +0100, Ævar Arnfjörð Bjarmason wrote:\n\n> I think a better approach is to just die early and not fallback if the\n> user's pager setting is broken. I.e. let's not second-guess their\n> explicit configuration, trust it, and if it doesn't work die() or\n> error().\n> \n> We do fallback on emitting to stdout, so perhaps there's a reason to do\n> more exhaustive fallbacks here, I'm not really sure about the above.\n\nI suspect this fallback-to-stdout does not kick in very much in\npractice. It only kicks in when start_command() fails, which means\neither:\n\n  - some OS-level error with fork, pipe(), etc\n\n  - execve() failed to find the program (we detect this even over fork()\n    with a magic protocol between the parent and child)\n\nThe second case is the more interesting one for a fallback, but it only\nworks if we aren't running the pager through a shell. And we do set\nuse_shell, so it kicks in at all only because of our \"skip the shell\"\noptimization when the command looks simple.\n\nSo this falls back:\n\n  $ GIT_PAGER=does-not-exist git log -1 --pretty=format:foo\n  error: cannot run does-not-exist: No such file or directory\n  foo\n\nbut this does not:\n\n  $ GIT_PAGER='does-not-exist --arg' git log -1 --pretty=format:foo\n  does-not-exist --arg: 1: does-not-exist: not found\n\nIn the non-fallback case we just send all output (including stderr!) to\na dead pipe (and probably die with SIGPIPE, though it's racy).\n\nSo we may be better to just die() immediately when pager setup fails.\nThat naturally fixes this bug, too. ;)\n\nOf course if we are going to do more exhaustive fallback, then it is\ngoing in the opposite direction. But I tend to agree that we should\nstick to what the user told us to do, and fail if it's not possible.\n\n-Peff\n"},{"id":"441984","messageId":"YZvY+BJhxaFIOdnJ@coredump.intra.peff.net","threadId":"56951","inReplyTo":"cover-0.5-00000000000-20211122T153605Z-avarab@gmail.com","subject":"Re: [PATCH 0/5] run-command API: get rid of \"argv\"","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-11-22T17:52:56Z","receivedAt":"2021-11-22T17:53:03Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Nov 22, 2021 at 05:04:02PM +0100, Ævar Arnfjörð Bjarmason wrote:\n\n> This series is an alternate but more thorough way to solve the pager\n> segfault reported by Enzo Matsumiya[1], and more generally avoids\n> similar issues in the future.\n> \n> That the run-command API exposed two subtly different ways of doing\n> the same thing wouldn't only lead to the sort of bug reported in [1],\n> but also made memory management around it rather painful. As noted by\n> Jeff King in[2]:\n> \n>     I'd like to eventually get rid of the argv interface entirely\n>     because it has memory-ownership semantics that are easy to get\n>     wrong.\n\nYeah, unsurprisingly I'm in favor of this direction (and in fact started\nlooking at myself before seeing your responses). It's big and complex\nenough that I do worry about prepending it in front of the segfault bug\nfix being discussed.\n\n> As noted in 5/5 we've still got a similar issue with \"env\" and\n> \"env_array\". I've got a follow-up series that similarly removes \"env\"\n> which we can do at some point (it's much smaller than this one), but\n> for now let's focus on \"argv\".\n\nI think we should probably do both, though I am OK with doing it\nseparately. There are fewer callers for \"env\", but I found more\nancillary cleanup necessary (e.g., \"const char **\" versus \"const char\n*const *\" headaches).\n\n> Ævar Arnfjörð Bjarmason (5):\n>   archive-tar: use our own cmd.buf in error message\n>   upload-archive: use regular \"struct child_process\" pattern\n>   run-command API users: use strvec_pushv(), not argv assignment\n>   run-command API users: use strvec_pushl(), not argv construction\n>   run-command API: remove \"argv\" member, always use \"args\"\n\nI left a few comments on individual patches. I had done a rough cut at\nthis, too. One big difference is that I used the opportunity to clean up\nsome ugly and error-prone uses of argv that are now unnecessary. For\ninstance:\n\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 2b2bac43f3..85d1abad88 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -134,14 +134,13 @@ static void copy_obj_to_fd(int fd, const struct object_id *oid)\n \n static void write_commented_object(int fd, const struct object_id *object)\n {\n-\tconst char *show_args[5] =\n-\t\t{\"show\", \"--stat\", \"--no-notes\", oid_to_hex(object), NULL};\n \tstruct child_process show = CHILD_PROCESS_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct strbuf cbuf = STRBUF_INIT;\n \n \t/* Invoke \"git show --stat --no-notes $object\" */\n-\tstrvec_pushv(&show.args, show_args);\n+\tstrvec_pushl(&show.args, \"show\", \"--stat\", \"--no-notes\",\n+\t\t     oid_to_hex(object), NULL);\n \tshow.no_stdin = 1;\n \tshow.out = -1;\n \tshow.err = 0;\n\nThe show_args variable is error-prone in two ways:\n\n  - the magic number \"5\" must be in sync with the rest of the array. In\n    this case it's superfluous and could just be removed, but I'll give\n    a related example below.\n\n  - we have to remember to include the trailing NULL. We have to for\n    pushl(), too, but in that case the compiler will warn us when we\n    omit it.\n\nHere's another one:\n\n@@ -943,23 +941,22 @@ static int run_receive_hook(struct command *commands,\n \n static int run_update_hook(struct command *cmd)\n {\n-\tconst char *argv[5];\n+\tconst char *hook_cmd;\n \tstruct child_process proc = CHILD_PROCESS_INIT;\n \tint code;\n \n-\targv[0] = find_hook(\"update\");\n-\tif (!argv[0])\n+\thook_cmd = find_hook(\"update\");\n+\tif (!hook_cmd)\n \t\treturn 0;\n \n-\targv[1] = cmd->ref_name;\n-\targv[2] = oid_to_hex(&cmd->old_oid);\n-\targv[3] = oid_to_hex(&cmd->new_oid);\n-\targv[4] = NULL;\n+\tstrvec_push(&proc.args, hook_cmd);\n+\tstrvec_push(&proc.args, cmd->ref_name);\n+\tstrvec_push(&proc.args, oid_to_hex(&cmd->old_oid));\n+\tstrvec_push(&proc.args, oid_to_hex(&cmd->new_oid));\n \n \tproc.no_stdin = 1;\n \tproc.stdout_to_stderr = 1;\n \tproc.err = use_sideband ? -1 : 0;\n-\tstrvec_pushv(&proc.args, argv);\n \tproc.trace2_hook_name = \"update\";\n\nIn this case the magic \"5\" really is important, and we get rid of it\n(and again don't need to worry about the terminating NULL).\n\nI'm on the fence on how important it is to do these cleanups. IMHO they\nare half of what really sells the change in the first place (since the\nother bug can pretty easily be fixed without it).\n\nBut maybe it is piling too much onto what is already a pretty big\nchange. The cleanups could be done individually later.\n\ndiff --git a/daemon.c b/daemon.c\nindex cc278077d2..4a000ee4af 100644\n--- a/daemon.c\n+++ b/daemon.c\n@@ -329,10 +329,15 @@ static int run_access_hook(struct daemon_service *service, const char *dir,\n \tchar *eol;\n \tint seen_errors = 0;\n \n+\tstrvec_push(&child.args, access_hook);\n+\tstrvec_push(&child.args, service->name);\n+\tstrvec_push(&child.args, path);\n+\tstrvec_push(&child.args, hi->hostname.buf);\n+\tstrvec_push(&child.args, get_canon_hostname(hi));\n+\tstrvec_push(&child.args, get_ip_address(hi));\n+\tstrvec_push(&child.args, hi->tcp_port.buf);\n+\n \tchild.use_shell = 1;\n-\tstrvec_pushl(&child.args, access_hook, service->name, path,\n-\t\t     hi->hostname.buf, get_canon_hostname(hi),\n-\t\t     get_ip_address(hi), hi->tcp_port.buf, NULL);\n \tchild.no_stdin = 1;\n \tchild.no_stderr = 1;\n \tchild.out = -1;\n\nI had other changes from yours like this. This is purely cosmetic, and I\ncould see arguments either way. I find the one-per-line version a bit\neasier to read. Even though it repeats child.args over and over, it's\neasy to look past since it's all aligned.\n\nI'm OK calling that bike-shedding, but I offer it mostly in case you\ndidn't try it the other way and actually like my color. ;)\n\n-Peff\n"},{"id":"441989","messageId":"xmqqk0h0gjnh.fsf@gitster.g","threadId":"56951","inReplyTo":"20211122164635.6zrqjqow4xa7idnn@cyberdelia","subject":"Re: [PATCH v2] pager: fix crash when pager program doesn't exist","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-22T17:55:14Z","receivedAt":"2021-11-22T17:55:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Enzo Matsumiya <ematsumiya@suse.de> writes:\n\n> However, IMHO it would make sense to try pager.<subcommand> if a\n> previous attempt failed, e.g.:\n>\n> $ git config pager.show my-valid-pager\n> $ GIT_PAGER=invalid-pager git -p show\n>\n> So this will first try invalid-pager, fail, and not try again, with the\n> above patch. As a user, I would expect that my-valid-pager to be run in\n> case invalid-pager failed.\n>\n> What do you think?\n\nInteresting.  As presented here, it makes it look as if the user is\ntrying to override configured values for this particular invocation,\nwhich would mean the environment must win (i.e. we should ignore\npager.<cmd> and only pay attention to GIT_PAGER), but if we consider\nthe way envionment is normally used, that is a faulty logic.  Just\nlike configuration variables are to set the value that the user\nnormally uses unless overridden, environment variables are the same\nway.\n\nI know $GIT_PAGER trumps core.pager, which indicates between\nequivalents, environment is taken as a stronger wish.  But I do not\nmind if the order were updated to pager.<cmd> trumps $GIT_PAGER,\nwhich in turn trumps core.pager, which in turn trumps $PAGER.\n"},{"id":"441991","messageId":"xmqqfsrogjf0.fsf@gitster.g","threadId":"56951","inReplyTo":"211122.861r38yuun.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v2] pager: fix crash when pager program doesn't exist","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-22T18:00:19Z","receivedAt":"2021-11-22T18:00:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> We then use that to check if we set it up ourselves and skip setting it\n> up, but any other program invoked by us will be fooled by\n> GIT_PAGER_IN_USE=true. Maybe that's intentional,...\n\nI think there is no other reason than to ensure subprocesses we\nspawn (be it git or somebody else that will later spawn git) are\naffected; otherwise a global variable in environment.c would have\nsufficed.\n\n> Presumably we can invoke N git <something>, where those <something> have\n> different pager.<something> config...\n\nA tangent, but pager.<cmd> should be renamed/transitioned to\npager.<cmd>.program, I would think.  Not that we allow characters\nthat are unsafe in the configuration variable names (like dot \".\")\nin the names of Git subcommands right now, but any name that is\ntaken from an unbounded set should not appear anywhere but the\nsecond level of a three-level configuration variable name.\n"},{"id":"441995","messageId":"xmqq7dd0giwp.fsf@gitster.g","threadId":"56951","inReplyTo":"YZvY+BJhxaFIOdnJ@coredump.intra.peff.net","subject":"Re: [PATCH 0/5] run-command API: get rid of \"argv\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-22T18:11:18Z","receivedAt":"2021-11-22T18:11:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> I'm on the fence on how important it is to do these cleanups. IMHO they\n> are half of what really sells the change in the first place (since the\n> other bug can pretty easily be fixed without it).\n\nYes.  I actually think these have much better value for their\ncomplexity, compared to the other \"half\" of the topic ;-)\n\n> But maybe it is piling too much onto what is already a pretty big\n> change. The cleanups could be done individually later.\n\nI am OK with that, too.  But I do agree that the series is too big\nto sit in front of a fix for a bug, for which a much simpler and\nfocused approach has already been discussed, to block it.\n\nThanks.\n"},{"id":"441996","messageId":"xmqq35nogijg.fsf@gitster.g","threadId":"56951","inReplyTo":"xmqqk0h0gjnh.fsf@gitster.g","subject":"Re: [PATCH v2] pager: fix crash when pager program doesn't exist","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-22T18:19:15Z","receivedAt":"2021-11-22T18:19:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> I know $GIT_PAGER trumps core.pager, which indicates between\n> equivalents, environment is taken as a stronger wish.  But I do not\n> mind if the order were updated to pager.<cmd> trumps $GIT_PAGER,\n> which in turn trumps core.pager, which in turn trumps $PAGER.\n\nIf such a precedence order makes it impossible to override a\nconfigured pager.<cmd> value at runtime, then it is a bad idea.  But\nluckily, we can do\n\n    git -c \"pager.<cmd>=<this one-shot pager>\" cmd ...\n\nto override a configured one, so perhaps it is OK.\n\nI tend to agree with opinions I read elsewhere in this thread that\nit would be better not to do the fallback in the first place, but\nin this case, what I said I am OK with is when pager.<cmd> is\ndefined, we do not even look at $GIT_PAGER or later choices, which\nis orthogonal.\n\n\n\n"},{"id":"441998","messageId":"211122.86wnl0xd24.gmgdl@evledraar.gmail.com","threadId":"56951","inReplyTo":"YZvUN0kuTpmf9Q7P@coredump.intra.peff.net","subject":"Re: [PATCH 5/5] run-command API: remove \"argv\" member, always use \"args\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-22T18:19:14Z","receivedAt":"2021-11-22T18:25:45Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Nov 22 2021, Jeff King wrote:\n\n> On Mon, Nov 22, 2021 at 05:04:07PM +0100, Ævar Arnfjörð Bjarmason wrote:\n>\n>> This change is larger than I'd like, but there's no easy way to avoid\n>> it that wouldn't involve even more verbose intermediate steps. We use\n>> the \"argv\" as the source of truth over the \"args\", so we need to\n>> change all parts of run-command.[ch] itself, as well as the trace2\n>> logging at the same time.\n>\n> Yeah, agreed most of this needs to come in a bundle. But at least few spots\n> could be split out into the earlier patches:\n>\n>>  builtin/worktree.c          |  2 --\n>>  t/helper/test-run-command.c | 10 +++++---\n>\n> as they are just users of the API.\n\nWill split these out, I had test-run-command.c split initially, but\nsquashed it locally, which did away with having to explain leaving this\narea untested as an intermediate step. But will split again.\n\n>> diff --git a/t/helper/test-run-command.c b/t/helper/test-run-command.c\n>> index 3c4fb862234..b5519f92a19 100644\n>> --- a/t/helper/test-run-command.c\n>> +++ b/t/helper/test-run-command.c\n>> [...]\n>> @@ -396,7 +396,8 @@ int cmd__run_command(int argc, const char **argv)\n>>  \t}\n>>  \tif (argc < 3)\n>>  \t\treturn 1;\n>> -\tproc.argv = (const char **)argv + 2;\n>> +\tstrvec_clear(&proc.args);\n>> +\tstrvec_pushv(&proc.args, (const char **)argv + 2);\n>>  \n>>  \tif (!strcmp(argv[1], \"start-command-ENOENT\")) {\n>>  \t\tif (start_command(&proc) < 0 && errno == ENOENT)\n>> @@ -408,7 +409,8 @@ int cmd__run_command(int argc, const char **argv)\n>>  \t\texit(run_command(&proc));\n>>  \n>>  \tjobs = atoi(argv[2]);\n>> -\tproc.argv = (const char **)argv + 3;\n>> +\tstrvec_clear(&proc.args);\n>> +\tstrvec_pushv(&proc.args, (const char **)argv + 3);\n>>  \n>>  \tif (!strcmp(argv[1], \"run-command-parallel\"))\n>>  \t\texit(run_processes_parallel(jobs, parallel_next,\n>\n> These two in particular are tricky. This second strvec_clear() is\n> necessary because we are overwriting what was put into proc.args by the\n> earlier strvec_pushv(). I think this is one of two interesting ways\n> these kinds of trivial conversions can fail:\n>\n>   - somebody is relying on the fact that \"argv = whatever\" is an\n>     assignment, not an append (which is the case here)\n>\n>   - somebody is relying on the fact that assignment of the pointer is\n>     shallow, whereas strvec_push() is doing a deep copy. So converting:\n>\n>       cp.argv = argv;\n>       argv[1] = \"foo\";\n>\n>     to:\n>\n>       strvec_pushv(&cp.args, argv);\n>       argv[1] = \"foo\";\n>\n>     is not correct. We wouldn't use the updated \"foo\". I didn't notice\n>     any cases like this during my rough own rough conversion, but they\n>     could be lurking.\n\nYes, I tried to eyeball all the code involved & see if there were any. I\ncould amend this to start renaming variables, but that'll be a much\nlarger change.\n\n> The strvec_clear() in the first hunk above is also not necessary (nobody\n> has written anything before it), but I don't mind it for consistency /\n> being defensive.\n\n*nod*, will remove.\n\n>> @@ -902,8 +900,9 @@ int start_command(struct child_process *cmd)\n>>  #else\n>>  {\n>>  \tint fhin = 0, fhout = 1, fherr = 2;\n>> -\tconst char **sargv = cmd->argv;\n>> +\tconst char **sargv = strvec_detach(&cmd->args);\n>>  \tstruct strvec nargv = STRVEC_INIT;\n>> +\tconst char **temp_argv = NULL;\n>\n> I wondered about detaching here, because other parts of the code\n> (finish_command(), tracing, etc) will be looking at cmd->args.v[0] for\n> the command name.\n>\n> But we eventually overwrite it with munged results:\n>\n>> @@ -929,20 +928,26 @@ int start_command(struct child_process *cmd)\n>>  \t\tfhout = dup(cmd->out);\n>>  \n>>  \tif (cmd->git_cmd)\n>> -\t\tcmd->argv = prepare_git_cmd(&nargv, cmd->argv);\n>> +\t\ttemp_argv = prepare_git_cmd(&nargv, sargv);\n>>  \telse if (cmd->use_shell)\n>> -\t\tcmd->argv = prepare_shell_cmd(&nargv, cmd->argv);\n>> +\t\ttemp_argv = prepare_shell_cmd(&nargv, sargv);\n>> +\telse\n>> +\t\ttemp_argv = sargv;\n>> +\tif (!temp_argv)\n>> +\t\tBUG(\"should have some cmd->args to set\");\n>> +\tstrvec_pushv(&cmd->args, temp_argv);\n>> +\tstrvec_clear(&nargv);\n>\n> So we have to do some replacement. I think the memory management gets\n> confusing here, though.\n>\n> At this point \"temp_argv\" might point to nargv.v (in which case it is a\n> dangling pointer after we clear nargv) or to the original sargv (in\n> which case it is memory owned by us that must be freed). The former is\n> OK, because we never look at it again. And the latter we eventually\n> reattach into &cmd->args, but:\n>\n>> -\tstrvec_clear(&nargv);\n>> -\tcmd->argv = sargv;\n>> +\tstrvec_pushv(&cmd->args, sargv);;\n>> +\tfree(sargv);\n>\n> I think we still have all the entries we pushed into cmd->args from\n> temp_argv earlier. So we'd need to strvec_clear() it before pushing\n> sargv again.\n>\n> And then the free(sargv) is too shallow. It's just freeing the outer\n> array, but sargv[0], etc, are leaked.\n\nI'll try to fix that, but updates to this part are very painful, since\nI've needed to wait 30m for each change in the Windows CI.\n\n> I think what you really want is the equivalent of strvec_attach(). We\n> don't have that, but it's basically just:\n>\n>   void strvec_attach(struct strvec *s, const char **v)\n>   {\n> \t/*\n> \t * this is convenient for our caller, but if we wanted to find\n> \t * potential misuses, we could also BUG() if s.nr > 0\n> \t */\n> \tstrvec_clear(&s);\n>\n>         /* take over ownership */\n> \ts->v = v;\n>\n> \t/* v must be NULL-terminated; count up to set number */\n> \ts->nr = 0;\n> \tfor (; *v; v++)\n> \t\ts->nr++;\n> \t/*\n> \t * we don't know how big the original allocation was, so we can\n> \t * assume only nr. But add 1 to account for the NULL.\n> \t */\n> \ts->alloc = s->nr + 1;\n>   }\n>\n> I do think this whole thing would be easier to read if we left cmd->argv\n> intact, and just operated on a separate argv strvec. That's what the\n> non-Windows side does. But that distinction is nothing new in your\n> patch, and I'm not sure if there is a reason that the Windows code does\n> it differently.\n\nI did have this with a strvec_attach() locally, but figured I'd get\ncomments about growing scope & with just one caller.\n\nThis version seems to be duplicating things in the existing API though,\nI just had the below, which I think works just as well without the\nduplication. Perhaps you missed strvec_push_nodup()?\n\ndiff --git a/strvec.c b/strvec.c\nindex 61a76ce6cb9..c10008d792f 100644\n--- a/strvec.c\n+++ b/strvec.c\n@@ -106,3 +106,9 @@ const char **strvec_detach(struct strvec *array)\n                return ret;\n        }\n }\n+\n+void strvec_attach(struct strvec *array, const char **items)\n+{\n+       for (; *items; items++)\n+               strvec_push_nodup(array, *items);\n+}\n"},{"id":"441999","messageId":"YZvg8xnt0KrjqSgq@coredump.intra.peff.net","threadId":"56951","inReplyTo":"xmqqfsrogjf0.fsf@gitster.g","subject":"Re: [PATCH v2] pager: fix crash when pager program doesn't exist","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-11-22T18:26:59Z","receivedAt":"2021-11-22T18:27:09Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Nov 22, 2021 at 10:00:19AM -0800, Junio C Hamano wrote:\n\n> > Presumably we can invoke N git <something>, where those <something> have\n> > different pager.<something> config...\n> \n> A tangent, but pager.<cmd> should be renamed/transitioned to\n> pager.<cmd>.program, I would think.  Not that we allow characters\n> that are unsafe in the configuration variable names (like dot \".\")\n> in the names of Git subcommands right now, but any name that is\n> taken from an unbounded set should not appear anywhere but the\n> second level of a three-level configuration variable name.\n\nIt does come up even now for custom commands or aliases. Here's an old\ndiscussion of somebody with an underscore:\n\n  https://lore.kernel.org/git/20150206124528.GA18859@inner.h.apk.li/\n\nIt would also allow other per-pager settings, though I don't off-hand\nremember any that would be useful. Splitting \"enabled\" from \"command\"\nwould be nice, but not that important. Per-pager color.pager settings\nwould potentially be useful, but it's a pretty obscure feature in the\nfirst place.\n\nI'm definitely in favor of this in the long term, but I don't think\nthere's any urgency in attaching it to the current discussion (that lack\nof urgency is why it has gone un-implemented for the past 6 years).\n\n-Peff\n"},{"id":"442000","messageId":"211122.86sfvoxcv6.gmgdl@evledraar.gmail.com","threadId":"56951","inReplyTo":"YZvY+BJhxaFIOdnJ@coredump.intra.peff.net","subject":"Re: [PATCH 0/5] run-command API: get rid of \"argv\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-22T18:26:26Z","receivedAt":"2021-11-22T18:29:53Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Nov 22 2021, Jeff King wrote:\n\n> On Mon, Nov 22, 2021 at 05:04:02PM +0100, Ævar Arnfjörð Bjarmason wrote:\n>\n>> This series is an alternate but more thorough way to solve the pager\n>> segfault reported by Enzo Matsumiya[1], and more generally avoids\n>> similar issues in the future.\n>> \n>> That the run-command API exposed two subtly different ways of doing\n>> the same thing wouldn't only lead to the sort of bug reported in [1],\n>> but also made memory management around it rather painful. As noted by\n>> Jeff King in[2]:\n>> \n>>     I'd like to eventually get rid of the argv interface entirely\n>>     because it has memory-ownership semantics that are easy to get\n>>     wrong.\n>\n> Yeah, unsurprisingly I'm in favor of this direction (and in fact started\n> looking at myself before seeing your responses). It's big and complex\n> enough that I do worry about prepending it in front of the segfault bug\n> fix being discussed.\n>\n>> As noted in 5/5 we've still got a similar issue with \"env\" and\n>> \"env_array\". I've got a follow-up series that similarly removes \"env\"\n>> which we can do at some point (it's much smaller than this one), but\n>> for now let's focus on \"argv\".\n>\n> I think we should probably do both, though I am OK with doing it\n> separately. There are fewer callers for \"env\", but I found more\n> ancillary cleanup necessary (e.g., \"const char **\" versus \"const char\n> *const *\" headaches).\n>\n>> Ævar Arnfjörð Bjarmason (5):\n>>   archive-tar: use our own cmd.buf in error message\n>>   upload-archive: use regular \"struct child_process\" pattern\n>>   run-command API users: use strvec_pushv(), not argv assignment\n>>   run-command API users: use strvec_pushl(), not argv construction\n>>   run-command API: remove \"argv\" member, always use \"args\"\n>\n> I left a few comments on individual patches. I had done a rough cut at\n> this, too. One big difference is that I used the opportunity to clean up\n> some ugly and error-prone uses of argv that are now unnecessary. For\n> instance:\n>\n> diff --git a/builtin/notes.c b/builtin/notes.c\n> index 2b2bac43f3..85d1abad88 100644\n> --- a/builtin/notes.c\n> +++ b/builtin/notes.c\n> @@ -134,14 +134,13 @@ static void copy_obj_to_fd(int fd, const struct object_id *oid)\n>  \n>  static void write_commented_object(int fd, const struct object_id *object)\n>  {\n> -\tconst char *show_args[5] =\n> -\t\t{\"show\", \"--stat\", \"--no-notes\", oid_to_hex(object), NULL};\n>  \tstruct child_process show = CHILD_PROCESS_INIT;\n>  \tstruct strbuf buf = STRBUF_INIT;\n>  \tstruct strbuf cbuf = STRBUF_INIT;\n>  \n>  \t/* Invoke \"git show --stat --no-notes $object\" */\n> -\tstrvec_pushv(&show.args, show_args);\n> +\tstrvec_pushl(&show.args, \"show\", \"--stat\", \"--no-notes\",\n> +\t\t     oid_to_hex(object), NULL);\n>  \tshow.no_stdin = 1;\n>  \tshow.out = -1;\n>  \tshow.err = 0;\n>\n> The show_args variable is error-prone in two ways:\n>\n>   - the magic number \"5\" must be in sync with the rest of the array. In\n>     this case it's superfluous and could just be removed, but I'll give\n>     a related example below.\n>\n>   - we have to remember to include the trailing NULL. We have to for\n>     pushl(), too, but in that case the compiler will warn us when we\n>     omit it.\n>\n> Here's another one:\n>\n> @@ -943,23 +941,22 @@ static int run_receive_hook(struct command *commands,\n>  \n>  static int run_update_hook(struct command *cmd)\n>  {\n> -\tconst char *argv[5];\n> +\tconst char *hook_cmd;\n>  \tstruct child_process proc = CHILD_PROCESS_INIT;\n>  \tint code;\n>  \n> -\targv[0] = find_hook(\"update\");\n> -\tif (!argv[0])\n> +\thook_cmd = find_hook(\"update\");\n> +\tif (!hook_cmd)\n>  \t\treturn 0;\n>  \n> -\targv[1] = cmd->ref_name;\n> -\targv[2] = oid_to_hex(&cmd->old_oid);\n> -\targv[3] = oid_to_hex(&cmd->new_oid);\n> -\targv[4] = NULL;\n> +\tstrvec_push(&proc.args, hook_cmd);\n> +\tstrvec_push(&proc.args, cmd->ref_name);\n> +\tstrvec_push(&proc.args, oid_to_hex(&cmd->old_oid));\n> +\tstrvec_push(&proc.args, oid_to_hex(&cmd->new_oid));\n>  \n>  \tproc.no_stdin = 1;\n>  \tproc.stdout_to_stderr = 1;\n>  \tproc.err = use_sideband ? -1 : 0;\n> -\tstrvec_pushv(&proc.args, argv);\n>  \tproc.trace2_hook_name = \"update\";\n>\n> In this case the magic \"5\" really is important, and we get rid of it\n> (and again don't need to worry about the terminating NULL).\n>\n> I'm on the fence on how important it is to do these cleanups. IMHO they\n> are half of what really sells the change in the first place (since the\n> other bug can pretty easily be fixed without it).\n>\n> But maybe it is piling too much onto what is already a pretty big\n> change. The cleanups could be done individually later.\n\nYeah, those are nice. I did do most/all those initially myself, but\nended up ejecting them in anticipation of getting comments about runaway\nrefactoring, as they're not strictly necessary. But I can include them\nagain if you/Junio would like...\n\n> diff --git a/daemon.c b/daemon.c\n> index cc278077d2..4a000ee4af 100644\n> --- a/daemon.c\n> +++ b/daemon.c\n> @@ -329,10 +329,15 @@ static int run_access_hook(struct daemon_service *service, const char *dir,\n>  \tchar *eol;\n>  \tint seen_errors = 0;\n>  \n> +\tstrvec_push(&child.args, access_hook);\n> +\tstrvec_push(&child.args, service->name);\n> +\tstrvec_push(&child.args, path);\n> +\tstrvec_push(&child.args, hi->hostname.buf);\n> +\tstrvec_push(&child.args, get_canon_hostname(hi));\n> +\tstrvec_push(&child.args, get_ip_address(hi));\n> +\tstrvec_push(&child.args, hi->tcp_port.buf);\n> +\n>  \tchild.use_shell = 1;\n> -\tstrvec_pushl(&child.args, access_hook, service->name, path,\n> -\t\t     hi->hostname.buf, get_canon_hostname(hi),\n> -\t\t     get_ip_address(hi), hi->tcp_port.buf, NULL);\n>  \tchild.no_stdin = 1;\n>  \tchild.no_stderr = 1;\n>  \tchild.out = -1;\n>\n> I had other changes from yours like this. This is purely cosmetic, and I\n> could see arguments either way. I find the one-per-line version a bit\n> easier to read. Even though it repeats child.args over and over, it's\n> easy to look past since it's all aligned.\n>\n> I'm OK calling that bike-shedding, but I offer it mostly in case you\n> didn't try it the other way and actually like my color. ;)\n\nI do like it better :) It's another thing I did like that initiall, but\nended up moving to strvec_pushl(). IIRC because I got the opposite\nrequest on a recent bundle.c topic of mine (now landed). I.e. it used\nmultiple aligned strvec_push() initailly, and it was suggested to use\nstrvec_pushl() instead...\n"},{"id":"442001","messageId":"211122.86o86cxcnu.gmgdl@evledraar.gmail.com","threadId":"56951","inReplyTo":"xmqq7dd0giwp.fsf@gitster.g","subject":"Re: [PATCH 0/5] run-command API: get rid of \"argv\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-22T18:33:10Z","receivedAt":"2021-11-22T18:34:19Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Nov 22 2021, Junio C Hamano wrote:\n\n> Jeff King <peff@peff.net> writes:\n>\n>> I'm on the fence on how important it is to do these cleanups. IMHO they\n>> are half of what really sells the change in the first place (since the\n>> other bug can pretty easily be fixed without it).\n>\n> Yes.  I actually think these have much better value for their\n> complexity, compared to the other \"half\" of the topic ;-)\n>\n>> But maybe it is piling too much onto what is already a pretty big\n>> change. The cleanups could be done individually later.\n>\n> I am OK with that, too.  But I do agree that the series is too big\n> to sit in front of a fix for a bug, for which a much simpler and\n> focused approach has already been discussed, to block it.\n\nI'm happy to re-roll this on top of the more narrow fix. FWIW the bug's\nthere since at least v2.30.0 (just tested that, probably much older), so\nin that sense there's no urgency either way.\n"},{"id":"442003","messageId":"YZvjhF+KM/gzu7vN@coredump.intra.peff.net","threadId":"56951","inReplyTo":"xmqq35nogijg.fsf@gitster.g","subject":"Re: [PATCH v2] pager: fix crash when pager program doesn't exist","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-11-22T18:37:56Z","receivedAt":"2021-11-22T18:38:14Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Nov 22, 2021 at 10:19:15AM -0800, Junio C Hamano wrote:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n> > I know $GIT_PAGER trumps core.pager, which indicates between\n> > equivalents, environment is taken as a stronger wish.  But I do not\n> > mind if the order were updated to pager.<cmd> trumps $GIT_PAGER,\n> > which in turn trumps core.pager, which in turn trumps $PAGER.\n> \n> If such a precedence order makes it impossible to override a\n> configured pager.<cmd> value at runtime, then it is a bad idea.  But\n> luckily, we can do\n> \n>     git -c \"pager.<cmd>=<this one-shot pager>\" cmd ...\n> \n> to override a configured one, so perhaps it is OK.\n> \n> I tend to agree with opinions I read elsewhere in this thread that\n> it would be better not to do the fallback in the first place, but\n> in this case, what I said I am OK with is when pager.<cmd> is\n> defined, we do not even look at $GIT_PAGER or later choices, which\n> is orthogonal.\n\nFWIW, I also scratched my head while looking at this topic over seeing\n$GIT_PAGER take precedence over a command-specific pager.\n\nI do wonder if changing the override behavior would end up breaking some\nscripted uses, though. E.g., imagine some program which does custom\nmarkup of Git output, and ships with a wrapper script like:\n\n  $ cat pretty-git\n  #!/bin/sh\n  GIT_PAGER='markup-git-output | less' \"$@\"\n\n  $ pretty-git log -p\n\nwhich would now behave differently if the user has pager.log set.\n\nI dunno. That is perhaps a bit far-fetched. Most tools that I know of\nlike that (diff-highlight and diff-so-fancy) just tell you to set up\npager.log in the first place.\n\nJust grepping around on GitHub, I don't see many uses, but this one[1] for\nexample might get confused:\n\n  export GIT_PAGER=cat # disable pager when running interactively\n\nSo I dunno. It is probably unlikely to have much fallout. On the other\nhand, I kind of wonder if the benefit is worth changing now.\n\n-Peff\n\n[0] https://github.com/node-inspector/node-inspector/blob/79e01c049286374f86dd560742a614019c02402f/tools/git-changelog.sh#L38\n"},{"id":"442004","messageId":"211122.86k0h0xcdg.gmgdl@evledraar.gmail.com","threadId":"56951","inReplyTo":"xmqq8rxgi0ej.fsf@gitster.g","subject":"Re: [PATCH v2] pager: fix crash when pager program doesn't exist","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-22T18:35:43Z","receivedAt":"2021-11-22T18:40:33Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Nov 22 2021, Junio C Hamano wrote:\n\n> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n>\n>> I think an alternate direction of simply getting rid of \"argv\" is better\n>> in this case, and I've just submitted a topic to do that:\n>> https://lore.kernel.org/git/cover-0.5-00000000000-20211122T153605Z-avarab@gmail.com/\n>\n> I see you just submitted, but I am more curious on when you started\n> working on it.\n\nI've been using this series in my local build since mid-September, I\njust hadn't submitted it.\n\nI just did some cosmetic fixes to it today, and got the Windows CI\nworking with it (the GIT_WINDOWS_NATIVE code in run-command.c).\n"},{"id":"442006","messageId":"YZvlxn8EdL1V4Cg6@coredump.intra.peff.net","threadId":"56951","inReplyTo":"211122.86wnl0xd24.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 5/5] run-command API: remove \"argv\" member, always use \"args\"","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-11-22T18:47:34Z","receivedAt":"2021-11-22T18:47:45Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Nov 22, 2021 at 07:19:14PM +0100, Ævar Arnfjörð Bjarmason wrote:\n\n> I did have this with a strvec_attach() locally, but figured I'd get\n> comments about growing scope & with just one caller.\n\nI do think I'd rather see us avoiding have to do this attach() by\nrefactoring the Windows code. But I sympathize with your pain in the\nguess-and-wait-for-CI loop with Windows (I have an unrelated series I've\ndone that with, and it's rather awful).\n\n> This version seems to be duplicating things in the existing API though,\n> I just had the below, which I think works just as well without the\n> duplication. Perhaps you missed strvec_push_nodup()?\n> \n> diff --git a/strvec.c b/strvec.c\n> index 61a76ce6cb9..c10008d792f 100644\n> --- a/strvec.c\n> +++ b/strvec.c\n> @@ -106,3 +106,9 @@ const char **strvec_detach(struct strvec *array)\n>                 return ret;\n>         }\n>  }\n> +\n> +void strvec_attach(struct strvec *array, const char **items)\n> +{\n> +       for (; *items; items++)\n> +               strvec_push_nodup(array, *items);\n> +}\n\nThis isn't taking ownership of \"items\", though. We've attached the\nthings it points to, but not the array itself. It would perhaps be OK to\nfree() it here under the notion that we took ownership of it and it is\nours to do with as we please. Potentially a caller might expect that\nwe'd continue to use the attached buffer, but any such assumption is\ninvalid once another strvec_push() is called, since that could replace\nthe array anyway.\n\nIt's also slightly less efficient (it grows a new array unnecessarily),\nbut I doubt that matters much in practice.\n\n-Peff\n"},{"id":"442007","messageId":"YZvmSRz4oAzSHZgG@coredump.intra.peff.net","threadId":"56951","inReplyTo":"211122.86o86cxcnu.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 0/5] run-command API: get rid of \"argv\"","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-11-22T18:49:45Z","receivedAt":"2021-11-22T18:49:50Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Nov 22, 2021 at 07:33:10PM +0100, Ævar Arnfjörð Bjarmason wrote:\n\n> \n> On Mon, Nov 22 2021, Junio C Hamano wrote:\n> \n> > Jeff King <peff@peff.net> writes:\n> >\n> >> I'm on the fence on how important it is to do these cleanups. IMHO they\n> >> are half of what really sells the change in the first place (since the\n> >> other bug can pretty easily be fixed without it).\n> >\n> > Yes.  I actually think these have much better value for their\n> > complexity, compared to the other \"half\" of the topic ;-)\n> >\n> >> But maybe it is piling too much onto what is already a pretty big\n> >> change. The cleanups could be done individually later.\n> >\n> > I am OK with that, too.  But I do agree that the series is too big\n> > to sit in front of a fix for a bug, for which a much simpler and\n> > focused approach has already been discussed, to block it.\n> \n> I'm happy to re-roll this on top of the more narrow fix. FWIW the bug's\n> there since at least v2.30.0 (just tested that, probably much older), so\n> in that sense there's no urgency either way.\n\nI suspect it has been a problem since 43b0190224 (pager: lose a separate\nargv[], 2016-02-16) in v2.7.3. So yeah, definitely not urgent, but I do\nthink we can get out a 2-line minimal fix to start with.\n\n-Peff\n"},{"id":"442016","messageId":"xmqqzgpvexgq.fsf@gitster.g","threadId":"56951","inReplyTo":"YZvjhF+KM/gzu7vN@coredump.intra.peff.net","subject":"Re: [PATCH v2] pager: fix crash when pager program doesn't exist","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-22T20:39:49Z","receivedAt":"2021-11-22T20:40:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> Just grepping around on GitHub, I don't see many uses, but this one[1] for\n> example might get confused:\n>\n>   export GIT_PAGER=cat # disable pager when running interactively\n\nYeah, that is an excellent example that argues against changing\nthis.\n\n> So I dunno. It is probably unlikely to have much fallout. On the other\n> hand, I kind of wonder if the benefit is worth changing now.\n"},{"id":"442019","messageId":"211122.86fsrnyko4.gmgdl@evledraar.gmail.com","threadId":"56951","inReplyTo":"patch-2.5-a411098699d-20211122T153605Z-avarab@gmail.com","subject":"Re: [PATCH 2/5] upload-archive: use regular \"struct child_process\" pattern","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-22T20:53:34Z","receivedAt":"2021-11-22T20:56:01Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Nov 22 2021, Ævar Arnfjörð Bjarmason wrote:\n\n> This pattern added [1] in seems to have been intentional, but since\n> [2] and [3] we've wanted do initialization of what's now the \"struct\n> strvec\" \"args\" and \"env_array\" members. Let's not trample on that\n> initialization here.\n>\n> 1. 1bc01efed17 (upload-archive: use start_command instead of fork,\n>    2011-11-19)\n> 2. c460c0ecdca (run-command: store an optional argv_array, 2014-05-15)\n> 3. 9a583dc39e (run-command: add env_array, an optional argv_array for\n>    env, 2014-10-19)\n>\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> ---\n>  builtin/upload-archive.c | 5 ++++-\n>  1 file changed, 4 insertions(+), 1 deletion(-)\n>\n> diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c\n> index 24654b4c9bf..b4b9b3a6262 100644\n> --- a/builtin/upload-archive.c\n> +++ b/builtin/upload-archive.c\n> @@ -77,7 +77,7 @@ static ssize_t process_input(int child_fd, int band)\n>  \n>  int cmd_upload_archive(int argc, const char **argv, const char *prefix)\n>  {\n> -\tstruct child_process writer = { argv };\n> +\tstruct child_process writer = CHILD_PROCESS_INIT;\n>  \n>  \tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n>  \t\tusage(upload_archive_usage);\n> @@ -92,6 +92,9 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix)\n>  \targv[0] = \"upload-archive--writer\";\n>  \twriter.out = writer.err = -1;\n>  \twriter.git_cmd = 1;\n> +\tstrvec_push(&writer.args, \"upload-archive--writer\");\n> +\tif (argc > 1)\n> +\t\tstrvec_pushv(&writer.args, &argv[1]);\n>  \tif (start_command(&writer)) {\n>  \t\tint err = errno;\n>  \t\tpacket_write_fmt(1, \"NACK unable to spawn subprocess\\n\");\n\nChanging it to argv + 1 makes sense, I don't know why I did it like\nthat. It works, but is unusual.\n\nBut as to skipping the \"argc > 1\" test I've got this still:\n    \n    @@ -89,9 +89,11 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix)\n             * multiplexed out to our fd#1.  If the child dies, we tell the other\n             * end over channel #3.\n             */\n    -       argv[0] = \"upload-archive--writer\";\n            writer.out = writer.err = -1;\n            writer.git_cmd = 1;\n    +       strvec_push(&writer.args, \"upload-archive--writer\");\n    +       if (argc > 1)\n    +               strvec_pushv(&writer.args, argv + 1);\n            if (start_command(&writer)) {\n                    int err = errno;\n                    packet_write_fmt(1, \"NACK unable to spawn subprocess\\n\");\n\nWe'll segfault if we give NULL to strvec_pushv() so we still need that\ncheck. Were you thinking of strvec_pushl(), or am I missing something?\n\n(Even with strvec_pushl() we can't interpolate it as-is into the list,\nsince it'll point to uninitialized memory in this case).\n"},{"id":"442020","messageId":"xmqqr1b7ewbv.fsf@gitster.g","threadId":"56951","inReplyTo":"patch-1.5-1d8cab554bb-20211122T153605Z-avarab@gmail.com","subject":"Re: [PATCH 1/5] archive-tar: use our own cmd.buf in error message","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-22T21:04:20Z","receivedAt":"2021-11-22T21:04:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:\n\n> Use the \"cmd.buf\" we just created in this function, instead of the\n> argv[0], which we assigned \"cmd.buf\" for. This is in preparation for\n> getting rid of the use of \"argv\" in this function.\n>\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> ---\n>  archive-tar.c | 4 ++--\n>  1 file changed, 2 insertions(+), 2 deletions(-)\n>\n> diff --git a/archive-tar.c b/archive-tar.c\n> index 05d2455870d..4154d9a0953 100644\n> --- a/archive-tar.c\n> +++ b/archive-tar.c\n> @@ -447,7 +447,7 @@ static int write_tar_filter_archive(const struct archiver *ar,\n>  \tfilter.in = -1;\n>  \n>  \tif (start_command(&filter) < 0)\n> -\t\tdie_errno(_(\"unable to start '%s' filter\"), argv[0]);\n> +\t\tdie_errno(_(\"unable to start '%s' filter\"), cmd.buf);\n>  \tclose(1);\n>  \tif (dup2(filter.in, 1) < 0)\n>  \t\tdie_errno(_(\"unable to redirect descriptor\"));\n> @@ -457,7 +457,7 @@ static int write_tar_filter_archive(const struct archiver *ar,\n>  \n>  \tclose(1);\n>  \tif (finish_command(&filter) != 0)\n> -\t\tdie(_(\"'%s' filter reported error\"), argv[0]);\n> +\t\tdie(_(\"'%s' filter reported error\"), cmd.buf);\n>  \n>  \tstrbuf_release(&cmd);\n>  \treturn r;\n\nIs this really needed to be a separate step?  If we update this\nfunction to\n\n - lose local argv[] array;\n - instead use the embedded filter.args\n\ni.e. something like the attached, such a \"modern\" code that uses\nchild_process .args would not require further changes when the\nrun-command API is streamlined by the remainder of the series, no?\n\nIOW, if can arrange this step to be trivially and obviously correct\nso that it can go in without fixing any bugs (if exists) in the main\npart of the series, and the API update would not require such code\nthat already uses cp.args not cp.argv, that would be much easier to\ngrok.  With this \"this is half a change in preparation\", reviewers\nneed to keep this promise in their heads that argv[] will be then\nremoved from the function.\n\n archive-tar.c | 9 +++------\n 1 file changed, 3 insertions(+), 6 deletions(-)\n\ndiff --git c/archive-tar.c w/archive-tar.c\nindex 05d2455870..3c74db1746 100644\n--- c/archive-tar.c\n+++ w/archive-tar.c\n@@ -430,7 +430,6 @@ static int write_tar_filter_archive(const struct archiver *ar,\n {\n \tstruct strbuf cmd = STRBUF_INIT;\n \tstruct child_process filter = CHILD_PROCESS_INIT;\n-\tconst char *argv[2];\n \tint r;\n \n \tif (!ar->data)\n@@ -440,14 +439,12 @@ static int write_tar_filter_archive(const struct archiver *ar,\n \tif (args->compression_level >= 0)\n \t\tstrbuf_addf(&cmd, \" -%d\", args->compression_level);\n \n-\targv[0] = cmd.buf;\n-\targv[1] = NULL;\n-\tfilter.argv = argv;\n+\tstrvec_push(&filter.args, cmd.buf);\n \tfilter.use_shell = 1;\n \tfilter.in = -1;\n \n \tif (start_command(&filter) < 0)\n-\t\tdie_errno(_(\"unable to start '%s' filter\"), argv[0]);\n+\t\tdie_errno(_(\"unable to start '%s' filter\"), cmd.buf);\n \tclose(1);\n \tif (dup2(filter.in, 1) < 0)\n \t\tdie_errno(_(\"unable to redirect descriptor\"));\n@@ -457,7 +454,7 @@ static int write_tar_filter_archive(const struct archiver *ar,\n \n \tclose(1);\n \tif (finish_command(&filter) != 0)\n-\t\tdie(_(\"'%s' filter reported error\"), argv[0]);\n+\t\tdie(_(\"'%s' filter reported error\"), cmd.buf);\n \n \tstrbuf_release(&cmd);\n \treturn r;\n"},{"id":"442021","messageId":"YZwHNkf2l9uPrIJv@coredump.intra.peff.net","threadId":"56951","inReplyTo":"211122.86fsrnyko4.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 2/5] upload-archive: use regular \"struct child_process\" pattern","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-11-22T21:10:14Z","receivedAt":"2021-11-22T21:10:17Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Nov 22, 2021 at 09:53:34PM +0100, Ævar Arnfjörð Bjarmason wrote:\n\n> But as to skipping the \"argc > 1\" test I've got this still:\n>     \n>     @@ -89,9 +89,11 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix)\n>              * multiplexed out to our fd#1.  If the child dies, we tell the other\n>              * end over channel #3.\n>              */\n>     -       argv[0] = \"upload-archive--writer\";\n>             writer.out = writer.err = -1;\n>             writer.git_cmd = 1;\n>     +       strvec_push(&writer.args, \"upload-archive--writer\");\n>     +       if (argc > 1)\n>     +               strvec_pushv(&writer.args, argv + 1);\n>             if (start_command(&writer)) {\n>                     int err = errno;\n>                     packet_write_fmt(1, \"NACK unable to spawn subprocess\\n\");\n> \n> We'll segfault if we give NULL to strvec_pushv() so we still need that\n> check. Were you thinking of strvec_pushl(), or am I missing something?\n\nWe wouldn't be giving NULL to strvec_pushv(). We'd be giving argv+1,\nwhich is guaranteed non-NULL, but may point to NULL. In that case the\nloop condition in strvec_pushv() would turn it into a noop.\n\n-Peff\n"},{"id":"442022","messageId":"xmqqlf1fevm7.fsf@gitster.g","threadId":"56951","inReplyTo":"patch-3.5-fd6c4c960ba-20211122T153605Z-avarab@gmail.com","subject":"Re: [PATCH 3/5] run-command API users: use strvec_pushv(), not argv assignment","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-22T21:19:44Z","receivedAt":"2021-11-22T21:19:49Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:\n\n> diff --git a/add-patch.c b/add-patch.c\n> index 8c41cdfe39b..573eef0cc4a 100644\n> --- a/add-patch.c\n> +++ b/add-patch.c\n> @@ -413,7 +413,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n>  \t\tstrvec_push(&args, ps->items[i].original);\n>  \n>  \tsetup_child_process(s, &cp, NULL);\n> -\tcp.argv = args.v;\n> +\tstrvec_pushv(&cp.args, args.v);\n>  \tres = capture_command(&cp, plain, 0);\n>  \tif (res) {\n>  \t\tstrvec_clear(&args);\n> @@ -431,7 +431,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n>  \n>  \t\tsetup_child_process(s, &colored_cp, NULL);\n>  \t\txsnprintf((char *)args.v[color_arg_index], 8, \"--color\");\n> -\t\tcolored_cp.argv = args.v;\n> +\t\tstrvec_pushv(&colored_cp.args, args.v);\n>  \t\tcolored = &s->colored;\n>  \t\tres = capture_command(&colored_cp, colored, 0);\n>  \t\tstrvec_clear(&args);\n\nWe used to use the caller-supplied args, run-command API borrowed\nthat strvec by pointing at cp.argv, and because it is borrowed,\ncapture_command() did not use cp.args/cp.env_array and there was no\nleak inside run-command API side, whether capture_command succeeded\nor failed.  The code was using its own args, so it already correctly\nreleases it (we can see one such strvec_clear() here).\n \nOK.\n\nI gave the remainder only a cursory look so I cannot call it quite\n\"reviewed\", but presumably all the other changes in this patch are\nthe same way?\n\nThanks.\n"},{"id":"442024","messageId":"211122.86bl2byj2z.gmgdl@evledraar.gmail.com","threadId":"56951","inReplyTo":"xmqqlf1fevm7.fsf@gitster.g","subject":"Re: [PATCH 3/5] run-command API users: use strvec_pushv(), not argv assignment","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-22T21:30:02Z","receivedAt":"2021-11-22T21:30:48Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Nov 22 2021, Junio C Hamano wrote:\n\n> Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:\n>\n>> diff --git a/add-patch.c b/add-patch.c\n>> index 8c41cdfe39b..573eef0cc4a 100644\n>> --- a/add-patch.c\n>> +++ b/add-patch.c\n>> @@ -413,7 +413,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n>>  \t\tstrvec_push(&args, ps->items[i].original);\n>>  \n>>  \tsetup_child_process(s, &cp, NULL);\n>> -\tcp.argv = args.v;\n>> +\tstrvec_pushv(&cp.args, args.v);\n>>  \tres = capture_command(&cp, plain, 0);\n>>  \tif (res) {\n>>  \t\tstrvec_clear(&args);\n>> @@ -431,7 +431,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n>>  \n>>  \t\tsetup_child_process(s, &colored_cp, NULL);\n>>  \t\txsnprintf((char *)args.v[color_arg_index], 8, \"--color\");\n>> -\t\tcolored_cp.argv = args.v;\n>> +\t\tstrvec_pushv(&colored_cp.args, args.v);\n>>  \t\tcolored = &s->colored;\n>>  \t\tres = capture_command(&colored_cp, colored, 0);\n>>  \t\tstrvec_clear(&args);\n>\n> We used to use the caller-supplied args, run-command API borrowed\n> that strvec by pointing at cp.argv, and because it is borrowed,\n> capture_command() did not use cp.args/cp.env_array and there was no\n> leak inside run-command API side, whether capture_command succeeded\n> or failed.  The code was using its own args, so it already correctly\n> releases it (we can see one such strvec_clear() here).\n>  \n> OK.\n>\n> I gave the remainder only a cursory look so I cannot call it quite\n> \"reviewed\", but presumably all the other changes in this patch are\n> the same way?\n\nYes, I tried to group all of these cases together.\n"},{"id":"442025","messageId":"211122.867dczyirm.gmgdl@evledraar.gmail.com","threadId":"56951","inReplyTo":"YZwHNkf2l9uPrIJv@coredump.intra.peff.net","subject":"Re: [PATCH 2/5] upload-archive: use regular \"struct child_process\" pattern","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-22T21:36:31Z","receivedAt":"2021-11-22T21:37:06Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Nov 22 2021, Jeff King wrote:\n\n> On Mon, Nov 22, 2021 at 09:53:34PM +0100, Ævar Arnfjörð Bjarmason wrote:\n>\n>> But as to skipping the \"argc > 1\" test I've got this still:\n>>     \n>>     @@ -89,9 +89,11 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix)\n>>              * multiplexed out to our fd#1.  If the child dies, we tell the other\n>>              * end over channel #3.\n>>              */\n>>     -       argv[0] = \"upload-archive--writer\";\n>>             writer.out = writer.err = -1;\n>>             writer.git_cmd = 1;\n>>     +       strvec_push(&writer.args, \"upload-archive--writer\");\n>>     +       if (argc > 1)\n>>     +               strvec_pushv(&writer.args, argv + 1);\n>>             if (start_command(&writer)) {\n>>                     int err = errno;\n>>                     packet_write_fmt(1, \"NACK unable to spawn subprocess\\n\");\n>> \n>> We'll segfault if we give NULL to strvec_pushv() so we still need that\n>> check. Were you thinking of strvec_pushl(), or am I missing something?\n>\n> We wouldn't be giving NULL to strvec_pushv(). We'd be giving argv+1,\n> which is guaranteed non-NULL, but may point to NULL. In that case the\n> loop condition in strvec_pushv() would turn it into a noop.\n\nD'oh. Thanks. Yes I was stupidly conflating a case where I'd tested it\nwith strvec_pushl() and moved that dereferenced version to\nstrvec_pushv(), sorry.\n"},{"id":"442130","messageId":"patch-v2-1.9-9cc220ce5a3-20211123T115551Z-avarab@gmail.com","threadId":"56951","inReplyTo":"cover-v2-0.9-00000000000-20211123T115551Z-avarab@gmail.com","subject":"[PATCH v2 1/9] worktree: remove redundant NULL-ing of \"cp.argv","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T12:06:28Z","receivedAt":"2021-11-23T12:08:01Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"The clearing of \"argv\" was added in 7f44e3d1de0 (worktree: make setup\nof new HEAD distinct from worktree population, 2015-07-17) when the\n\"cp\" variable wasn't initialized. It hasn't been needed since\n542aa25d974 (use CHILD_PROCESS_INIT to initialize automatic variables,\n2016-08-05).\n\nLet's remove it to make a later change that gets rid of the \"argv\"\nmember from \"struct child_process\" smaller.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/worktree.c | 2 --\n 1 file changed, 2 deletions(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex d22ece93e1a..7264a5b5de0 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -355,7 +355,6 @@ static int add_worktree(const char *path, const char *refname,\n \t\tgoto done;\n \n \tif (opts->checkout) {\n-\t\tcp.argv = NULL;\n \t\tstrvec_clear(&cp.args);\n \t\tstrvec_pushl(&cp.args, \"reset\", \"--hard\", \"--no-recurse-submodules\", NULL);\n \t\tif (opts->quiet)\n@@ -390,7 +389,6 @@ static int add_worktree(const char *path, const char *refname,\n \t\t\tcp.stdout_to_stderr = 1;\n \t\t\tcp.dir = path;\n \t\t\tcp.env = env;\n-\t\t\tcp.argv = NULL;\n \t\t\tcp.trace2_hook_name = \"post-checkout\";\n \t\t\tstrvec_pushl(&cp.args, absolute_path(hook),\n \t\t\t\t     oid_to_hex(null_oid()),\n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442131","messageId":"cover-v2-0.9-00000000000-20211123T115551Z-avarab@gmail.com","threadId":"56951","inReplyTo":"cover-0.5-00000000000-20211122T153605Z-avarab@gmail.com","subject":"[PATCH v2 0/9] run-command API: get rid of \"argv\" and \"env\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T12:06:27Z","receivedAt":"2021-11-23T12:08:02Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"That the run-command API exposed two subtly different ways of doing\nthe same thing wouldn't only lead to the sort of bug reported in [1],\nbut also made memory management around it rather painful. As noted by\nJeff King in[2]:\n\n    I'd like to eventually get rid of the argv interface entirely\n    because it has memory-ownership semantics that are easy to get\n    wrong.\n\nAs noted in v1[1] there's probably never going to be a perfect time to\ndo this as a change to this widely used API will probably impact\nthings in-flight.\n\nNow seems to be a particularly good time though, merging this to\n\"seen\" only conflicts with my ab/config-based-hooks-2 in\nbuiltin/worktree.c. The resolution is trivial (just use the new hook\nAPI added in that topic).\n\nThis re-rolled v2 also had a semantic conflict with that topic's use\nof the \"env\" member, which is solved with a just-submitted update of\nmine[2].\n\nChanges since v2:\n\n * I ejected the test for the segfault this topic solves, I understood\n   that Junio wanted to fast-track a more limited fix based on Enzo\n   Matsumiya's [3]. In any case the two won't semantically conflict as\n   far as the test goes, this larger change also fixes the segfault.\n\n * I'm now removing the \"env\" member in addition to \"argv\". The two\n   patches at the end are new. Doing so was a much smaller change than\n   \"argv\".\n\n * I changed all the minimal refactorings to get rid of \"env\" to\n   larger changes to get rid of any function-local \"argv\" arrays with\n   a hardcoded size.\n\n * As a result, the mid-series is now structured in such a way as to\n   group changes where we can trivially prove that we're not\n   introducing a logic error (since the \"argv\" variable went away),\n   and those cases where we still keep the \"argv\" (usually from an\n   existing \"struct strvec\").\n\n * The whole Windows-specific complexity in v1 is gone. I didn't\n   really need to change the behavior of that code, now it's just a\n   simple search-replacement of s/cmd->argv/cmd->args.v/. It means we\n   clobber the \"env_array\" member as we were doing with the \"env\"\n   before, but since that's nothing new it doesn't require careful\n   review of change semantics.\n\n1. https://lore.kernel.org/git/cover-0.5-00000000000-20211122T153605Z-avarab@gmail.com/\n2. https://lore.kernel.org/git/cover-v5-00.17-00000000000-20211123T114206Z-avarab@gmail.com/\n3. https://lore.kernel.org/git/20211120194048.12125-1-ematsumiya@suse.de/\n\nÆvar Arnfjörð Bjarmason (9):\n  worktree: remove redundant NULL-ing of \"cp.argv\n  upload-archive: use regular \"struct child_process\" pattern\n  run-command API users: use strvec_pushv(), not argv assignment\n  run-command tests: use strvec_pushv(), not argv assignment\n  run-command API users: use strvec_pushl(), not argv construction\n  run-command API users: use strvec_push(), not argv construction\n  run-command API: remove \"argv\" member, always use \"args\"\n  difftool: use \"env_array\" to simplify memory management\n  run-command API: remove \"env\" member, always use \"env_array\"\n\n add-patch.c                 |  4 +-\n archive-tar.c               |  9 ++---\n builtin/add.c               |  7 +---\n builtin/difftool.c          | 14 ++-----\n builtin/fsck.c              | 12 ++----\n builtin/help.c              |  3 +-\n builtin/merge.c             |  3 +-\n builtin/notes.c             |  5 +--\n builtin/receive-pack.c      | 80 ++++++++++++++-----------------------\n builtin/replace.c           |  3 +-\n builtin/upload-archive.c    |  5 ++-\n builtin/worktree.c          |  8 ++--\n connected.c                 |  3 +-\n daemon.c                    | 20 ++++------\n diff.c                      |  8 +---\n editor.c                    |  8 ++--\n http-backend.c              |  2 +-\n http.c                      |  5 ++-\n object-file.c               |  2 +-\n prompt.c                    |  7 +---\n remote-curl.c               |  2 +-\n run-command.c               | 62 +++++++++++++---------------\n run-command.h               | 54 ++++++++++++-------------\n sequencer.c                 | 10 ++---\n sub-process.c               |  2 +-\n t/helper/test-run-command.c |  9 +++--\n t/helper/test-subprocess.c  |  2 +-\n trace2/tr2_tgt_event.c      |  2 +-\n trace2/tr2_tgt_normal.c     |  2 +-\n trace2/tr2_tgt_perf.c       |  4 +-\n trailer.c                   |  2 +-\n transport.c                 | 11 +++--\n upload-pack.c               |  5 +--\n 33 files changed, 154 insertions(+), 221 deletions(-)\n\nRange-diff against v1:\n -:  ----------- >  1:  9cc220ce5a3 worktree: remove redundant NULL-ing of \"cp.argv\n 2:  a411098699d !  2:  bfa65e5afd7 upload-archive: use regular \"struct child_process\" pattern\n    @@ builtin/upload-archive.c: static ssize_t process_input(int child_fd, int band)\n      \tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n      \t\tusage(upload_archive_usage);\n     @@ builtin/upload-archive.c: int cmd_upload_archive(int argc, const char **argv, const char *prefix)\n    - \targv[0] = \"upload-archive--writer\";\n    + \t * multiplexed out to our fd#1.  If the child dies, we tell the other\n    + \t * end over channel #3.\n    + \t */\n    +-\targv[0] = \"upload-archive--writer\";\n      \twriter.out = writer.err = -1;\n      \twriter.git_cmd = 1;\n     +\tstrvec_push(&writer.args, \"upload-archive--writer\");\n    -+\tif (argc > 1)\n    -+\t\tstrvec_pushv(&writer.args, &argv[1]);\n    ++\tstrvec_pushv(&writer.args, argv + 1);\n      \tif (start_command(&writer)) {\n      \t\tint err = errno;\n      \t\tpacket_write_fmt(1, \"NACK unable to spawn subprocess\\n\");\n 3:  fd6c4c960ba !  3:  61e4eb8e173 run-command API users: use strvec_pushv(), not argv assignment\n    @@ Commit message\n         run-command API users: use strvec_pushv(), not argv assignment\n     \n         Migrate those run-command API users that assign directly to the \"argv\"\n    -    member to use a strvec_pushv() of \"args\" instead, but exclude those\n    -    cases where we can't easily get rid of the construction of the \"argv\"\n    -    variable being given to the resulting \"strvec_pushv()\".\n    +    member to use a strvec_pushv() of \"args\" instead.\n     \n    -    This is in preparation for getting rid of the \"argv\" member from the\n    -    run-command API itself.\n    +    In these cases it did not make sense to further refactor these\n    +    callers, e.g. daemon.c could be made to construct the arguments closer\n    +    to handle(), but that would require moving the construction from its\n    +    cmd_main() and pass \"argv\" through two intermediate functions.\n    +\n    +    It would be possible for a change like this to introduce a regression\n    +    if we were doing:\n    +\n    +          cp.argv = argv;\n    +          argv[1] = \"foo\";\n    +\n    +    And changed the code, as is being done here, to:\n    +\n    +          strvec_pushv(&cp.args, argv);\n    +          argv[1] = \"foo\";\n    +\n    +    But as viewing this change with the \"-W\" flag reveals none of these\n    +    functions modify variable that's being pushed afterwards in a way that\n    +    would introduce such a logic error.\n     \n         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n     \n    @@ add-patch.c: static int parse_diff(struct add_p_state *s, const struct pathspec\n      \t\tres = capture_command(&colored_cp, colored, 0);\n      \t\tstrvec_clear(&args);\n     \n    - ## builtin/notes.c ##\n    -@@ builtin/notes.c: static void write_commented_object(int fd, const struct object_id *object)\n    - \tstruct strbuf cbuf = STRBUF_INIT;\n    - \n    - \t/* Invoke \"git show --stat --no-notes $object\" */\n    --\tshow.argv = show_args;\n    -+\tstrvec_pushv(&show.args, show_args);\n    - \tshow.no_stdin = 1;\n    - \tshow.out = -1;\n    - \tshow.err = 0;\n    -\n    - ## builtin/receive-pack.c ##\n    -@@ builtin/receive-pack.c: static int run_and_feed_hook(const char *hook_name, feed_fn feed,\n    - \n    - \targv[1] = NULL;\n    - \n    --\tproc.argv = argv;\n    -+\tstrvec_pushv(&proc.args, argv);\n    - \tproc.in = -1;\n    - \tproc.stdout_to_stderr = 1;\n    - \tproc.trace2_hook_name = hook_name;\n    -@@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n    - \tproc.no_stdin = 1;\n    - \tproc.stdout_to_stderr = 1;\n    - \tproc.err = use_sideband ? -1 : 0;\n    --\tproc.argv = argv;\n    -+\tstrvec_pushv(&proc.args, argv);\n    - \tproc.trace2_hook_name = \"update\";\n    - \n    - \tcode = start_command(&proc);\n    -@@ builtin/receive-pack.c: static int run_proc_receive_hook(struct command *commands,\n    - \t}\n    - \targv[1] = NULL;\n    - \n    --\tproc.argv = argv;\n    -+\tstrvec_pushv(&proc.args, argv);\n    - \tproc.in = -1;\n    - \tproc.out = -1;\n    - \tproc.trace2_hook_name = \"proc-receive\";\n    -@@ builtin/receive-pack.c: static const char *push_to_deploy(unsigned char *sha1,\n    - \t};\n    - \tstruct child_process child = CHILD_PROCESS_INIT;\n    - \n    --\tchild.argv = update_refresh;\n    -+\tstrvec_pushv(&child.args, update_refresh);\n    - \tchild.env = env->v;\n    - \tchild.dir = work_tree;\n    - \tchild.no_stdin = 1;\n    -@@ builtin/receive-pack.c: static const char *push_to_deploy(unsigned char *sha1,\n    - \n    - \t/* run_command() does not clean up completely; reinitialize */\n    - \tchild_process_init(&child);\n    --\tchild.argv = diff_files;\n    -+\tstrvec_pushv(&child.args, diff_files);\n    - \tchild.env = env->v;\n    - \tchild.dir = work_tree;\n    - \tchild.no_stdin = 1;\n    -@@ builtin/receive-pack.c: static const char *push_to_deploy(unsigned char *sha1,\n    - \tdiff_index[4] = head_has_history() ? \"HEAD\" : empty_tree_oid_hex();\n    - \n    - \tchild_process_init(&child);\n    --\tchild.argv = diff_index;\n    -+\tstrvec_pushv(&child.args, diff_index);\n    - \tchild.env = env->v;\n    - \tchild.no_stdin = 1;\n    - \tchild.no_stdout = 1;\n    -@@ builtin/receive-pack.c: static const char *push_to_deploy(unsigned char *sha1,\n    - \n    - \tread_tree[3] = hash_to_hex(sha1);\n    - \tchild_process_init(&child);\n    --\tchild.argv = read_tree;\n    -+\tstrvec_pushv(&child.args, read_tree);\n    - \tchild.env = env->v;\n    - \tchild.dir = work_tree;\n    - \tchild.no_stdin = 1;\n    -@@ builtin/receive-pack.c: int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n    - \t\t\tproc.stdout_to_stderr = 1;\n    - \t\t\tproc.err = use_sideband ? -1 : 0;\n    - \t\t\tproc.git_cmd = proc.close_object_store = 1;\n    --\t\t\tproc.argv = argv_gc_auto;\n    -+\t\t\tstrvec_pushv(&proc.args, argv_gc_auto);\n    - \n    - \t\t\tif (!start_command(&proc)) {\n    - \t\t\t\tif (use_sideband)\n    -\n      ## daemon.c ##\n     @@ daemon.c: static void handle(int incoming, struct sockaddr *addr, socklen_t addrlen)\n      #endif\n    @@ daemon.c: static void handle(int incoming, struct sockaddr *addr, socklen_t addr\n      \tcld.out = dup(incoming);\n      \n     \n    - ## editor.c ##\n    -@@ editor.c: static int launch_specified_editor(const char *editor, const char *path,\n    - \t\tstrbuf_realpath(&realpath, path, 1);\n    - \t\targs[1] = realpath.buf;\n    - \n    --\t\tp.argv = args;\n    -+\t\tstrvec_pushv(&p.args, args);\n    - \t\tp.env = env;\n    - \t\tp.use_shell = 1;\n    - \t\tp.trace2_child_class = \"editor\";\n    -\n      ## http-backend.c ##\n     @@ http-backend.c: static void run_service(const char **argv, int buffer_input)\n      \t\tstrvec_pushf(&cld.env_array,\n    @@ run-command.c: int run_command_v_opt_cd_env_tr2(const char **argv, int opt, cons\n      \tcmd.git_cmd = opt & RUN_GIT_CMD ? 1 : 0;\n      \tcmd.stdout_to_stderr = opt & RUN_COMMAND_STDOUT_TO_STDERR ? 1 : 0;\n     \n    - ## sequencer.c ##\n    -@@ sequencer.c: static int run_rewrite_hook(const struct object_id *oldoid,\n    - \targv[1] = \"amend\";\n    - \targv[2] = NULL;\n    - \n    --\tproc.argv = argv;\n    -+\tstrvec_pushv(&proc.args, argv);\n    - \tproc.in = -1;\n    - \tproc.stdout_to_stderr = 1;\n    - \tproc.trace2_hook_name = \"post-rewrite\";\n    -\n      ## t/helper/test-subprocess.c ##\n     @@ t/helper/test-subprocess.c: int cmd__subprocess(int argc, const char **argv)\n      \t\targv++;\n    @@ t/helper/test-subprocess.c: int cmd__subprocess(int argc, const char **argv)\n     +\tstrvec_pushv(&cp.args, (const char **)argv + 1);\n      \treturn run_command(&cp);\n      }\n    -\n    - ## transport.c ##\n    -@@ transport.c: static int run_pre_push_hook(struct transport *transport,\n    - \targv[2] = transport->url;\n    - \targv[3] = NULL;\n    - \n    --\tproc.argv = argv;\n    -+\tstrvec_pushv(&proc.args, argv);\n    - \tproc.in = -1;\n    - \tproc.trace2_hook_name = \"pre-push\";\n    - \n -:  ----------- >  4:  a2ee10e214c run-command tests: use strvec_pushv(), not argv assignment\n 4:  61751f55ff2 !  5:  2b446606eb9 run-command API users: use strvec_pushl(), not argv construction\n    @@ Metadata\n      ## Commit message ##\n         run-command API users: use strvec_pushl(), not argv construction\n     \n    -    Migrate those run-command API users that assign directly to the \"argv\"\n    -    member to use a strvec_pushl() of a list instead, this gets rid of the\n    -    intermediate \"const char *args[]\" these callers were using.\n    +    Change a pattern of hardcoding an \"argv\" array size, populating it and\n    +    assigning to the \"argv\" member of \"struct child_process\" to instead\n    +    use \"strvec_pushl()\" to add data to the \"args\" member.\n     \n    -    This is in preparation for getting rid of the \"argv\" member from the\n    -    run-command API itself.\n    +    This implements the same behavior as before in fewer lines of code,\n    +    and moves us further towards being able to remove the \"argv\" member in\n    +    a subsequent commit.\n     \n    -    Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n    +    Since we've entirely removed the \"argv\" variable(s) we can be sure\n    +    that no potential logic errors of the type discussed in a preceding\n    +    commit are being introduced here, i.e. ones where the local \"argv\" was\n    +    being modified after the assignment to \"struct child_process\"'s\n    +    \"argv\".\n     \n    - ## archive-tar.c ##\n    -@@ archive-tar.c: static int write_tar_filter_archive(const struct archiver *ar,\n    - {\n    - \tstruct strbuf cmd = STRBUF_INIT;\n    - \tstruct child_process filter = CHILD_PROCESS_INIT;\n    --\tconst char *argv[2];\n    - \tint r;\n    - \n    - \tif (!ar->data)\n    -@@ archive-tar.c: static int write_tar_filter_archive(const struct archiver *ar,\n    - \tif (args->compression_level >= 0)\n    - \t\tstrbuf_addf(&cmd, \" -%d\", args->compression_level);\n    - \n    --\targv[0] = cmd.buf;\n    --\targv[1] = NULL;\n    --\tfilter.argv = argv;\n    -+\tstrvec_push(&filter.args, cmd.buf);\n    - \tfilter.use_shell = 1;\n    - \tfilter.in = -1;\n    - \n    +    Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n     \n      ## builtin/add.c ##\n     @@ builtin/add.c: int interactive_add(const char **argv, const char *prefix, int patch)\n    @@ builtin/add.c: static int edit_patch(int argc, const char **argv, const char *pr\n      \n      \tchild.git_cmd = 1;\n     -\tchild.argv = apply_argv;\n    -+\tstrvec_pushl(&child.args, \"apply\", \"--recount\", \"--cached\", file, NULL);\n    ++\tstrvec_pushl(&child.args, \"apply\", \"--recount\", \"--cached\", file,\n    ++\t\t     NULL);\n      \tif (run_command(&child))\n      \t\tdie(_(\"Could not apply '%s'\"), file);\n      \n    @@ builtin/merge.c: static int save_state(struct object_id *stash)\n      \tcp.git_cmd = 1;\n      \n     \n    + ## builtin/notes.c ##\n    +@@ builtin/notes.c: static void copy_obj_to_fd(int fd, const struct object_id *oid)\n    + \n    + static void write_commented_object(int fd, const struct object_id *object)\n    + {\n    +-\tconst char *show_args[5] =\n    +-\t\t{\"show\", \"--stat\", \"--no-notes\", oid_to_hex(object), NULL};\n    + \tstruct child_process show = CHILD_PROCESS_INIT;\n    + \tstruct strbuf buf = STRBUF_INIT;\n    + \tstruct strbuf cbuf = STRBUF_INIT;\n    + \n    + \t/* Invoke \"git show --stat --no-notes $object\" */\n    +-\tshow.argv = show_args;\n    ++\tstrvec_pushl(&show.args, \"show\", \"--stat\", \"--no-notes\",\n    ++\t\t     oid_to_hex(object), NULL);\n    + \tshow.no_stdin = 1;\n    + \tshow.out = -1;\n    + \tshow.err = 0;\n    +\n    + ## builtin/receive-pack.c ##\n    +@@ builtin/receive-pack.c: static const char *push_to_deploy(unsigned char *sha1,\n    + \t\t\t\t  struct strvec *env,\n    + \t\t\t\t  const char *work_tree)\n    + {\n    +-\tconst char *update_refresh[] = {\n    +-\t\t\"update-index\", \"-q\", \"--ignore-submodules\", \"--refresh\", NULL\n    +-\t};\n    +-\tconst char *diff_files[] = {\n    +-\t\t\"diff-files\", \"--quiet\", \"--ignore-submodules\", \"--\", NULL\n    +-\t};\n    +-\tconst char *diff_index[] = {\n    +-\t\t\"diff-index\", \"--quiet\", \"--cached\", \"--ignore-submodules\",\n    +-\t\tNULL, \"--\", NULL\n    +-\t};\n    +-\tconst char *read_tree[] = {\n    +-\t\t\"read-tree\", \"-u\", \"-m\", NULL, NULL\n    +-\t};\n    + \tstruct child_process child = CHILD_PROCESS_INIT;\n    + \n    +-\tchild.argv = update_refresh;\n    ++\tstrvec_pushl(&child.args, \"update-index\", \"-q\", \"--ignore-submodules\",\n    ++\t\t     \"--refresh\", NULL);\n    + \tchild.env = env->v;\n    + \tchild.dir = work_tree;\n    + \tchild.no_stdin = 1;\n    +@@ builtin/receive-pack.c: static const char *push_to_deploy(unsigned char *sha1,\n    + \n    + \t/* run_command() does not clean up completely; reinitialize */\n    + \tchild_process_init(&child);\n    +-\tchild.argv = diff_files;\n    ++\tstrvec_pushl(&child.args, \"diff-files\", \"--quiet\",\n    ++\t\t     \"--ignore-submodules\", \"--\", NULL);\n    + \tchild.env = env->v;\n    + \tchild.dir = work_tree;\n    + \tchild.no_stdin = 1;\n    +@@ builtin/receive-pack.c: static const char *push_to_deploy(unsigned char *sha1,\n    + \tif (run_command(&child))\n    + \t\treturn \"Working directory has unstaged changes\";\n    + \n    +-\t/* diff-index with either HEAD or an empty tree */\n    +-\tdiff_index[4] = head_has_history() ? \"HEAD\" : empty_tree_oid_hex();\n    +-\n    + \tchild_process_init(&child);\n    +-\tchild.argv = diff_index;\n    ++\tstrvec_pushl(&child.args, \"diff-index\", \"--quiet\", \"--cached\",\n    ++\t\t     \"--ignore-submodules\",\n    ++\t\t     /* diff-index with either HEAD or an empty tree */\n    ++\t\t     head_has_history() ? \"HEAD\" : empty_tree_oid_hex(),\n    ++\t\t     \"--\", NULL);\n    + \tchild.env = env->v;\n    + \tchild.no_stdin = 1;\n    + \tchild.no_stdout = 1;\n    +@@ builtin/receive-pack.c: static const char *push_to_deploy(unsigned char *sha1,\n    + \tif (run_command(&child))\n    + \t\treturn \"Working directory has staged changes\";\n    + \n    +-\tread_tree[3] = hash_to_hex(sha1);\n    + \tchild_process_init(&child);\n    +-\tchild.argv = read_tree;\n    ++\tstrvec_pushl(&child.args, \"read-tree\", \"-u\", \"-m\", hash_to_hex(sha1),\n    ++\t\t     NULL);\n    + \tchild.env = env->v;\n    + \tchild.dir = work_tree;\n    + \tchild.no_stdin = 1;\n    +@@ builtin/receive-pack.c: int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n    + \t\trun_update_post_hook(commands);\n    + \t\tstring_list_clear(&push_options, 0);\n    + \t\tif (auto_gc) {\n    +-\t\t\tconst char *argv_gc_auto[] = {\n    +-\t\t\t\t\"gc\", \"--auto\", \"--quiet\", NULL,\n    +-\t\t\t};\n    + \t\t\tstruct child_process proc = CHILD_PROCESS_INIT;\n    + \n    + \t\t\tproc.no_stdin = 1;\n    + \t\t\tproc.stdout_to_stderr = 1;\n    + \t\t\tproc.err = use_sideband ? -1 : 0;\n    + \t\t\tproc.git_cmd = proc.close_object_store = 1;\n    +-\t\t\tproc.argv = argv_gc_auto;\n    ++\t\t\tstrvec_pushl(&proc.args, \"gc\", \"--auto\", \"--quiet\",\n    ++\t\t\t\t     NULL);\n    + \n    + \t\t\tif (!start_command(&proc)) {\n    + \t\t\t\tif (use_sideband)\n    +\n      ## builtin/replace.c ##\n     @@ builtin/replace.c: static int import_object(struct object_id *oid, enum object_type type,\n      \t\treturn error_errno(_(\"unable to open %s for reading\"), filename);\n    @@ builtin/replace.c: static int import_object(struct object_id *oid, enum object_t\n      \t\tcmd.in = fd;\n      \t\tcmd.out = -1;\n     \n    - ## daemon.c ##\n    -@@ daemon.c: static int run_access_hook(struct daemon_service *service, const char *dir,\n    - {\n    - \tstruct child_process child = CHILD_PROCESS_INIT;\n    - \tstruct strbuf buf = STRBUF_INIT;\n    --\tconst char *argv[8];\n    --\tconst char **arg = argv;\n    - \tchar *eol;\n    - \tint seen_errors = 0;\n    - \n    --\t*arg++ = access_hook;\n    --\t*arg++ = service->name;\n    --\t*arg++ = path;\n    --\t*arg++ = hi->hostname.buf;\n    --\t*arg++ = get_canon_hostname(hi);\n    --\t*arg++ = get_ip_address(hi);\n    --\t*arg++ = hi->tcp_port.buf;\n    --\t*arg = NULL;\n    --\n    - \tchild.use_shell = 1;\n    --\tchild.argv = argv;\n    -+\tstrvec_pushl(&child.args, access_hook, service->name, path,\n    -+\t\t     hi->hostname.buf, get_canon_hostname(hi),\n    -+\t\t     get_ip_address(hi), hi->tcp_port.buf, NULL);\n    - \tchild.no_stdin = 1;\n    - \tchild.no_stderr = 1;\n    - \tchild.out = -1;\n    + ## editor.c ##\n    +@@ editor.c: static int launch_specified_editor(const char *editor, const char *path,\n    + \n    + \tif (strcmp(editor, \":\")) {\n    + \t\tstruct strbuf realpath = STRBUF_INIT;\n    +-\t\tconst char *args[] = { editor, NULL, NULL };\n    + \t\tstruct child_process p = CHILD_PROCESS_INIT;\n    + \t\tint ret, sig;\n    + \t\tint print_waiting_for_editor = advice_enabled(ADVICE_WAITING_FOR_EDITOR) && isatty(2);\n    +@@ editor.c: static int launch_specified_editor(const char *editor, const char *path,\n    + \t\t}\n    + \n    + \t\tstrbuf_realpath(&realpath, path, 1);\n    +-\t\targs[1] = realpath.buf;\n    + \n    +-\t\tp.argv = args;\n    ++\t\tstrvec_pushl(&p.args, editor, realpath.buf, NULL);\n    + \t\tp.env = env;\n    + \t\tp.use_shell = 1;\n    + \t\tp.trace2_child_class = \"editor\";\n     \n    - ## diff.c ##\n    -@@ diff.c: static char *run_textconv(struct repository *r,\n    - \t\t\t  size_t *outsize)\n    + ## sequencer.c ##\n    +@@ sequencer.c: static int run_rewrite_hook(const struct object_id *oldoid,\n    + \t\t\t    const struct object_id *newoid)\n      {\n    - \tstruct diff_tempfile *temp;\n    + \tstruct child_process proc = CHILD_PROCESS_INIT;\n     -\tconst char *argv[3];\n    --\tconst char **arg = argv;\n    - \tstruct child_process child = CHILD_PROCESS_INIT;\n    - \tstruct strbuf buf = STRBUF_INIT;\n    - \tint err = 0;\n    - \n    - \ttemp = prepare_temp_file(r, spec->path, spec);\n    --\t*arg++ = pgm;\n    --\t*arg++ = temp->name;\n    --\t*arg = NULL;\n    - \n    - \tchild.use_shell = 1;\n    --\tchild.argv = argv;\n    -+\tstrvec_pushl(&child.args, pgm, temp->name, NULL);\n    - \tchild.out = -1;\n    - \tif (start_command(&child)) {\n    - \t\tremove_tempfile();\n    -\n    - ## prompt.c ##\n    -@@\n    - static char *do_askpass(const char *cmd, const char *prompt)\n    - {\n    - \tstruct child_process pass = CHILD_PROCESS_INIT;\n    --\tconst char *args[3];\n    - \tstatic struct strbuf buffer = STRBUF_INIT;\n    - \tint err = 0;\n    - \n    --\targs[0] = cmd;\n    --\targs[1]\t= prompt;\n    --\targs[2] = NULL;\n    --\n    --\tpass.argv = args;\n    -+\tstrvec_pushl(&pass.args, cmd, prompt, NULL);\n    - \tpass.out = -1;\n    + \tint code;\n    + \tstruct strbuf sb = STRBUF_INIT;\n    ++\tconst char *hook_path = find_hook(\"post-rewrite\");\n      \n    - \tif (start_command(&pass))\n    +-\targv[0] = find_hook(\"post-rewrite\");\n    +-\tif (!argv[0])\n    ++\tif (!hook_path)\n    + \t\treturn 0;\n    + \n    +-\targv[1] = \"amend\";\n    +-\targv[2] = NULL;\n    +-\n    +-\tproc.argv = argv;\n    ++\tstrvec_pushl(&proc.args, hook_path, \"amend\", NULL);\n    + \tproc.in = -1;\n    + \tproc.stdout_to_stderr = 1;\n    + \tproc.trace2_hook_name = \"post-rewrite\";\n     \n      ## upload-pack.c ##\n     @@ upload-pack.c: static int do_reachable_revlist(struct child_process *cmd,\n 1:  1d8cab554bb !  6:  fad420dc563 archive-tar: use our own cmd.buf in error message\n    @@ Metadata\n     Author: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n     \n      ## Commit message ##\n    -    archive-tar: use our own cmd.buf in error message\n    +    run-command API users: use strvec_push(), not argv construction\n     \n    -    Use the \"cmd.buf\" we just created in this function, instead of the\n    -    argv[0], which we assigned \"cmd.buf\" for. This is in preparation for\n    -    getting rid of the use of \"argv\" in this function.\n    +    Change a pattern of hardcoding an \"argv\" array size, populating it and\n    +    assigning to the \"argv\" member of \"struct child_process\" to instead\n    +    use \"strvec_push()\" to add data to the \"args\" member.\n    +\n    +    As noted in the preceding commit this moves us further towards being\n    +    able to remove the \"argv\" member in a subsequent commit\n    +\n    +    These callers could have used strvec_pushl(), but moving to\n    +    strvec_push() makes the diff easier to read, and keeps the arguments\n    +    aligned as before.\n     \n         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n     \n      ## archive-tar.c ##\n     @@ archive-tar.c: static int write_tar_filter_archive(const struct archiver *ar,\n    + {\n    + \tstruct strbuf cmd = STRBUF_INIT;\n    + \tstruct child_process filter = CHILD_PROCESS_INIT;\n    +-\tconst char *argv[2];\n    + \tint r;\n    + \n    + \tif (!ar->data)\n    +@@ archive-tar.c: static int write_tar_filter_archive(const struct archiver *ar,\n    + \tif (args->compression_level >= 0)\n    + \t\tstrbuf_addf(&cmd, \" -%d\", args->compression_level);\n    + \n    +-\targv[0] = cmd.buf;\n    +-\targv[1] = NULL;\n    +-\tfilter.argv = argv;\n    ++\tstrvec_push(&filter.args, cmd.buf);\n    + \tfilter.use_shell = 1;\n      \tfilter.in = -1;\n      \n      \tif (start_command(&filter) < 0)\n    @@ archive-tar.c: static int write_tar_filter_archive(const struct archiver *ar,\n      \n      \tstrbuf_release(&cmd);\n      \treturn r;\n    +\n    + ## builtin/receive-pack.c ##\n    +@@ builtin/receive-pack.c: static int run_and_feed_hook(const char *hook_name, feed_fn feed,\n    + {\n    + \tstruct child_process proc = CHILD_PROCESS_INIT;\n    + \tstruct async muxer;\n    +-\tconst char *argv[2];\n    + \tint code;\n    ++\tconst char *hook_path = find_hook(hook_name);\n    + \n    +-\targv[0] = find_hook(hook_name);\n    +-\tif (!argv[0])\n    ++\tif (!hook_path)\n    + \t\treturn 0;\n    + \n    +-\targv[1] = NULL;\n    +-\n    +-\tproc.argv = argv;\n    ++\tstrvec_push(&proc.args, hook_path);\n    + \tproc.in = -1;\n    + \tproc.stdout_to_stderr = 1;\n    + \tproc.trace2_hook_name = hook_name;\n    +@@ builtin/receive-pack.c: static int run_receive_hook(struct command *commands,\n    + \n    + static int run_update_hook(struct command *cmd)\n    + {\n    +-\tconst char *argv[5];\n    + \tstruct child_process proc = CHILD_PROCESS_INIT;\n    + \tint code;\n    ++\tconst char *hook_path = find_hook(\"update\");\n    + \n    +-\targv[0] = find_hook(\"update\");\n    +-\tif (!argv[0])\n    ++\tif (!hook_path)\n    + \t\treturn 0;\n    + \n    +-\targv[1] = cmd->ref_name;\n    +-\targv[2] = oid_to_hex(&cmd->old_oid);\n    +-\targv[3] = oid_to_hex(&cmd->new_oid);\n    +-\targv[4] = NULL;\n    ++\tstrvec_push(&proc.args, hook_path);\n    ++\tstrvec_push(&proc.args, cmd->ref_name);\n    ++\tstrvec_push(&proc.args, oid_to_hex(&cmd->old_oid));\n    ++\tstrvec_push(&proc.args, oid_to_hex(&cmd->new_oid));\n    + \n    + \tproc.no_stdin = 1;\n    + \tproc.stdout_to_stderr = 1;\n    + \tproc.err = use_sideband ? -1 : 0;\n    +-\tproc.argv = argv;\n    + \tproc.trace2_hook_name = \"update\";\n    + \n    + \tcode = start_command(&proc);\n    +@@ builtin/receive-pack.c: static int run_proc_receive_hook(struct command *commands,\n    + \tstruct child_process proc = CHILD_PROCESS_INIT;\n    + \tstruct async muxer;\n    + \tstruct command *cmd;\n    +-\tconst char *argv[2];\n    + \tstruct packet_reader reader;\n    + \tstruct strbuf cap = STRBUF_INIT;\n    + \tstruct strbuf errmsg = STRBUF_INIT;\n    + \tint hook_use_push_options = 0;\n    + \tint version = 0;\n    + \tint code;\n    ++\tconst char *hook_path = find_hook(\"proc-receive\");\n    + \n    +-\targv[0] = find_hook(\"proc-receive\");\n    +-\tif (!argv[0]) {\n    ++\tif (!hook_path) {\n    + \t\trp_error(\"cannot find hook 'proc-receive'\");\n    + \t\treturn -1;\n    + \t}\n    +-\targv[1] = NULL;\n    + \n    +-\tproc.argv = argv;\n    ++\tstrvec_push(&proc.args, hook_path);\n    + \tproc.in = -1;\n    + \tproc.out = -1;\n    + \tproc.trace2_hook_name = \"proc-receive\";\n    +\n    + ## daemon.c ##\n    +@@ daemon.c: static int run_access_hook(struct daemon_service *service, const char *dir,\n    + {\n    + \tstruct child_process child = CHILD_PROCESS_INIT;\n    + \tstruct strbuf buf = STRBUF_INIT;\n    +-\tconst char *argv[8];\n    +-\tconst char **arg = argv;\n    + \tchar *eol;\n    + \tint seen_errors = 0;\n    + \n    +-\t*arg++ = access_hook;\n    +-\t*arg++ = service->name;\n    +-\t*arg++ = path;\n    +-\t*arg++ = hi->hostname.buf;\n    +-\t*arg++ = get_canon_hostname(hi);\n    +-\t*arg++ = get_ip_address(hi);\n    +-\t*arg++ = hi->tcp_port.buf;\n    +-\t*arg = NULL;\n    ++\tstrvec_push(&child.args, access_hook);\n    ++\tstrvec_push(&child.args, service->name);\n    ++\tstrvec_push(&child.args, path);\n    ++\tstrvec_push(&child.args, hi->hostname.buf);\n    ++\tstrvec_push(&child.args, get_canon_hostname(hi));\n    ++\tstrvec_push(&child.args, get_ip_address(hi));\n    ++\tstrvec_push(&child.args, hi->tcp_port.buf);\n    + \n    + \tchild.use_shell = 1;\n    +-\tchild.argv = argv;\n    + \tchild.no_stdin = 1;\n    + \tchild.no_stderr = 1;\n    + \tchild.out = -1;\n    +\n    + ## diff.c ##\n    +@@ diff.c: static char *run_textconv(struct repository *r,\n    + \t\t\t  size_t *outsize)\n    + {\n    + \tstruct diff_tempfile *temp;\n    +-\tconst char *argv[3];\n    +-\tconst char **arg = argv;\n    + \tstruct child_process child = CHILD_PROCESS_INIT;\n    + \tstruct strbuf buf = STRBUF_INIT;\n    + \tint err = 0;\n    + \n    + \ttemp = prepare_temp_file(r, spec->path, spec);\n    +-\t*arg++ = pgm;\n    +-\t*arg++ = temp->name;\n    +-\t*arg = NULL;\n    ++\tstrvec_push(&child.args, pgm);\n    ++\tstrvec_push(&child.args, temp->name);\n    + \n    + \tchild.use_shell = 1;\n    +-\tchild.argv = argv;\n    + \tchild.out = -1;\n    + \tif (start_command(&child)) {\n    + \t\tremove_tempfile();\n    +\n    + ## prompt.c ##\n    +@@\n    + static char *do_askpass(const char *cmd, const char *prompt)\n    + {\n    + \tstruct child_process pass = CHILD_PROCESS_INIT;\n    +-\tconst char *args[3];\n    + \tstatic struct strbuf buffer = STRBUF_INIT;\n    + \tint err = 0;\n    + \n    +-\targs[0] = cmd;\n    +-\targs[1]\t= prompt;\n    +-\targs[2] = NULL;\n    ++\tstrvec_push(&pass.args, cmd);\n    ++\tstrvec_push(&pass.args, prompt);\n    + \n    +-\tpass.argv = args;\n    + \tpass.out = -1;\n    + \n    + \tif (start_command(&pass))\n    +\n    + ## transport.c ##\n    +@@ transport.c: static int run_pre_push_hook(struct transport *transport,\n    + \tstruct ref *r;\n    + \tstruct child_process proc = CHILD_PROCESS_INIT;\n    + \tstruct strbuf buf;\n    +-\tconst char *argv[4];\n    ++\tconst char *hook_path = find_hook(\"pre-push\");\n    + \n    +-\tif (!(argv[0] = find_hook(\"pre-push\")))\n    ++\tif (!hook_path)\n    + \t\treturn 0;\n    + \n    +-\targv[1] = transport->remote->name;\n    +-\targv[2] = transport->url;\n    +-\targv[3] = NULL;\n    ++\tstrvec_push(&proc.args, hook_path);\n    ++\tstrvec_push(&proc.args, transport->remote->name);\n    ++\tstrvec_push(&proc.args, transport->url);\n    + \n    +-\tproc.argv = argv;\n    + \tproc.in = -1;\n    + \tproc.trace2_hook_name = \"pre-push\";\n    + \n 5:  ea1011f7473 !  7:  67ab5114ed7 run-command API: remove \"argv\" member, always use \"args\"\n    @@ Commit message\n         API might be painful.\n     \n         We also have a recent report[2] of a user of the API segfaulting,\n    -    which is a direct result of it being complex to use. A test being\n    -    added here would segfault before this change.\n    +    which is a direct result of it being complex to use. This commit\n    +    addresses the root cause of that bug.\n     \n         This change is larger than I'd like, but there's no easy way to avoid\n         it that wouldn't involve even more verbose intermediate steps. We use\n    @@ Commit message\n         change all parts of run-command.[ch] itself, as well as the trace2\n         logging at the same time.\n     \n    -    We can also similarly follow-up and remove \"env\" member in favor of\n    -    \"env_array\", which is a much smaller change than this series removing\n    -    \"argv\", but let's stop at \"argv\" for now. We can always do \"env\" as a\n    -    follow-up.\n    -\n    -    I have not carefully tested the Windows-specific code in\n    -    start_command() as I don't have a Windows system, but it seems to pass\n    -    CI + runs the tests there.\n    +    The resulting Windows-specific code in start_command() is a bit nasty,\n    +    as we're now assigning to a strvec's \"v\" member, instead of to our own\n    +    \"argv\". There was a suggestion of some alternate approaches in reply\n    +    to an earlier version of this commit[3], but let's leave larger a\n    +    larger and needless refactoring of this code for now.\n     \n         1. http://lore.kernel.org/git/YT6BnnXeAWn8BycF@coredump.intra.peff.net\n         2. https://lore.kernel.org/git/20211120194048.12125-1-ematsumiya@suse.de/\n    +    3. https://lore.kernel.org/git/patch-5.5-ea1011f7473-20211122T153605Z-avarab@gmail.com/\n     \n    -    Reported-by: Enzo Matsumiya <ematsumiya@suse.de>\n         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n     \n    - ## builtin/worktree.c ##\n    -@@ builtin/worktree.c: static int add_worktree(const char *path, const char *refname,\n    - \t\tgoto done;\n    - \n    - \tif (opts->checkout) {\n    --\t\tcp.argv = NULL;\n    - \t\tstrvec_clear(&cp.args);\n    - \t\tstrvec_pushl(&cp.args, \"reset\", \"--hard\", \"--no-recurse-submodules\", NULL);\n    - \t\tif (opts->quiet)\n    -@@ builtin/worktree.c: static int add_worktree(const char *path, const char *refname,\n    - \t\t\tcp.stdout_to_stderr = 1;\n    - \t\t\tcp.dir = path;\n    - \t\t\tcp.env = env;\n    --\t\t\tcp.argv = NULL;\n    - \t\t\tcp.trace2_hook_name = \"post-checkout\";\n    - \t\t\tstrvec_pushl(&cp.args, absolute_path(hook),\n    - \t\t\t\t     oid_to_hex(null_oid()),\n    -\n      ## run-command.c ##\n     @@ run-command.c: static void child_err_spew(struct child_process *cmd, struct child_err *cerr)\n      \tswitch (cerr->err) {\n    @@ run-command.c: int start_command(struct child_process *cmd)\n      {\n      \tint fhin = 0, fhout = 1, fherr = 2;\n     -\tconst char **sargv = cmd->argv;\n    -+\tconst char **sargv = strvec_detach(&cmd->args);\n    ++\tconst char **sargv = cmd->args.v;\n      \tstruct strvec nargv = STRVEC_INIT;\n    -+\tconst char **temp_argv = NULL;\n      \n      \tif (cmd->no_stdin)\n    - \t\tfhin = open(\"/dev/null\", O_RDWR);\n     @@ run-command.c: int start_command(struct child_process *cmd)\n      \t\tfhout = dup(cmd->out);\n      \n      \tif (cmd->git_cmd)\n     -\t\tcmd->argv = prepare_git_cmd(&nargv, cmd->argv);\n    -+\t\ttemp_argv = prepare_git_cmd(&nargv, sargv);\n    ++\t\tcmd->args.v = prepare_git_cmd(&nargv, sargv);\n      \telse if (cmd->use_shell)\n     -\t\tcmd->argv = prepare_shell_cmd(&nargv, cmd->argv);\n    -+\t\ttemp_argv = prepare_shell_cmd(&nargv, sargv);\n    -+\telse\n    -+\t\ttemp_argv = sargv;\n    -+\tif (!temp_argv)\n    -+\t\tBUG(\"should have some cmd->args to set\");\n    -+\tstrvec_pushv(&cmd->args, temp_argv);\n    -+\tstrvec_clear(&nargv);\n    ++\t\tcmd->args.v = prepare_shell_cmd(&nargv, sargv);\n      \n     -\tcmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, (char**) cmd->env,\n     +\tcmd->pid = mingw_spawnvpe(cmd->args.v[0], cmd->args.v, (char**) cmd->env,\n    @@ run-command.c: int start_command(struct child_process *cmd)\n      \tif (cmd->clean_on_exit && cmd->pid >= 0)\n      \t\tmark_child_for_cleanup(cmd->pid, cmd);\n      \n    --\tstrvec_clear(&nargv);\n    + \tstrvec_clear(&nargv);\n     -\tcmd->argv = sargv;\n    -+\tstrvec_pushv(&cmd->args, sargv);;\n    -+\tfree(sargv);\n    ++\tcmd->args.v = sargv;\n      \tif (fhin != 0)\n      \t\tclose(fhin);\n      \tif (fhout != 1)\n    @@ sub-process.c: static int handshake_capabilities(struct child_process *process,\n      \t}\n      \n     \n    - ## t/helper/test-run-command.c ##\n    -@@ t/helper/test-run-command.c: static int parallel_next(struct child_process *cp,\n    - \tif (number_callbacks >= 4)\n    - \t\treturn 0;\n    - \n    --\tstrvec_pushv(&cp->args, d->argv);\n    -+\tstrvec_pushv(&cp->args, d->args.v);\n    - \tstrbuf_addstr(err, \"preloaded output of a child\\n\");\n    - \tnumber_callbacks++;\n    - \treturn 1;\n    -@@ t/helper/test-run-command.c: static int quote_stress_test(int argc, const char **argv)\n    - \t\tif (i < skip)\n    - \t\t\tcontinue;\n    - \n    --\t\tcp.argv = args.v;\n    -+\t\tstrvec_pushv(&cp.args, args.v);\n    - \t\tstrbuf_reset(&out);\n    - \t\tif (pipe_command(&cp, NULL, 0, &out, 0, NULL, 0) < 0)\n    - \t\t\treturn error(\"Failed to spawn child process\");\n    -@@ t/helper/test-run-command.c: int cmd__run_command(int argc, const char **argv)\n    - \t}\n    - \tif (argc < 3)\n    - \t\treturn 1;\n    --\tproc.argv = (const char **)argv + 2;\n    -+\tstrvec_clear(&proc.args);\n    -+\tstrvec_pushv(&proc.args, (const char **)argv + 2);\n    - \n    - \tif (!strcmp(argv[1], \"start-command-ENOENT\")) {\n    - \t\tif (start_command(&proc) < 0 && errno == ENOENT)\n    -@@ t/helper/test-run-command.c: int cmd__run_command(int argc, const char **argv)\n    - \t\texit(run_command(&proc));\n    - \n    - \tjobs = atoi(argv[2]);\n    --\tproc.argv = (const char **)argv + 3;\n    -+\tstrvec_clear(&proc.args);\n    -+\tstrvec_pushv(&proc.args, (const char **)argv + 3);\n    - \n    - \tif (!strcmp(argv[1], \"run-command-parallel\"))\n    - \t\texit(run_processes_parallel(jobs, parallel_next,\n    -\n    - ## t/t7006-pager.sh ##\n    -@@ t/t7006-pager.sh: test_expect_success 'setup' '\n    - \ttest_commit initial\n    - '\n    - \n    -+test_expect_success 'non-existent pager does not segfault' '\n    -+\tgit -c pager.show=INVALID show\n    -+'\n    -+\n    - test_expect_success TTY 'some commands use a pager' '\n    - \trm -f paginated.out &&\n    - \ttest_terminal git log &&\n    -\n      ## trace2/tr2_tgt_event.c ##\n     @@ trace2/tr2_tgt_event.c: static void fn_child_start_fl(const char *file, int line,\n      \tjw_object_inline_begin_array(&jw, \"argv\");\n -:  ----------- >  8:  b8387a4a76d difftool: use \"env_array\" to simplify memory management\n -:  ----------- >  9:  6bd9f508a3d run-command API: remove \"env\" member, always use \"env_array\"\n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442132","messageId":"patch-v2-2.9-bfa65e5afd7-20211123T115551Z-avarab@gmail.com","threadId":"56951","inReplyTo":"cover-v2-0.9-00000000000-20211123T115551Z-avarab@gmail.com","subject":"[PATCH v2 2/9] upload-archive: use regular \"struct child_process\" pattern","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T12:06:29Z","receivedAt":"2021-11-23T12:08:04Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"This pattern added [1] in seems to have been intentional, but since\n[2] and [3] we've wanted do initialization of what's now the \"struct\nstrvec\" \"args\" and \"env_array\" members. Let's not trample on that\ninitialization here.\n\n1. 1bc01efed17 (upload-archive: use start_command instead of fork,\n   2011-11-19)\n2. c460c0ecdca (run-command: store an optional argv_array, 2014-05-15)\n3. 9a583dc39e (run-command: add env_array, an optional argv_array for\n   env, 2014-10-19)\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/upload-archive.c | 5 +++--\n 1 file changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/upload-archive.c b/builtin/upload-archive.c\nindex 24654b4c9bf..98d028dae67 100644\n--- a/builtin/upload-archive.c\n+++ b/builtin/upload-archive.c\n@@ -77,7 +77,7 @@ static ssize_t process_input(int child_fd, int band)\n \n int cmd_upload_archive(int argc, const char **argv, const char *prefix)\n {\n-\tstruct child_process writer = { argv };\n+\tstruct child_process writer = CHILD_PROCESS_INIT;\n \n \tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n \t\tusage(upload_archive_usage);\n@@ -89,9 +89,10 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix)\n \t * multiplexed out to our fd#1.  If the child dies, we tell the other\n \t * end over channel #3.\n \t */\n-\targv[0] = \"upload-archive--writer\";\n \twriter.out = writer.err = -1;\n \twriter.git_cmd = 1;\n+\tstrvec_push(&writer.args, \"upload-archive--writer\");\n+\tstrvec_pushv(&writer.args, argv + 1);\n \tif (start_command(&writer)) {\n \t\tint err = errno;\n \t\tpacket_write_fmt(1, \"NACK unable to spawn subprocess\\n\");\n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442133","messageId":"patch-v2-3.9-61e4eb8e173-20211123T115551Z-avarab@gmail.com","threadId":"56951","inReplyTo":"cover-v2-0.9-00000000000-20211123T115551Z-avarab@gmail.com","subject":"[PATCH v2 3/9] run-command API users: use strvec_pushv(), not argv assignment","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T12:06:30Z","receivedAt":"2021-11-23T12:08:06Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Migrate those run-command API users that assign directly to the \"argv\"\nmember to use a strvec_pushv() of \"args\" instead.\n\nIn these cases it did not make sense to further refactor these\ncallers, e.g. daemon.c could be made to construct the arguments closer\nto handle(), but that would require moving the construction from its\ncmd_main() and pass \"argv\" through two intermediate functions.\n\nIt would be possible for a change like this to introduce a regression\nif we were doing:\n\n      cp.argv = argv;\n      argv[1] = \"foo\";\n\nAnd changed the code, as is being done here, to:\n\n      strvec_pushv(&cp.args, argv);\n      argv[1] = \"foo\";\n\nBut as viewing this change with the \"-W\" flag reveals none of these\nfunctions modify variable that's being pushed afterwards in a way that\nwould introduce such a logic error.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n add-patch.c                | 4 ++--\n daemon.c                   | 2 +-\n http-backend.c             | 2 +-\n http.c                     | 5 +++--\n remote-curl.c              | 2 +-\n run-command.c              | 2 +-\n t/helper/test-subprocess.c | 2 +-\n 7 files changed, 10 insertions(+), 9 deletions(-)\n\ndiff --git a/add-patch.c b/add-patch.c\nindex 8c41cdfe39b..573eef0cc4a 100644\n--- a/add-patch.c\n+++ b/add-patch.c\n@@ -413,7 +413,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n \t\tstrvec_push(&args, ps->items[i].original);\n \n \tsetup_child_process(s, &cp, NULL);\n-\tcp.argv = args.v;\n+\tstrvec_pushv(&cp.args, args.v);\n \tres = capture_command(&cp, plain, 0);\n \tif (res) {\n \t\tstrvec_clear(&args);\n@@ -431,7 +431,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n \n \t\tsetup_child_process(s, &colored_cp, NULL);\n \t\txsnprintf((char *)args.v[color_arg_index], 8, \"--color\");\n-\t\tcolored_cp.argv = args.v;\n+\t\tstrvec_pushv(&colored_cp.args, args.v);\n \t\tcolored = &s->colored;\n \t\tres = capture_command(&colored_cp, colored, 0);\n \t\tstrvec_clear(&args);\ndiff --git a/daemon.c b/daemon.c\nindex b1fcbe0d6fa..8df21f2130c 100644\n--- a/daemon.c\n+++ b/daemon.c\n@@ -922,7 +922,7 @@ static void handle(int incoming, struct sockaddr *addr, socklen_t addrlen)\n #endif\n \t}\n \n-\tcld.argv = cld_argv.v;\n+\tstrvec_pushv(&cld.args, cld_argv.v);\n \tcld.in = incoming;\n \tcld.out = dup(incoming);\n \ndiff --git a/http-backend.c b/http-backend.c\nindex 3d6e2ff17f8..4dd4d939f8a 100644\n--- a/http-backend.c\n+++ b/http-backend.c\n@@ -480,7 +480,7 @@ static void run_service(const char **argv, int buffer_input)\n \t\tstrvec_pushf(&cld.env_array,\n \t\t\t     \"GIT_COMMITTER_EMAIL=%s@http.%s\", user, host);\n \n-\tcld.argv = argv;\n+\tstrvec_pushv(&cld.args, argv);\n \tif (buffer_input || gzipped_request || req_len >= 0)\n \t\tcld.in = -1;\n \tcld.git_cmd = 1;\ndiff --git a/http.c b/http.c\nindex f92859f43fa..229da4d1488 100644\n--- a/http.c\n+++ b/http.c\n@@ -2126,8 +2126,9 @@ int finish_http_pack_request(struct http_pack_request *preq)\n \n \tip.git_cmd = 1;\n \tip.in = tmpfile_fd;\n-\tip.argv = preq->index_pack_args ? preq->index_pack_args\n-\t\t\t\t\t: default_index_pack_args;\n+\tstrvec_pushv(&ip.args, preq->index_pack_args ?\n+\t\t     preq->index_pack_args :\n+\t\t     default_index_pack_args);\n \n \tif (preq->preserve_index_pack_stdout)\n \t\tip.out = 0;\ndiff --git a/remote-curl.c b/remote-curl.c\nindex d69156312bd..0dabef2dd7c 100644\n--- a/remote-curl.c\n+++ b/remote-curl.c\n@@ -1061,7 +1061,7 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads,\n \tclient.in = -1;\n \tclient.out = -1;\n \tclient.git_cmd = 1;\n-\tclient.argv = client_argv;\n+\tstrvec_pushv(&client.args, client_argv);\n \tif (start_command(&client))\n \t\texit(1);\n \twrite_or_die(client.in, preamble->buf, preamble->len);\ndiff --git a/run-command.c b/run-command.c\nindex f40df01c772..620a06ca2f5 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -1039,7 +1039,7 @@ int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,\n \t\t\t\t const char *const *env, const char *tr2_class)\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n-\tcmd.argv = argv;\n+\tstrvec_pushv(&cmd.args, argv);\n \tcmd.no_stdin = opt & RUN_COMMAND_NO_STDIN ? 1 : 0;\n \tcmd.git_cmd = opt & RUN_GIT_CMD ? 1 : 0;\n \tcmd.stdout_to_stderr = opt & RUN_COMMAND_STDOUT_TO_STDERR ? 1 : 0;\ndiff --git a/t/helper/test-subprocess.c b/t/helper/test-subprocess.c\nindex 92b69de6352..ff22f2fa2c5 100644\n--- a/t/helper/test-subprocess.c\n+++ b/t/helper/test-subprocess.c\n@@ -15,6 +15,6 @@ int cmd__subprocess(int argc, const char **argv)\n \t\targv++;\n \t}\n \tcp.git_cmd = 1;\n-\tcp.argv = (const char **)argv + 1;\n+\tstrvec_pushv(&cp.args, (const char **)argv + 1);\n \treturn run_command(&cp);\n }\n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442134","messageId":"patch-v2-4.9-a2ee10e214c-20211123T115551Z-avarab@gmail.com","threadId":"56951","inReplyTo":"cover-v2-0.9-00000000000-20211123T115551Z-avarab@gmail.com","subject":"[PATCH v2 4/9] run-command tests: use strvec_pushv(), not argv assignment","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T12:06:31Z","receivedAt":"2021-11-23T12:08:08Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"As in the preceding commit change this API user to use strvec_pushv()\ninstead of assigning to the \"argv\" member directly. This leaves is\nwithout test coverage of how the \"argv\" assignment in this API works,\nbut we'll be removing it in a subsequent commit.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n t/helper/test-run-command.c | 9 +++++----\n 1 file changed, 5 insertions(+), 4 deletions(-)\n\ndiff --git a/t/helper/test-run-command.c b/t/helper/test-run-command.c\nindex 3c4fb862234..913775a14b7 100644\n--- a/t/helper/test-run-command.c\n+++ b/t/helper/test-run-command.c\n@@ -31,7 +31,7 @@ static int parallel_next(struct child_process *cp,\n \tif (number_callbacks >= 4)\n \t\treturn 0;\n \n-\tstrvec_pushv(&cp->args, d->argv);\n+\tstrvec_pushv(&cp->args, d->args.v);\n \tstrbuf_addstr(err, \"preloaded output of a child\\n\");\n \tnumber_callbacks++;\n \treturn 1;\n@@ -274,7 +274,7 @@ static int quote_stress_test(int argc, const char **argv)\n \t\tif (i < skip)\n \t\t\tcontinue;\n \n-\t\tcp.argv = args.v;\n+\t\tstrvec_pushv(&cp.args, args.v);\n \t\tstrbuf_reset(&out);\n \t\tif (pipe_command(&cp, NULL, 0, &out, 0, NULL, 0) < 0)\n \t\t\treturn error(\"Failed to spawn child process\");\n@@ -396,7 +396,7 @@ int cmd__run_command(int argc, const char **argv)\n \t}\n \tif (argc < 3)\n \t\treturn 1;\n-\tproc.argv = (const char **)argv + 2;\n+\tstrvec_pushv(&proc.args, (const char **)argv + 2);\n \n \tif (!strcmp(argv[1], \"start-command-ENOENT\")) {\n \t\tif (start_command(&proc) < 0 && errno == ENOENT)\n@@ -408,7 +408,8 @@ int cmd__run_command(int argc, const char **argv)\n \t\texit(run_command(&proc));\n \n \tjobs = atoi(argv[2]);\n-\tproc.argv = (const char **)argv + 3;\n+\tstrvec_clear(&proc.args);\n+\tstrvec_pushv(&proc.args, (const char **)argv + 3);\n \n \tif (!strcmp(argv[1], \"run-command-parallel\"))\n \t\texit(run_processes_parallel(jobs, parallel_next,\n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442135","messageId":"patch-v2-5.9-2b446606eb9-20211123T115551Z-avarab@gmail.com","threadId":"56951","inReplyTo":"cover-v2-0.9-00000000000-20211123T115551Z-avarab@gmail.com","subject":"[PATCH v2 5/9] run-command API users: use strvec_pushl(), not argv construction","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T12:06:32Z","receivedAt":"2021-11-23T12:08:12Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Change a pattern of hardcoding an \"argv\" array size, populating it and\nassigning to the \"argv\" member of \"struct child_process\" to instead\nuse \"strvec_pushl()\" to add data to the \"args\" member.\n\nThis implements the same behavior as before in fewer lines of code,\nand moves us further towards being able to remove the \"argv\" member in\na subsequent commit.\n\nSince we've entirely removed the \"argv\" variable(s) we can be sure\nthat no potential logic errors of the type discussed in a preceding\ncommit are being introduced here, i.e. ones where the local \"argv\" was\nbeing modified after the assignment to \"struct child_process\"'s\n\"argv\".\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/add.c          |  7 ++-----\n builtin/fsck.c         | 12 ++++--------\n builtin/help.c         |  3 +--\n builtin/merge.c        |  3 +--\n builtin/notes.c        |  5 ++---\n builtin/receive-pack.c | 38 +++++++++++++-------------------------\n builtin/replace.c      |  3 +--\n editor.c               |  4 +---\n sequencer.c            | 10 +++-------\n upload-pack.c          |  5 +----\n 10 files changed, 29 insertions(+), 61 deletions(-)\n\ndiff --git a/builtin/add.c b/builtin/add.c\nindex ef6b619c45e..a010b2c325f 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -302,15 +302,11 @@ int interactive_add(const char **argv, const char *prefix, int patch)\n static int edit_patch(int argc, const char **argv, const char *prefix)\n {\n \tchar *file = git_pathdup(\"ADD_EDIT.patch\");\n-\tconst char *apply_argv[] = { \"apply\", \"--recount\", \"--cached\",\n-\t\tNULL, NULL };\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \tstruct rev_info rev;\n \tint out;\n \tstruct stat st;\n \n-\tapply_argv[3] = file;\n-\n \tgit_config(git_diff_basic_config, NULL); /* no \"diff\" UI options */\n \n \tif (read_cache() < 0)\n@@ -338,7 +334,8 @@ static int edit_patch(int argc, const char **argv, const char *prefix)\n \t\tdie(_(\"Empty patch. Aborted.\"));\n \n \tchild.git_cmd = 1;\n-\tchild.argv = apply_argv;\n+\tstrvec_pushl(&child.args, \"apply\", \"--recount\", \"--cached\", file,\n+\t\t     NULL);\n \tif (run_command(&child))\n \t\tdie(_(\"Could not apply '%s'\"), file);\n \ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 27b9e78094d..9e54892311d 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -944,15 +944,13 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)\n \n \tif (the_repository->settings.core_commit_graph) {\n \t\tstruct child_process commit_graph_verify = CHILD_PROCESS_INIT;\n-\t\tconst char *verify_argv[] = { \"commit-graph\", \"verify\", NULL, NULL, NULL };\n \n \t\tprepare_alt_odb(the_repository);\n \t\tfor (odb = the_repository->objects->odb; odb; odb = odb->next) {\n \t\t\tchild_process_init(&commit_graph_verify);\n-\t\t\tcommit_graph_verify.argv = verify_argv;\n \t\t\tcommit_graph_verify.git_cmd = 1;\n-\t\t\tverify_argv[2] = \"--object-dir\";\n-\t\t\tverify_argv[3] = odb->path;\n+\t\t\tstrvec_pushl(&commit_graph_verify.args, \"commit-graph\",\n+\t\t\t\t     \"verify\", \"--object-dir\", odb->path, NULL);\n \t\t\tif (run_command(&commit_graph_verify))\n \t\t\t\terrors_found |= ERROR_COMMIT_GRAPH;\n \t\t}\n@@ -960,15 +958,13 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)\n \n \tif (the_repository->settings.core_multi_pack_index) {\n \t\tstruct child_process midx_verify = CHILD_PROCESS_INIT;\n-\t\tconst char *midx_argv[] = { \"multi-pack-index\", \"verify\", NULL, NULL, NULL };\n \n \t\tprepare_alt_odb(the_repository);\n \t\tfor (odb = the_repository->objects->odb; odb; odb = odb->next) {\n \t\t\tchild_process_init(&midx_verify);\n-\t\t\tmidx_verify.argv = midx_argv;\n \t\t\tmidx_verify.git_cmd = 1;\n-\t\t\tmidx_argv[2] = \"--object-dir\";\n-\t\t\tmidx_argv[3] = odb->path;\n+\t\t\tstrvec_pushl(&midx_verify.args, \"multi-pack-index\",\n+\t\t\t\t     \"verify\", \"--object-dir\", odb->path, NULL);\n \t\t\tif (run_command(&midx_verify))\n \t\t\t\terrors_found |= ERROR_MULTI_PACK_INDEX;\n \t\t}\ndiff --git a/builtin/help.c b/builtin/help.c\nindex 75cd2fb407f..d387131dd83 100644\n--- a/builtin/help.c\n+++ b/builtin/help.c\n@@ -212,11 +212,10 @@ static int check_emacsclient_version(void)\n {\n \tstruct strbuf buffer = STRBUF_INIT;\n \tstruct child_process ec_process = CHILD_PROCESS_INIT;\n-\tconst char *argv_ec[] = { \"emacsclient\", \"--version\", NULL };\n \tint version;\n \n \t/* emacsclient prints its version number on stderr */\n-\tec_process.argv = argv_ec;\n+\tstrvec_pushl(&ec_process.args, \"emacsclient\", \"--version\", NULL);\n \tec_process.err = -1;\n \tec_process.stdout_to_stderr = 1;\n \tif (start_command(&ec_process))\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex ea3112e0c0b..5f0476b0b76 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -310,10 +310,9 @@ static int save_state(struct object_id *stash)\n \tint len;\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n \tstruct strbuf buffer = STRBUF_INIT;\n-\tconst char *argv[] = {\"stash\", \"create\", NULL};\n \tint rc = -1;\n \n-\tcp.argv = argv;\n+\tstrvec_pushl(&cp.args, \"stash\", \"create\", NULL);\n \tcp.out = -1;\n \tcp.git_cmd = 1;\n \ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 71c59583a17..85d1abad884 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -134,14 +134,13 @@ static void copy_obj_to_fd(int fd, const struct object_id *oid)\n \n static void write_commented_object(int fd, const struct object_id *object)\n {\n-\tconst char *show_args[5] =\n-\t\t{\"show\", \"--stat\", \"--no-notes\", oid_to_hex(object), NULL};\n \tstruct child_process show = CHILD_PROCESS_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct strbuf cbuf = STRBUF_INIT;\n \n \t/* Invoke \"git show --stat --no-notes $object\" */\n-\tshow.argv = show_args;\n+\tstrvec_pushl(&show.args, \"show\", \"--stat\", \"--no-notes\",\n+\t\t     oid_to_hex(object), NULL);\n \tshow.no_stdin = 1;\n \tshow.out = -1;\n \tshow.err = 0;\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 49b846d9605..6149d507965 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1370,22 +1370,10 @@ static const char *push_to_deploy(unsigned char *sha1,\n \t\t\t\t  struct strvec *env,\n \t\t\t\t  const char *work_tree)\n {\n-\tconst char *update_refresh[] = {\n-\t\t\"update-index\", \"-q\", \"--ignore-submodules\", \"--refresh\", NULL\n-\t};\n-\tconst char *diff_files[] = {\n-\t\t\"diff-files\", \"--quiet\", \"--ignore-submodules\", \"--\", NULL\n-\t};\n-\tconst char *diff_index[] = {\n-\t\t\"diff-index\", \"--quiet\", \"--cached\", \"--ignore-submodules\",\n-\t\tNULL, \"--\", NULL\n-\t};\n-\tconst char *read_tree[] = {\n-\t\t\"read-tree\", \"-u\", \"-m\", NULL, NULL\n-\t};\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \n-\tchild.argv = update_refresh;\n+\tstrvec_pushl(&child.args, \"update-index\", \"-q\", \"--ignore-submodules\",\n+\t\t     \"--refresh\", NULL);\n \tchild.env = env->v;\n \tchild.dir = work_tree;\n \tchild.no_stdin = 1;\n@@ -1396,7 +1384,8 @@ static const char *push_to_deploy(unsigned char *sha1,\n \n \t/* run_command() does not clean up completely; reinitialize */\n \tchild_process_init(&child);\n-\tchild.argv = diff_files;\n+\tstrvec_pushl(&child.args, \"diff-files\", \"--quiet\",\n+\t\t     \"--ignore-submodules\", \"--\", NULL);\n \tchild.env = env->v;\n \tchild.dir = work_tree;\n \tchild.no_stdin = 1;\n@@ -1405,11 +1394,12 @@ static const char *push_to_deploy(unsigned char *sha1,\n \tif (run_command(&child))\n \t\treturn \"Working directory has unstaged changes\";\n \n-\t/* diff-index with either HEAD or an empty tree */\n-\tdiff_index[4] = head_has_history() ? \"HEAD\" : empty_tree_oid_hex();\n-\n \tchild_process_init(&child);\n-\tchild.argv = diff_index;\n+\tstrvec_pushl(&child.args, \"diff-index\", \"--quiet\", \"--cached\",\n+\t\t     \"--ignore-submodules\",\n+\t\t     /* diff-index with either HEAD or an empty tree */\n+\t\t     head_has_history() ? \"HEAD\" : empty_tree_oid_hex(),\n+\t\t     \"--\", NULL);\n \tchild.env = env->v;\n \tchild.no_stdin = 1;\n \tchild.no_stdout = 1;\n@@ -1418,9 +1408,9 @@ static const char *push_to_deploy(unsigned char *sha1,\n \tif (run_command(&child))\n \t\treturn \"Working directory has staged changes\";\n \n-\tread_tree[3] = hash_to_hex(sha1);\n \tchild_process_init(&child);\n-\tchild.argv = read_tree;\n+\tstrvec_pushl(&child.args, \"read-tree\", \"-u\", \"-m\", hash_to_hex(sha1),\n+\t\t     NULL);\n \tchild.env = env->v;\n \tchild.dir = work_tree;\n \tchild.no_stdin = 1;\n@@ -2575,16 +2565,14 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\trun_update_post_hook(commands);\n \t\tstring_list_clear(&push_options, 0);\n \t\tif (auto_gc) {\n-\t\t\tconst char *argv_gc_auto[] = {\n-\t\t\t\t\"gc\", \"--auto\", \"--quiet\", NULL,\n-\t\t\t};\n \t\t\tstruct child_process proc = CHILD_PROCESS_INIT;\n \n \t\t\tproc.no_stdin = 1;\n \t\t\tproc.stdout_to_stderr = 1;\n \t\t\tproc.err = use_sideband ? -1 : 0;\n \t\t\tproc.git_cmd = proc.close_object_store = 1;\n-\t\t\tproc.argv = argv_gc_auto;\n+\t\t\tstrvec_pushl(&proc.args, \"gc\", \"--auto\", \"--quiet\",\n+\t\t\t\t     NULL);\n \n \t\t\tif (!start_command(&proc)) {\n \t\t\t\tif (use_sideband)\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 946938d011e..6ff1734d587 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -258,11 +258,10 @@ static int import_object(struct object_id *oid, enum object_type type,\n \t\treturn error_errno(_(\"unable to open %s for reading\"), filename);\n \n \tif (!raw && type == OBJ_TREE) {\n-\t\tconst char *argv[] = { \"mktree\", NULL };\n \t\tstruct child_process cmd = CHILD_PROCESS_INIT;\n \t\tstruct strbuf result = STRBUF_INIT;\n \n-\t\tcmd.argv = argv;\n+\t\tstrvec_push(&cmd.args, \"mktree\");\n \t\tcmd.git_cmd = 1;\n \t\tcmd.in = fd;\n \t\tcmd.out = -1;\ndiff --git a/editor.c b/editor.c\nindex 674309eed8b..b7d2a9b73c3 100644\n--- a/editor.c\n+++ b/editor.c\n@@ -58,7 +58,6 @@ static int launch_specified_editor(const char *editor, const char *path,\n \n \tif (strcmp(editor, \":\")) {\n \t\tstruct strbuf realpath = STRBUF_INIT;\n-\t\tconst char *args[] = { editor, NULL, NULL };\n \t\tstruct child_process p = CHILD_PROCESS_INIT;\n \t\tint ret, sig;\n \t\tint print_waiting_for_editor = advice_enabled(ADVICE_WAITING_FOR_EDITOR) && isatty(2);\n@@ -80,9 +79,8 @@ static int launch_specified_editor(const char *editor, const char *path,\n \t\t}\n \n \t\tstrbuf_realpath(&realpath, path, 1);\n-\t\targs[1] = realpath.buf;\n \n-\t\tp.argv = args;\n+\t\tstrvec_pushl(&p.args, editor, realpath.buf, NULL);\n \t\tp.env = env;\n \t\tp.use_shell = 1;\n \t\tp.trace2_child_class = \"editor\";\ndiff --git a/sequencer.c b/sequencer.c\nindex ea96837cde3..6e02210db7a 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1164,18 +1164,14 @@ static int run_rewrite_hook(const struct object_id *oldoid,\n \t\t\t    const struct object_id *newoid)\n {\n \tstruct child_process proc = CHILD_PROCESS_INIT;\n-\tconst char *argv[3];\n \tint code;\n \tstruct strbuf sb = STRBUF_INIT;\n+\tconst char *hook_path = find_hook(\"post-rewrite\");\n \n-\targv[0] = find_hook(\"post-rewrite\");\n-\tif (!argv[0])\n+\tif (!hook_path)\n \t\treturn 0;\n \n-\targv[1] = \"amend\";\n-\targv[2] = NULL;\n-\n-\tproc.argv = argv;\n+\tstrvec_pushl(&proc.args, hook_path, \"amend\", NULL);\n \tproc.in = -1;\n \tproc.stdout_to_stderr = 1;\n \tproc.trace2_hook_name = \"post-rewrite\";\ndiff --git a/upload-pack.c b/upload-pack.c\nindex c78d55bc674..9b5db32623f 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -596,14 +596,11 @@ static int do_reachable_revlist(struct child_process *cmd,\n \t\t\t\tstruct object_array *reachable,\n \t\t\t\tenum allow_uor allow_uor)\n {\n-\tstatic const char *argv[] = {\n-\t\t\"rev-list\", \"--stdin\", NULL,\n-\t};\n \tstruct object *o;\n \tFILE *cmd_in = NULL;\n \tint i;\n \n-\tcmd->argv = argv;\n+\tstrvec_pushl(&cmd->args, \"rev-list\", \"--stdin\", NULL);\n \tcmd->git_cmd = 1;\n \tcmd->no_stderr = 1;\n \tcmd->in = -1;\n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442136","messageId":"patch-v2-6.9-fad420dc563-20211123T115551Z-avarab@gmail.com","threadId":"56951","inReplyTo":"cover-v2-0.9-00000000000-20211123T115551Z-avarab@gmail.com","subject":"[PATCH v2 6/9] run-command API users: use strvec_push(), not argv construction","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T12:06:33Z","receivedAt":"2021-11-23T12:08:13Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Change a pattern of hardcoding an \"argv\" array size, populating it and\nassigning to the \"argv\" member of \"struct child_process\" to instead\nuse \"strvec_push()\" to add data to the \"args\" member.\n\nAs noted in the preceding commit this moves us further towards being\nable to remove the \"argv\" member in a subsequent commit\n\nThese callers could have used strvec_pushl(), but moving to\nstrvec_push() makes the diff easier to read, and keeps the arguments\naligned as before.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n archive-tar.c          |  9 +++------\n builtin/receive-pack.c | 31 ++++++++++++-------------------\n daemon.c               | 18 +++++++-----------\n diff.c                 |  8 ++------\n prompt.c               |  7 ++-----\n transport.c            | 11 +++++------\n 6 files changed, 31 insertions(+), 53 deletions(-)\n\ndiff --git a/archive-tar.c b/archive-tar.c\nindex 05d2455870d..3c74db17468 100644\n--- a/archive-tar.c\n+++ b/archive-tar.c\n@@ -430,7 +430,6 @@ static int write_tar_filter_archive(const struct archiver *ar,\n {\n \tstruct strbuf cmd = STRBUF_INIT;\n \tstruct child_process filter = CHILD_PROCESS_INIT;\n-\tconst char *argv[2];\n \tint r;\n \n \tif (!ar->data)\n@@ -440,14 +439,12 @@ static int write_tar_filter_archive(const struct archiver *ar,\n \tif (args->compression_level >= 0)\n \t\tstrbuf_addf(&cmd, \" -%d\", args->compression_level);\n \n-\targv[0] = cmd.buf;\n-\targv[1] = NULL;\n-\tfilter.argv = argv;\n+\tstrvec_push(&filter.args, cmd.buf);\n \tfilter.use_shell = 1;\n \tfilter.in = -1;\n \n \tif (start_command(&filter) < 0)\n-\t\tdie_errno(_(\"unable to start '%s' filter\"), argv[0]);\n+\t\tdie_errno(_(\"unable to start '%s' filter\"), cmd.buf);\n \tclose(1);\n \tif (dup2(filter.in, 1) < 0)\n \t\tdie_errno(_(\"unable to redirect descriptor\"));\n@@ -457,7 +454,7 @@ static int write_tar_filter_archive(const struct archiver *ar,\n \n \tclose(1);\n \tif (finish_command(&filter) != 0)\n-\t\tdie(_(\"'%s' filter reported error\"), argv[0]);\n+\t\tdie(_(\"'%s' filter reported error\"), cmd.buf);\n \n \tstrbuf_release(&cmd);\n \treturn r;\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 6149d507965..48c99c8ee45 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -812,16 +812,13 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,\n {\n \tstruct child_process proc = CHILD_PROCESS_INIT;\n \tstruct async muxer;\n-\tconst char *argv[2];\n \tint code;\n+\tconst char *hook_path = find_hook(hook_name);\n \n-\targv[0] = find_hook(hook_name);\n-\tif (!argv[0])\n+\tif (!hook_path)\n \t\treturn 0;\n \n-\targv[1] = NULL;\n-\n-\tproc.argv = argv;\n+\tstrvec_push(&proc.args, hook_path);\n \tproc.in = -1;\n \tproc.stdout_to_stderr = 1;\n \tproc.trace2_hook_name = hook_name;\n@@ -943,23 +940,21 @@ static int run_receive_hook(struct command *commands,\n \n static int run_update_hook(struct command *cmd)\n {\n-\tconst char *argv[5];\n \tstruct child_process proc = CHILD_PROCESS_INIT;\n \tint code;\n+\tconst char *hook_path = find_hook(\"update\");\n \n-\targv[0] = find_hook(\"update\");\n-\tif (!argv[0])\n+\tif (!hook_path)\n \t\treturn 0;\n \n-\targv[1] = cmd->ref_name;\n-\targv[2] = oid_to_hex(&cmd->old_oid);\n-\targv[3] = oid_to_hex(&cmd->new_oid);\n-\targv[4] = NULL;\n+\tstrvec_push(&proc.args, hook_path);\n+\tstrvec_push(&proc.args, cmd->ref_name);\n+\tstrvec_push(&proc.args, oid_to_hex(&cmd->old_oid));\n+\tstrvec_push(&proc.args, oid_to_hex(&cmd->new_oid));\n \n \tproc.no_stdin = 1;\n \tproc.stdout_to_stderr = 1;\n \tproc.err = use_sideband ? -1 : 0;\n-\tproc.argv = argv;\n \tproc.trace2_hook_name = \"update\";\n \n \tcode = start_command(&proc);\n@@ -1117,22 +1112,20 @@ static int run_proc_receive_hook(struct command *commands,\n \tstruct child_process proc = CHILD_PROCESS_INIT;\n \tstruct async muxer;\n \tstruct command *cmd;\n-\tconst char *argv[2];\n \tstruct packet_reader reader;\n \tstruct strbuf cap = STRBUF_INIT;\n \tstruct strbuf errmsg = STRBUF_INIT;\n \tint hook_use_push_options = 0;\n \tint version = 0;\n \tint code;\n+\tconst char *hook_path = find_hook(\"proc-receive\");\n \n-\targv[0] = find_hook(\"proc-receive\");\n-\tif (!argv[0]) {\n+\tif (!hook_path) {\n \t\trp_error(\"cannot find hook 'proc-receive'\");\n \t\treturn -1;\n \t}\n-\targv[1] = NULL;\n \n-\tproc.argv = argv;\n+\tstrvec_push(&proc.args, hook_path);\n \tproc.in = -1;\n \tproc.out = -1;\n \tproc.trace2_hook_name = \"proc-receive\";\ndiff --git a/daemon.c b/daemon.c\nindex 8df21f2130c..4a000ee4afa 100644\n--- a/daemon.c\n+++ b/daemon.c\n@@ -326,22 +326,18 @@ static int run_access_hook(struct daemon_service *service, const char *dir,\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n-\tconst char *argv[8];\n-\tconst char **arg = argv;\n \tchar *eol;\n \tint seen_errors = 0;\n \n-\t*arg++ = access_hook;\n-\t*arg++ = service->name;\n-\t*arg++ = path;\n-\t*arg++ = hi->hostname.buf;\n-\t*arg++ = get_canon_hostname(hi);\n-\t*arg++ = get_ip_address(hi);\n-\t*arg++ = hi->tcp_port.buf;\n-\t*arg = NULL;\n+\tstrvec_push(&child.args, access_hook);\n+\tstrvec_push(&child.args, service->name);\n+\tstrvec_push(&child.args, path);\n+\tstrvec_push(&child.args, hi->hostname.buf);\n+\tstrvec_push(&child.args, get_canon_hostname(hi));\n+\tstrvec_push(&child.args, get_ip_address(hi));\n+\tstrvec_push(&child.args, hi->tcp_port.buf);\n \n \tchild.use_shell = 1;\n-\tchild.argv = argv;\n \tchild.no_stdin = 1;\n \tchild.no_stderr = 1;\n \tchild.out = -1;\ndiff --git a/diff.c b/diff.c\nindex 861282db1c3..41076857428 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -6921,19 +6921,15 @@ static char *run_textconv(struct repository *r,\n \t\t\t  size_t *outsize)\n {\n \tstruct diff_tempfile *temp;\n-\tconst char *argv[3];\n-\tconst char **arg = argv;\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n \tint err = 0;\n \n \ttemp = prepare_temp_file(r, spec->path, spec);\n-\t*arg++ = pgm;\n-\t*arg++ = temp->name;\n-\t*arg = NULL;\n+\tstrvec_push(&child.args, pgm);\n+\tstrvec_push(&child.args, temp->name);\n \n \tchild.use_shell = 1;\n-\tchild.argv = argv;\n \tchild.out = -1;\n \tif (start_command(&child)) {\n \t\tremove_tempfile();\ndiff --git a/prompt.c b/prompt.c\nindex 5ded21a017f..50df17279d1 100644\n--- a/prompt.c\n+++ b/prompt.c\n@@ -8,15 +8,12 @@\n static char *do_askpass(const char *cmd, const char *prompt)\n {\n \tstruct child_process pass = CHILD_PROCESS_INIT;\n-\tconst char *args[3];\n \tstatic struct strbuf buffer = STRBUF_INIT;\n \tint err = 0;\n \n-\targs[0] = cmd;\n-\targs[1]\t= prompt;\n-\targs[2] = NULL;\n+\tstrvec_push(&pass.args, cmd);\n+\tstrvec_push(&pass.args, prompt);\n \n-\tpass.argv = args;\n \tpass.out = -1;\n \n \tif (start_command(&pass))\ndiff --git a/transport.c b/transport.c\nindex e4f1decae20..92ab9a3fa6b 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1204,16 +1204,15 @@ static int run_pre_push_hook(struct transport *transport,\n \tstruct ref *r;\n \tstruct child_process proc = CHILD_PROCESS_INIT;\n \tstruct strbuf buf;\n-\tconst char *argv[4];\n+\tconst char *hook_path = find_hook(\"pre-push\");\n \n-\tif (!(argv[0] = find_hook(\"pre-push\")))\n+\tif (!hook_path)\n \t\treturn 0;\n \n-\targv[1] = transport->remote->name;\n-\targv[2] = transport->url;\n-\targv[3] = NULL;\n+\tstrvec_push(&proc.args, hook_path);\n+\tstrvec_push(&proc.args, transport->remote->name);\n+\tstrvec_push(&proc.args, transport->url);\n \n-\tproc.argv = argv;\n \tproc.in = -1;\n \tproc.trace2_hook_name = \"pre-push\";\n \n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442137","messageId":"patch-v2-7.9-67ab5114ed7-20211123T115551Z-avarab@gmail.com","threadId":"56951","inReplyTo":"cover-v2-0.9-00000000000-20211123T115551Z-avarab@gmail.com","subject":"[PATCH v2 7/9] run-command API: remove \"argv\" member, always use \"args\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T12:06:34Z","receivedAt":"2021-11-23T12:08:15Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Remove the \"argv\" member from the run-command API, ever since \"args\"\nwas added in c460c0ecdca (run-command: store an optional argv_array,\n2014-05-15) being able to provide either \"argv\" or \"args\" has led to\nsome confusion and bugs.\n\nIf we hadn't gone in that direction and only had an \"argv\" our\nproblems wouldn't have been solved either, as noted in [1] (and in the\ndocumentation amended here) it comes with inherent memory management\nissues: The caller would have to hang on to the \"argv\" until the\nrun-command API was finished. If the \"argv\" was an argument to main()\nthis wasn't an issue, but if it it was manually constructed using the\nAPI might be painful.\n\nWe also have a recent report[2] of a user of the API segfaulting,\nwhich is a direct result of it being complex to use. This commit\naddresses the root cause of that bug.\n\nThis change is larger than I'd like, but there's no easy way to avoid\nit that wouldn't involve even more verbose intermediate steps. We use\nthe \"argv\" as the source of truth over the \"args\", so we need to\nchange all parts of run-command.[ch] itself, as well as the trace2\nlogging at the same time.\n\nThe resulting Windows-specific code in start_command() is a bit nasty,\nas we're now assigning to a strvec's \"v\" member, instead of to our own\n\"argv\". There was a suggestion of some alternate approaches in reply\nto an earlier version of this commit[3], but let's leave larger a\nlarger and needless refactoring of this code for now.\n\n1. http://lore.kernel.org/git/YT6BnnXeAWn8BycF@coredump.intra.peff.net\n2. https://lore.kernel.org/git/20211120194048.12125-1-ematsumiya@suse.de/\n3. https://lore.kernel.org/git/patch-5.5-ea1011f7473-20211122T153605Z-avarab@gmail.com/\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n run-command.c           | 42 ++++++++++++++++++++---------------------\n run-command.h           | 20 ++++++++------------\n sub-process.c           |  2 +-\n trace2/tr2_tgt_event.c  |  2 +-\n trace2/tr2_tgt_normal.c |  2 +-\n trace2/tr2_tgt_perf.c   |  4 ++--\n 6 files changed, 33 insertions(+), 39 deletions(-)\n\ndiff --git a/run-command.c b/run-command.c\nindex 620a06ca2f5..99dc93e7300 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -380,7 +380,7 @@ static void child_err_spew(struct child_process *cmd, struct child_err *cerr)\n \tswitch (cerr->err) {\n \tcase CHILD_ERR_CHDIR:\n \t\terror_errno(\"exec '%s': cd to '%s' failed\",\n-\t\t\t    cmd->argv[0], cmd->dir);\n+\t\t\t    cmd->args.v[0], cmd->dir);\n \t\tbreak;\n \tcase CHILD_ERR_DUP2:\n \t\terror_errno(\"dup2() in child failed\");\n@@ -392,12 +392,12 @@ static void child_err_spew(struct child_process *cmd, struct child_err *cerr)\n \t\terror_errno(\"sigprocmask failed restoring signals\");\n \t\tbreak;\n \tcase CHILD_ERR_ENOENT:\n-\t\terror_errno(\"cannot run %s\", cmd->argv[0]);\n+\t\terror_errno(\"cannot run %s\", cmd->args.v[0]);\n \t\tbreak;\n \tcase CHILD_ERR_SILENT:\n \t\tbreak;\n \tcase CHILD_ERR_ERRNO:\n-\t\terror_errno(\"cannot exec '%s'\", cmd->argv[0]);\n+\t\terror_errno(\"cannot exec '%s'\", cmd->args.v[0]);\n \t\tbreak;\n \t}\n \tset_error_routine(old_errfn);\n@@ -405,7 +405,7 @@ static void child_err_spew(struct child_process *cmd, struct child_err *cerr)\n \n static int prepare_cmd(struct strvec *out, const struct child_process *cmd)\n {\n-\tif (!cmd->argv[0])\n+\tif (!cmd->args.v[0])\n \t\tBUG(\"command is empty\");\n \n \t/*\n@@ -415,11 +415,11 @@ static int prepare_cmd(struct strvec *out, const struct child_process *cmd)\n \tstrvec_push(out, SHELL_PATH);\n \n \tif (cmd->git_cmd) {\n-\t\tprepare_git_cmd(out, cmd->argv);\n+\t\tprepare_git_cmd(out, cmd->args.v);\n \t} else if (cmd->use_shell) {\n-\t\tprepare_shell_cmd(out, cmd->argv);\n+\t\tprepare_shell_cmd(out, cmd->args.v);\n \t} else {\n-\t\tstrvec_pushv(out, cmd->argv);\n+\t\tstrvec_pushv(out, cmd->args.v);\n \t}\n \n \t/*\n@@ -663,7 +663,7 @@ static void trace_run_command(const struct child_process *cp)\n \t\ttrace_add_env(&buf, cp->env);\n \tif (cp->git_cmd)\n \t\tstrbuf_addstr(&buf, \" git\");\n-\tsq_quote_argv_pretty(&buf, cp->argv);\n+\tsq_quote_argv_pretty(&buf, cp->args.v);\n \n \ttrace_printf(\"%s\", buf.buf);\n \tstrbuf_release(&buf);\n@@ -676,8 +676,6 @@ int start_command(struct child_process *cmd)\n \tint failed_errno;\n \tchar *str;\n \n-\tif (!cmd->argv)\n-\t\tcmd->argv = cmd->args.v;\n \tif (!cmd->env)\n \t\tcmd->env = cmd->env_array.v;\n \n@@ -729,7 +727,7 @@ int start_command(struct child_process *cmd)\n \t\t\tstr = \"standard error\";\n fail_pipe:\n \t\t\terror(\"cannot create %s pipe for %s: %s\",\n-\t\t\t\tstr, cmd->argv[0], strerror(failed_errno));\n+\t\t\t\tstr, cmd->args.v[0], strerror(failed_errno));\n \t\t\tchild_process_clear(cmd);\n \t\t\terrno = failed_errno;\n \t\t\treturn -1;\n@@ -758,7 +756,7 @@ int start_command(struct child_process *cmd)\n \t\tfailed_errno = errno;\n \t\tcmd->pid = -1;\n \t\tif (!cmd->silent_exec_failure)\n-\t\t\terror_errno(\"cannot run %s\", cmd->argv[0]);\n+\t\t\terror_errno(\"cannot run %s\", cmd->args.v[0]);\n \t\tgoto end_of_spawn;\n \t}\n \n@@ -868,7 +866,7 @@ int start_command(struct child_process *cmd)\n \t}\n \tatfork_parent(&as);\n \tif (cmd->pid < 0)\n-\t\terror_errno(\"cannot fork() for %s\", cmd->argv[0]);\n+\t\terror_errno(\"cannot fork() for %s\", cmd->args.v[0]);\n \telse if (cmd->clean_on_exit)\n \t\tmark_child_for_cleanup(cmd->pid, cmd);\n \n@@ -885,7 +883,7 @@ int start_command(struct child_process *cmd)\n \t\t * At this point we know that fork() succeeded, but exec()\n \t\t * failed. Errors have been reported to our stderr.\n \t\t */\n-\t\twait_or_whine(cmd->pid, cmd->argv[0], 0);\n+\t\twait_or_whine(cmd->pid, cmd->args.v[0], 0);\n \t\tchild_err_spew(cmd, &cerr);\n \t\tfailed_errno = errno;\n \t\tcmd->pid = -1;\n@@ -902,7 +900,7 @@ int start_command(struct child_process *cmd)\n #else\n {\n \tint fhin = 0, fhout = 1, fherr = 2;\n-\tconst char **sargv = cmd->argv;\n+\tconst char **sargv = cmd->args.v;\n \tstruct strvec nargv = STRVEC_INIT;\n \n \tif (cmd->no_stdin)\n@@ -929,20 +927,20 @@ int start_command(struct child_process *cmd)\n \t\tfhout = dup(cmd->out);\n \n \tif (cmd->git_cmd)\n-\t\tcmd->argv = prepare_git_cmd(&nargv, cmd->argv);\n+\t\tcmd->args.v = prepare_git_cmd(&nargv, sargv);\n \telse if (cmd->use_shell)\n-\t\tcmd->argv = prepare_shell_cmd(&nargv, cmd->argv);\n+\t\tcmd->args.v = prepare_shell_cmd(&nargv, sargv);\n \n-\tcmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, (char**) cmd->env,\n+\tcmd->pid = mingw_spawnvpe(cmd->args.v[0], cmd->args.v, (char**) cmd->env,\n \t\t\tcmd->dir, fhin, fhout, fherr);\n \tfailed_errno = errno;\n \tif (cmd->pid < 0 && (!cmd->silent_exec_failure || errno != ENOENT))\n-\t\terror_errno(\"cannot spawn %s\", cmd->argv[0]);\n+\t\terror_errno(\"cannot spawn %s\", cmd->args.v[0]);\n \tif (cmd->clean_on_exit && cmd->pid >= 0)\n \t\tmark_child_for_cleanup(cmd->pid, cmd);\n \n \tstrvec_clear(&nargv);\n-\tcmd->argv = sargv;\n+\tcmd->args.v = sargv;\n \tif (fhin != 0)\n \t\tclose(fhin);\n \tif (fhout != 1)\n@@ -992,7 +990,7 @@ int start_command(struct child_process *cmd)\n \n int finish_command(struct child_process *cmd)\n {\n-\tint ret = wait_or_whine(cmd->pid, cmd->argv[0], 0);\n+\tint ret = wait_or_whine(cmd->pid, cmd->args.v[0], 0);\n \ttrace2_child_exit(cmd, ret);\n \tchild_process_clear(cmd);\n \tinvalidate_lstat_cache();\n@@ -1001,7 +999,7 @@ int finish_command(struct child_process *cmd)\n \n int finish_command_in_signal(struct child_process *cmd)\n {\n-\tint ret = wait_or_whine(cmd->pid, cmd->argv[0], 1);\n+\tint ret = wait_or_whine(cmd->pid, cmd->args.v[0], 1);\n \ttrace2_child_exit(cmd, ret);\n \treturn ret;\n }\ndiff --git a/run-command.h b/run-command.h\nindex 49878262584..c0d1210cc63 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -44,21 +44,17 @@\n struct child_process {\n \n \t/**\n-\t * The .argv member is set up as an array of string pointers (NULL\n-\t * terminated), of which .argv[0] is the program name to run (usually\n-\t * without a path). If the command to run is a git command, set argv[0] to\n-\t * the command name without the 'git-' prefix and set .git_cmd = 1.\n+\t * The .args is a `struct strvec', use that API to manipulate\n+\t * it, e.g. strvec_pushv() to add an existing \"const char **\"\n+\t * vector.\n \t *\n-\t * Note that the ownership of the memory pointed to by .argv stays with the\n-\t * caller, but it should survive until `finish_command` completes. If the\n-\t * .argv member is NULL, `start_command` will point it at the .args\n-\t * `strvec` (so you may use one or the other, but you must use exactly\n-\t * one). The memory in .args will be cleaned up automatically during\n-\t * `finish_command` (or during `start_command` when it is unsuccessful).\n+\t * If the command to run is a git command, set the first\n+\t * element in the strvec to the command name without the\n+\t * 'git-' prefix and set .git_cmd = 1.\n \t *\n+\t * The memory in .args will be cleaned up automatically during\n+\t * `finish_command` (or during `start_command` when it is unsuccessful).\n \t */\n-\tconst char **argv;\n-\n \tstruct strvec args;\n \tstruct strvec env_array;\n \tpid_t pid;\ndiff --git a/sub-process.c b/sub-process.c\nindex dfa790d3ff9..cae56ae6b80 100644\n--- a/sub-process.c\n+++ b/sub-process.c\n@@ -187,7 +187,7 @@ static int handshake_capabilities(struct child_process *process,\n \t\t\t\t*supported_capabilities |= capabilities[i].flag;\n \t\t} else {\n \t\t\tdie(\"subprocess '%s' requested unsupported capability '%s'\",\n-\t\t\t    process->argv[0], p);\n+\t\t\t    process->args.v[0], p);\n \t\t}\n \t}\n \ndiff --git a/trace2/tr2_tgt_event.c b/trace2/tr2_tgt_event.c\nindex 3a0014417cc..bd17ecdc321 100644\n--- a/trace2/tr2_tgt_event.c\n+++ b/trace2/tr2_tgt_event.c\n@@ -354,7 +354,7 @@ static void fn_child_start_fl(const char *file, int line,\n \tjw_object_inline_begin_array(&jw, \"argv\");\n \tif (cmd->git_cmd)\n \t\tjw_array_string(&jw, \"git\");\n-\tjw_array_argv(&jw, cmd->argv);\n+\tjw_array_argv(&jw, cmd->args.v);\n \tjw_end(&jw);\n \tjw_end(&jw);\n \ndiff --git a/trace2/tr2_tgt_normal.c b/trace2/tr2_tgt_normal.c\nindex 58d9e430f05..6e429a3fb9e 100644\n--- a/trace2/tr2_tgt_normal.c\n+++ b/trace2/tr2_tgt_normal.c\n@@ -232,7 +232,7 @@ static void fn_child_start_fl(const char *file, int line,\n \tstrbuf_addch(&buf_payload, ' ');\n \tif (cmd->git_cmd)\n \t\tstrbuf_addstr(&buf_payload, \"git \");\n-\tsq_append_quote_argv_pretty(&buf_payload, cmd->argv);\n+\tsq_append_quote_argv_pretty(&buf_payload, cmd->args.v);\n \n \tnormal_io_write_fl(file, line, &buf_payload);\n \tstrbuf_release(&buf_payload);\ndiff --git a/trace2/tr2_tgt_perf.c b/trace2/tr2_tgt_perf.c\nindex e4acca13d64..2ff9cf70835 100644\n--- a/trace2/tr2_tgt_perf.c\n+++ b/trace2/tr2_tgt_perf.c\n@@ -335,10 +335,10 @@ static void fn_child_start_fl(const char *file, int line,\n \tstrbuf_addstr(&buf_payload, \" argv:[\");\n \tif (cmd->git_cmd) {\n \t\tstrbuf_addstr(&buf_payload, \"git\");\n-\t\tif (cmd->argv[0])\n+\t\tif (cmd->args.nr)\n \t\t\tstrbuf_addch(&buf_payload, ' ');\n \t}\n-\tsq_append_quote_argv_pretty(&buf_payload, cmd->argv);\n+\tsq_append_quote_argv_pretty(&buf_payload, cmd->args.v);\n \tstrbuf_addch(&buf_payload, ']');\n \n \tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442138","messageId":"patch-v2-8.9-b8387a4a76d-20211123T115551Z-avarab@gmail.com","threadId":"56951","inReplyTo":"cover-v2-0.9-00000000000-20211123T115551Z-avarab@gmail.com","subject":"[PATCH v2 8/9] difftool: use \"env_array\" to simplify memory management","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T12:06:35Z","receivedAt":"2021-11-23T12:08:20Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Amend code added in 03831ef7b50 (difftool: implement the functionality\nin the builtin, 2017-01-19) to use the \"env_array\" in the\nrun_command.[ch] API. Now we no longer need to manage our own\n\"index_env\" buffer.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/difftool.c | 14 ++++----------\n 1 file changed, 4 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nindex 4931c108451..4ee40fe3a06 100644\n--- a/builtin/difftool.c\n+++ b/builtin/difftool.c\n@@ -202,15 +202,10 @@ static void changed_files(struct hashmap *result, const char *index_path,\n {\n \tstruct child_process update_index = CHILD_PROCESS_INIT;\n \tstruct child_process diff_files = CHILD_PROCESS_INIT;\n-\tstruct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT;\n-\tconst char *git_dir = absolute_path(get_git_dir()), *env[] = {\n-\t\tNULL, NULL\n-\t};\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tconst char *git_dir = absolute_path(get_git_dir());\n \tFILE *fp;\n \n-\tstrbuf_addf(&index_env, \"GIT_INDEX_FILE=%s\", index_path);\n-\tenv[0] = index_env.buf;\n-\n \tstrvec_pushl(&update_index.args,\n \t\t     \"--git-dir\", git_dir, \"--work-tree\", workdir,\n \t\t     \"update-index\", \"--really-refresh\", \"-q\",\n@@ -222,7 +217,7 @@ static void changed_files(struct hashmap *result, const char *index_path,\n \tupdate_index.use_shell = 0;\n \tupdate_index.clean_on_exit = 1;\n \tupdate_index.dir = workdir;\n-\tupdate_index.env = env;\n+\tstrvec_pushf(&update_index.env_array, \"GIT_INDEX_FILE=%s\", index_path);\n \t/* Ignore any errors of update-index */\n \trun_command(&update_index);\n \n@@ -235,7 +230,7 @@ static void changed_files(struct hashmap *result, const char *index_path,\n \tdiff_files.clean_on_exit = 1;\n \tdiff_files.out = -1;\n \tdiff_files.dir = workdir;\n-\tdiff_files.env = env;\n+\tstrvec_pushf(&diff_files.env_array, \"GIT_INDEX_FILE=%s\", index_path);\n \tif (start_command(&diff_files))\n \t\tdie(\"could not obtain raw diff\");\n \tfp = xfdopen(diff_files.out, \"r\");\n@@ -248,7 +243,6 @@ static void changed_files(struct hashmap *result, const char *index_path,\n \tfclose(fp);\n \tif (finish_command(&diff_files))\n \t\tdie(\"diff-files did not exit properly\");\n-\tstrbuf_release(&index_env);\n \tstrbuf_release(&buf);\n }\n \n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442139","messageId":"patch-v2-9.9-6bd9f508a3d-20211123T115551Z-avarab@gmail.com","threadId":"56951","inReplyTo":"cover-v2-0.9-00000000000-20211123T115551Z-avarab@gmail.com","subject":"[PATCH v2 9/9] run-command API: remove \"env\" member, always use \"env_array\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T12:06:36Z","receivedAt":"2021-11-23T12:08:23Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Remove the \"env\" member from \"struct child_process\" in favor of always\nusing the \"env_array\". As with the preceding removal of \"argv\" in\nfavor of \"args\" this gets rid of current and future oddities around\nmemory management at the API boundary (see the amended API docs).\n\nFor some of the conversions we can replace patterns like:\n\n    child.env = env->v;\n\nWith:\n\n    strvec_pushv(&child.env_array, env->v);\n\nBut for others we need to guard the strvec_pushv() with a NULL check,\nsince we're not passing in the \"v\" member of a \"struct strvec\",\ne.g. in the case of tmp_objdir_env()'s return value.\n\nIdeally we'd rename the \"env_array\" member to simply \"env\" as a\nfollow-up, since it and \"args\" are now inconsistent in not having an\n\"_array\" suffix, and seemingly without any good reason, unless we look\nat the history of how they came to be.\n\nBut as we've currently got 122 in-tree hits for a \"git grep env_array\"\nlet's leave that for now (and possibly forever). Doing that rename\nwould be too disruptive.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/receive-pack.c | 11 ++++++-----\n builtin/worktree.c     |  6 +++---\n connected.c            |  3 ++-\n editor.c               |  4 +++-\n object-file.c          |  2 +-\n run-command.c          | 20 +++++++-------------\n run-command.h          | 34 +++++++++++++++++-----------------\n trailer.c              |  2 +-\n 8 files changed, 40 insertions(+), 42 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 48c99c8ee45..3979752ceca 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1367,7 +1367,7 @@ static const char *push_to_deploy(unsigned char *sha1,\n \n \tstrvec_pushl(&child.args, \"update-index\", \"-q\", \"--ignore-submodules\",\n \t\t     \"--refresh\", NULL);\n-\tchild.env = env->v;\n+\tstrvec_pushv(&child.env_array, env->v);\n \tchild.dir = work_tree;\n \tchild.no_stdin = 1;\n \tchild.stdout_to_stderr = 1;\n@@ -1379,7 +1379,7 @@ static const char *push_to_deploy(unsigned char *sha1,\n \tchild_process_init(&child);\n \tstrvec_pushl(&child.args, \"diff-files\", \"--quiet\",\n \t\t     \"--ignore-submodules\", \"--\", NULL);\n-\tchild.env = env->v;\n+\tstrvec_pushv(&child.env_array, env->v);\n \tchild.dir = work_tree;\n \tchild.no_stdin = 1;\n \tchild.stdout_to_stderr = 1;\n@@ -1393,7 +1393,7 @@ static const char *push_to_deploy(unsigned char *sha1,\n \t\t     /* diff-index with either HEAD or an empty tree */\n \t\t     head_has_history() ? \"HEAD\" : empty_tree_oid_hex(),\n \t\t     \"--\", NULL);\n-\tchild.env = env->v;\n+\tstrvec_pushv(&child.env_array, env->v);\n \tchild.no_stdin = 1;\n \tchild.no_stdout = 1;\n \tchild.stdout_to_stderr = 0;\n@@ -1404,7 +1404,7 @@ static const char *push_to_deploy(unsigned char *sha1,\n \tchild_process_init(&child);\n \tstrvec_pushl(&child.args, \"read-tree\", \"-u\", \"-m\", hash_to_hex(sha1),\n \t\t     NULL);\n-\tchild.env = env->v;\n+\tstrvec_pushv(&child.env_array, env->v);\n \tchild.dir = work_tree;\n \tchild.no_stdin = 1;\n \tchild.no_stdout = 1;\n@@ -2202,7 +2202,8 @@ static const char *unpack(int err_fd, struct shallow_info *si)\n \t\t\tclose(err_fd);\n \t\treturn \"unable to create temporary object directory\";\n \t}\n-\tchild.env = tmp_objdir_env(tmp_objdir);\n+\tif (tmp_objdir)\n+\t\tstrvec_pushv(&child.env_array, tmp_objdir_env(tmp_objdir));\n \n \t/*\n \t * Normally we just pass the tmp_objdir environment to the child\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 7264a5b5de0..36b3d6c40da 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -349,7 +349,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\t\tstrvec_push(&cp.args, \"--quiet\");\n \t}\n \n-\tcp.env = child_env.v;\n+\tstrvec_pushv(&cp.env_array, child_env.v);\n \tret = run_command(&cp);\n \tif (ret)\n \t\tgoto done;\n@@ -359,7 +359,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\tstrvec_pushl(&cp.args, \"reset\", \"--hard\", \"--no-recurse-submodules\", NULL);\n \t\tif (opts->quiet)\n \t\t\tstrvec_push(&cp.args, \"--quiet\");\n-\t\tcp.env = child_env.v;\n+\t\tstrvec_pushv(&cp.env_array, child_env.v);\n \t\tret = run_command(&cp);\n \t\tif (ret)\n \t\t\tgoto done;\n@@ -388,7 +388,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\t\tcp.no_stdin = 1;\n \t\t\tcp.stdout_to_stderr = 1;\n \t\t\tcp.dir = path;\n-\t\t\tcp.env = env;\n+\t\t\tstrvec_pushv(&cp.env_array, env);\n \t\t\tcp.trace2_hook_name = \"post-checkout\";\n \t\t\tstrvec_pushl(&cp.args, absolute_path(hook),\n \t\t\t\t     oid_to_hex(null_oid()),\ndiff --git a/connected.c b/connected.c\nindex 35bd4a26382..ed3025e7a2a 100644\n--- a/connected.c\n+++ b/connected.c\n@@ -109,7 +109,8 @@ int check_connected(oid_iterate_fn fn, void *cb_data,\n \t\t\t     _(\"Checking connectivity\"));\n \n \trev_list.git_cmd = 1;\n-\trev_list.env = opt->env;\n+\tif (opt->env)\n+\t\tstrvec_pushv(&rev_list.env_array, opt->env);\n \trev_list.in = -1;\n \trev_list.no_stdout = 1;\n \tif (opt->err_fd)\ndiff --git a/editor.c b/editor.c\nindex b7d2a9b73c3..7044ad5f72e 100644\n--- a/editor.c\n+++ b/editor.c\n@@ -1,6 +1,7 @@\n #include \"cache.h\"\n #include \"config.h\"\n #include \"strbuf.h\"\n+#include \"strvec.h\"\n #include \"run-command.h\"\n #include \"sigchain.h\"\n #include \"compat/terminal.h\"\n@@ -81,7 +82,8 @@ static int launch_specified_editor(const char *editor, const char *path,\n \t\tstrbuf_realpath(&realpath, path, 1);\n \n \t\tstrvec_pushl(&p.args, editor, realpath.buf, NULL);\n-\t\tp.env = env;\n+\t\tif (env)\n+\t\t\tstrvec_pushv(&p.env_array, (const char **)env);\n \t\tp.use_shell = 1;\n \t\tp.trace2_child_class = \"editor\";\n \t\tterm_fail = save_term(1);\ndiff --git a/object-file.c b/object-file.c\nindex c3d866a287e..2fc1bed417c 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -797,7 +797,7 @@ static void fill_alternate_refs_command(struct child_process *cmd,\n \t\t}\n \t}\n \n-\tcmd->env = local_repo_env;\n+\tstrvec_pushv(&cmd->env_array, (const char **)local_repo_env);\n \tcmd->out = -1;\n }\n \ndiff --git a/run-command.c b/run-command.c\nindex 99dc93e7300..ebade086700 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -655,12 +655,7 @@ static void trace_run_command(const struct child_process *cp)\n \t\tsq_quote_buf_pretty(&buf, cp->dir);\n \t\tstrbuf_addch(&buf, ';');\n \t}\n-\t/*\n-\t * The caller is responsible for initializing cp->env from\n-\t * cp->env_array if needed. We only check one place.\n-\t */\n-\tif (cp->env)\n-\t\ttrace_add_env(&buf, cp->env);\n+\ttrace_add_env(&buf, cp->env_array.v);\n \tif (cp->git_cmd)\n \t\tstrbuf_addstr(&buf, \" git\");\n \tsq_quote_argv_pretty(&buf, cp->args.v);\n@@ -676,9 +671,6 @@ int start_command(struct child_process *cmd)\n \tint failed_errno;\n \tchar *str;\n \n-\tif (!cmd->env)\n-\t\tcmd->env = cmd->env_array.v;\n-\n \t/*\n \t * In case of errors we must keep the promise to close FDs\n \t * that have been passed in via ->in and ->out.\n@@ -768,7 +760,7 @@ int start_command(struct child_process *cmd)\n \t\tset_cloexec(null_fd);\n \t}\n \n-\tchildenv = prep_childenv(cmd->env);\n+\tchildenv = prep_childenv(cmd->env_array.v);\n \tatfork_prepare(&as);\n \n \t/*\n@@ -931,7 +923,7 @@ int start_command(struct child_process *cmd)\n \telse if (cmd->use_shell)\n \t\tcmd->args.v = prepare_shell_cmd(&nargv, sargv);\n \n-\tcmd->pid = mingw_spawnvpe(cmd->args.v[0], cmd->args.v, (char**) cmd->env,\n+\tcmd->pid = mingw_spawnvpe(cmd->args.v[0], cmd->args.v, (char**) cmd->env_array.v,\n \t\t\tcmd->dir, fhin, fhout, fherr);\n \tfailed_errno = errno;\n \tif (cmd->pid < 0 && (!cmd->silent_exec_failure || errno != ENOENT))\n@@ -1047,7 +1039,8 @@ int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,\n \tcmd.wait_after_clean = opt & RUN_WAIT_AFTER_CLEAN ? 1 : 0;\n \tcmd.close_object_store = opt & RUN_CLOSE_OBJECT_STORE ? 1 : 0;\n \tcmd.dir = dir;\n-\tcmd.env = env;\n+\tif (env)\n+\t\tstrvec_pushv(&cmd.env_array, (const char **)env);\n \tcmd.trace2_child_class = tr2_class;\n \treturn run_command(&cmd);\n }\n@@ -1333,7 +1326,8 @@ int run_hook_ve(const char *const *env, const char *name, va_list args)\n \tstrvec_push(&hook.args, p);\n \twhile ((p = va_arg(args, const char *)))\n \t\tstrvec_push(&hook.args, p);\n-\thook.env = env;\n+\tif (env)\n+\t\tstrvec_pushv(&hook.env_array, (const char **)env);\n \thook.no_stdin = 1;\n \thook.stdout_to_stderr = 1;\n \thook.trace2_hook_name = name;\ndiff --git a/run-command.h b/run-command.h\nindex c0d1210cc63..2be5f5d6422 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -56,6 +56,23 @@ struct child_process {\n \t * `finish_command` (or during `start_command` when it is unsuccessful).\n \t */\n \tstruct strvec args;\n+\n+\t/**\n+\t * Like .args the .env_array is a `struct strvec'.\n+\t *\n+\t * To modify the environment of the sub-process, specify an array of\n+\t * environment settings. Each string in the array manipulates the\n+\t * environment.\n+\t *\n+\t * - If the string is of the form \"VAR=value\", i.e. it contains '='\n+\t *   the variable is added to the child process's environment.\n+\t *\n+\t * - If the string does not contain '=', it names an environment\n+\t *   variable that will be removed from the child process's environment.\n+\t *\n+\t * The memory in .env_array will be cleaned up automatically during\n+\t * `finish_command` (or during `start_command` when it is unsuccessful).\n+\t */\n \tstruct strvec env_array;\n \tpid_t pid;\n \n@@ -92,23 +109,6 @@ struct child_process {\n \t */\n \tconst char *dir;\n \n-\t/**\n-\t * To modify the environment of the sub-process, specify an array of\n-\t * string pointers (NULL terminated) in .env:\n-\t *\n-\t * - If the string is of the form \"VAR=value\", i.e. it contains '='\n-\t *   the variable is added to the child process's environment.\n-\t *\n-\t * - If the string does not contain '=', it names an environment\n-\t *   variable that will be removed from the child process's environment.\n-\t *\n-\t * If the .env member is NULL, `start_command` will point it at the\n-\t * .env_array `strvec` (so you may use one or the other, but not both).\n-\t * The memory in .env_array will be cleaned up automatically during\n-\t * `finish_command` (or during `start_command` when it is unsuccessful).\n-\t */\n-\tconst char *const *env;\n-\n \tunsigned no_stdin:1;\n \tunsigned no_stdout:1;\n \tunsigned no_stderr:1;\ndiff --git a/trailer.c b/trailer.c\nindex 7c7cb61a945..1b12f77d945 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -236,7 +236,7 @@ static char *apply_command(struct conf_info *conf, const char *arg)\n \t\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n \t\tstrvec_push(&cp.args, cmd.buf);\n \t}\n-\tcp.env = local_repo_env;\n+\tstrvec_pushv(&cp.env_array, (const char **)local_repo_env);\n \tcp.no_stdin = 1;\n \tcp.use_shell = 1;\n \n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442160","messageId":"CAPig+cStZp=AOPHW8i7AqwDOV6djYzHC6GmcVeb=4PUj5bjvAw@mail.gmail.com","threadId":"56951","inReplyTo":"patch-v2-1.9-9cc220ce5a3-20211123T115551Z-avarab@gmail.com","subject":"Re: [PATCH v2 1/9] worktree: remove redundant NULL-ing of \"cp.argv","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2021-11-23T15:26:56Z","receivedAt":"2021-11-23T15:27:10Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Nov 23, 2021 at 7:08 AM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n> The clearing of \"argv\" was added in 7f44e3d1de0 (worktree: make setup\n> of new HEAD distinct from worktree population, 2015-07-17) when the\n> \"cp\" variable wasn't initialized. It hasn't been needed since\n> 542aa25d974 (use CHILD_PROCESS_INIT to initialize automatic variables,\n> 2016-08-05).\n>\n> Let's remove it to make a later change that gets rid of the \"argv\"\n> member from \"struct child_process\" smaller.\n\nLet me preface with the caveat that (due to lack of time) I haven't\ndug into this deeply and I haven't been following changes to the\nrun_command() machinery closely, so what I say below may be\ninaccurate, but...\n\nAlthough it will be safe to drop these builtin/worktree.c assignments\nwhen the series eventually removes the child_process::argv member, I\nthink that the commit message is misleading (or outright wrong), and\nthat this change so early in the series potentially breaks\ngit-worktree, leaving it in a state where it works only \"by accident\".\n\nThe reason that this code repeatedly clears `cp.argv` is that the `cp`\nstructure is reused for multiple run_command() invocations (which, in\nretrospect, is pretty ugly since worktree.c is too familiar with the\ninternals of run-command). The reason that `cp.argv` needs to be\ncleared between each invocation is due to this code from\nstart_command():\n\n    if (!cmd->argv)\n         cmd->argv = cmd->args.v;\n\nworktree.c re-populates child_process::args between run_command()\ninvocations and needs to clear child_process::argv to ensure that the\nlatter gets re-initialized from child_process::args each time. The use\nof CHILD_PROCESS_INIT does not change anything in regard to the\nrequirement; child_process::argv still needs to be cleared between\nrun_command() invocations.\n\nAs to why this change so early in the series potentially breaks\ngit-worktree: with the removal of these assignments,\nchild_process::argv now _always_ points at the _initial_ value of\nchild_process::args.v even though that vector gets cleared between\nrun_command() invocations. At best, following this change,\ngit-worktree is only working \"by accident\" if the underlying\nchild_process::args.v doesn't get reallocated between run_command()\ninvocations. Relying upon this \"by accident\" behavior feels rather\nunsafe.\n\nI think perhaps the simplest thing to do is merely to squash this\npatch into the patch which ultimately removes the child_process::argv\nmember (and the removal of these lines from worktree.c probably\ndoesn't even need mention in the commit message -- or maybe just a\nminor mention).\n\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> ---\n> diff --git a/builtin/worktree.c b/builtin/worktree.c\n> index d22ece93e1a..7264a5b5de0 100644\n> --- a/builtin/worktree.c\n> +++ b/builtin/worktree.c\n> @@ -355,7 +355,6 @@ static int add_worktree(const char *path, const char *refname,\n>                 goto done;\n>\n>         if (opts->checkout) {\n> -               cp.argv = NULL;\n>                 strvec_clear(&cp.args);\n>                 strvec_pushl(&cp.args, \"reset\", \"--hard\", \"--no-recurse-submodules\", NULL);\n>                 if (opts->quiet)\n> @@ -390,7 +389,6 @@ static int add_worktree(const char *path, const char *refname,\n>                         cp.stdout_to_stderr = 1;\n>                         cp.dir = path;\n>                         cp.env = env;\n> -                       cp.argv = NULL;\n>                         cp.trace2_hook_name = \"post-checkout\";\n>                         strvec_pushl(&cp.args, absolute_path(hook),\n>                                      oid_to_hex(null_oid()),\n> --\n> 2.34.0.831.gd33babec0d1\n"},{"id":"442171","messageId":"20211123164015.6zkbf3xmnofykedz@cyberdelia","threadId":"56951","inReplyTo":"xmqqczmsi0i5.fsf@gitster.g","subject":"Re: [PATCH v2] pager: fix crash when pager program doesn't exist","fromName":"Enzo Matsumiya","fromEmail":"ematsumiya@suse.de","sentAt":"2021-11-23T16:40:15Z","receivedAt":"2021-11-23T16:40:20Z","isPatch":true,"sender":{"key":"ematsumiya@suse.de","avatar":"https://avatars.githubusercontent.com/u/45605263?v=4"},"body":"On 11/22, Junio C Hamano wrote:\n>Enzo Matsumiya <ematsumiya@suse.de> writes:\n>\n>> I'm preparing v3 with the above suggestions in mind.\n>\n>Thanks for an update, and thanks for working on this one.\n\nBtw I'm on hold until Ævar's patchset is sorted out, which seems to kind\nof overlap/invalidate my fix.\n\nSorry I couldn't follow much of yesterday's discussion.\n\n\nCheers,\n\nEnzo\n"},{"id":"442221","messageId":"CAPig+cSybWJ=M7X_qQFWJVVWP-9pVbVcwptky2QUA+jOYKMFng@mail.gmail.com","threadId":"56951","inReplyTo":"patch-v2-4.9-a2ee10e214c-20211123T115551Z-avarab@gmail.com","subject":"Re: [PATCH v2 4/9] run-command tests: use strvec_pushv(), not argv assignment","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2021-11-24T01:33:02Z","receivedAt":"2021-11-24T01:33:20Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Nov 23, 2021 at 7:08 AM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n> As in the preceding commit change this API user to use strvec_pushv()\n> instead of assigning to the \"argv\" member directly. This leaves is\n\ns/leaves is/leaves us/\n\n> without test coverage of how the \"argv\" assignment in this API works,\n> but we'll be removing it in a subsequent commit.\n>\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n"},{"id":"442224","messageId":"xmqqy25e48u5.fsf@gitster.g","threadId":"56951","inReplyTo":"CAPig+cStZp=AOPHW8i7AqwDOV6djYzHC6GmcVeb=4PUj5bjvAw@mail.gmail.com","subject":"Re: [PATCH v2 1/9] worktree: remove redundant NULL-ing of \"cp.argv","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-24T01:54:10Z","receivedAt":"2021-11-24T01:54:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> ... At best, following this change,\n> git-worktree is only working \"by accident\" if the underlying\n> child_process::args.v doesn't get reallocated between run_command()\n> invocations. Relying upon this \"by accident\" behavior feels rather\n> unsafe.\n\nVery true.  Relying on the \"if argv is null, point it at args.v\"\nassignment at the very beginning of the start_command() function is\nsafe because by that time the reallocations have happened already\nif needed.\n\nThe pattern with or without NULLing is\n\n\tinitialize cp\n\tpush to cp.args\n\tuse cp\n\n\t/* cp.argv = NULL */\n\tstrvec_clear(&cp.args);\n\tpush to cp.args\n\nand strvec_clear() frees the underying array, and the first push\nwill reallocates from NULL, so there is no guarantee that cp.argv\nin the first use that used to be pointing at cp.args that has\nalready been freed is still valid.\n\nThanks for spotting this.  Has this patch ever been tested with\nsanitizer?  Do we have gap in test coverage?\n\n\n\n\n\n"},{"id":"442225","messageId":"211124.865ysie2br.gmgdl@evledraar.gmail.com","threadId":"56951","inReplyTo":"20211123164015.6zkbf3xmnofykedz@cyberdelia","subject":"Re: [PATCH v2] pager: fix crash when pager program doesn't exist","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-24T01:55:20Z","receivedAt":"2021-11-24T02:04:47Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Nov 23 2021, Enzo Matsumiya wrote:\n\n> On 11/22, Junio C Hamano wrote:\n>>Enzo Matsumiya <ematsumiya@suse.de> writes:\n>>\n>>> I'm preparing v3 with the above suggestions in mind.\n>>\n>>Thanks for an update, and thanks for working on this one.\n>\n> Btw I'm on hold until Ævar's patchset is sorted out, which seems to kind\n> of overlap/invalidate my fix.\n>\n> Sorry I couldn't follow much of yesterday's discussion.\n\nI think per https://lore.kernel.org/git/xmqq7dd0giwp.fsf@gitster.g/ that\nJunio's in turn waiting on you, and in my v2 re-roll of my topic[1] I\nejected the test derived from your report, on the assumption that an\nearlier fix from you would land first.\n\nI.e. I understood that Junio wanted to queue up your more narrow fix\nwhich would fix the segfault, and my larger topic to remove \"argv\" and\n\"env\" might come some time later.\n\nI don't mind either way as long as the root cause of \"argv\" and \"env\"\ngets fixed eventually.\n\nI do wonder re [2] and [3] if a simpler and self-contained/isolated\npatch in this area might not be a mirage of sorts. I.e. to know whether\nthe approach in [2] and [3] is safe we basically have to reason about\nall the callers of this API anyway, which is what my larger series does.\n\nBut I honestly didn't look too deeply into your approach & what could be\ndone to safely work around \"argv\" and/or \"env\" on the current \"master\",\nsince I had the alternate patches to remove them entirely :)\n\n1. https://lore.kernel.org/git/cover-v2-0.9-00000000000-20211123T115551Z-avarab@gmail.com/\n2. https://lore.kernel.org/git/20211122153119.h2t2ti3lkiycd7pb@cyberdelia/\n3. https://lore.kernel.org/git/YZvFkwivicJ%2ftFAo@coredump.intra.peff.net/\n"},{"id":"442228","messageId":"YZ3RUkGpixYAREcI@flurp.local","threadId":"56951","inReplyTo":"CAPig+cStZp=AOPHW8i7AqwDOV6djYzHC6GmcVeb=4PUj5bjvAw@mail.gmail.com","subject":"Re: [PATCH v2 1/9] worktree: remove redundant NULL-ing of \"cp.argv","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2021-11-24T05:44:50Z","receivedAt":"2021-11-24T05:44:59Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Nov 23, 2021 at 10:26:56AM -0500, Eric Sunshine wrote:\n> I think perhaps the simplest thing to do is merely to squash this\n> patch into the patch which ultimately removes the child_process::argv\n> member (and the removal of these lines from worktree.c probably\n> doesn't even need mention in the commit message -- or maybe just a\n> minor mention).\n\nOn second thought, squashing this patch into the patch which\nultimately retires child_process::argv is probably not necessary. If\nthe patch is instead rewritten as below, then it prepares\nbuiltin/worktree.c for eventual retirement of child_process::argv\nwithout breaking git-worktree functionality.\n\n(You could also extend this patch so it prepares for removal of\nchild_process::env, as well, or keep it minimal as I did here.)\n\n-- >8 --\nFrom: Eric Sunshine <sunshine@sunshineco.com>\nSubject: [PATCH] worktree: stop being overly intimate with run_command()\n internals\n\nadd_worktree() reuses a `child_process` for three run_command()\ninvocations, but to do so, it has overly-intimate knowledge of\nrun-command.c internals. In particular, it knows that it must reset\nchild_process::argv to NULL for each subsequent invocation[*] in order\nfor start_command() to latch the newly-populated child_process::args for\neach invocation, even though this behavior is not a part of the\ndocumented API. Beyond having overly-intimate knowledge of run-command.c\ninternals, the reuse of one `child_process` for three run_command()\ninvocations smells like an unnecessary micro-optimization. Therefore,\nstop sharing one `child_process` and instead use a new one for each\nrun_command() call.\n\n[*] If child_process::argv is not reset to NULL, then subsequent\nrun_command() invocations will instead incorrectly access a dangling\npointer to freed memory which had been allocated by child_process::args\non the previous run. This is due to the following code in\nstart_command():\n\n    if (!cmd->argv)\n        cmd->argv = cmd->args.v;\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n builtin/worktree.c | 7 +++----\n 1 file changed, 3 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex d22ece93e1..9edd3e2829 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -355,8 +355,8 @@ static int add_worktree(const char *path, const char *refname,\n \t\tgoto done;\n \n \tif (opts->checkout) {\n-\t\tcp.argv = NULL;\n-\t\tstrvec_clear(&cp.args);\n+\t\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\t\tcp.git_cmd = 1;\n \t\tstrvec_pushl(&cp.args, \"reset\", \"--hard\", \"--no-recurse-submodules\", NULL);\n \t\tif (opts->quiet)\n \t\t\tstrvec_push(&cp.args, \"--quiet\");\n@@ -385,12 +385,11 @@ static int add_worktree(const char *path, const char *refname,\n \t\tconst char *hook = find_hook(\"post-checkout\");\n \t\tif (hook) {\n \t\t\tconst char *env[] = { \"GIT_DIR\", \"GIT_WORK_TREE\", NULL };\n-\t\t\tcp.git_cmd = 0;\n+\t\t\tstruct child_process cp = CHILD_PROCESS_INIT;\n \t\t\tcp.no_stdin = 1;\n \t\t\tcp.stdout_to_stderr = 1;\n \t\t\tcp.dir = path;\n \t\t\tcp.env = env;\n-\t\t\tcp.argv = NULL;\n \t\t\tcp.trace2_hook_name = \"post-checkout\";\n \t\t\tstrvec_pushl(&cp.args, absolute_path(hook),\n \t\t\t\t     oid_to_hex(null_oid()),\n-- \n2.34.0.399.gdf2c515fd2\n\n-- >8 --\n"},{"id":"442231","messageId":"CAPig+cRMYiCvq7=ESSk7+PM50p4PeoKFat+w9svXMxsCHA6HEw@mail.gmail.com","threadId":"56951","inReplyTo":"xmqqy25e48u5.fsf@gitster.g","subject":"Re: [PATCH v2 1/9] worktree: remove redundant NULL-ing of \"cp.argv","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2021-11-24T06:00:43Z","receivedAt":"2021-11-24T06:01:02Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Nov 23, 2021 at 8:54 PM Junio C Hamano <gitster@pobox.com> wrote:\n> Eric Sunshine <sunshine@sunshineco.com> writes:\n> > ... At best, following this change,\n> > git-worktree is only working \"by accident\" if the underlying\n> > child_process::args.v doesn't get reallocated between run_command()\n> > invocations. Relying upon this \"by accident\" behavior feels rather\n> > unsafe.\n>\n> The pattern with or without NULLing is\n>         /* cp.argv = NULL */\n>         strvec_clear(&cp.args);\n>         push to cp.args\n>\n> and strvec_clear() frees the underying array, and the first push\n> will reallocates from NULL, so there is no guarantee that cp.argv\n> in the first use that used to be pointing at cp.args that has\n> already been freed is still valid.\n\nIndeed, so this is even worse than I thought. I was somewhat pressed\nfor time when I wrote the review, thus didn't look at the\nimplementation of strvec_clear(), and incorrectly thought that it only\nreset its `nr` member to 0 but kept the array allocated (much like\nstrbuf_reset()). That's why I thought it was only working \"by\naccident\". But, as you point out, strvec_clear() does free its\nallocated array (much like strbuf_release()), so -- with this patch\napplied -- each subsequent run_command() invocation is _definitely_\naccessing the dangling pointer in child_process::argv, and that\ndangling pointer would (in the \"best\" case) be referencing the\noriginal populated value of child_process::args, not the repopulated\nvalue. So, even if it didn't crash outright, it would just re-run the\nsame command three times (unless by chance it reallocated the same\nmemory it had freed earlier.)\n\n> Thanks for spotting this.  Has this patch ever been tested with\n> sanitizer?  Do we have gap in test coverage?\n\nThe question about potential gap in test coverage is a good one.\nMaybe, by chance it reallocated the same memory that it had earlier\nfreed, thus did indeed work \"by accident\". Another possibility is that\nÆvar only ran the tests after applying the full patch series, in which\ncase this dangling-pointer bug would be gone, rather than running the\ntests after each patch.\n"},{"id":"442233","messageId":"CAPig+cTNEQhv=OeZDrXA4k7K1hgpbrBUa91wdfpxo6Se38DBEw@mail.gmail.com","threadId":"56951","inReplyTo":"CAPig+cRMYiCvq7=ESSk7+PM50p4PeoKFat+w9svXMxsCHA6HEw@mail.gmail.com","subject":"Re: [PATCH v2 1/9] worktree: remove redundant NULL-ing of \"cp.argv","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2021-11-24T06:12:11Z","receivedAt":"2021-11-24T06:12:25Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, Nov 24, 2021 at 1:00 AM Eric Sunshine <sunshine@sunshineco.com> wrote:\n> On Tue, Nov 23, 2021 at 8:54 PM Junio C Hamano <gitster@pobox.com> wrote:\n> > Thanks for spotting this.  Has this patch ever been tested with\n> > sanitizer?  Do we have gap in test coverage?\n>\n> The question about potential gap in test coverage is a good one.\n> Maybe, by chance it reallocated the same memory that it had earlier\n> freed, thus did indeed work \"by accident\". Another possibility is that\n> Ævar only ran the tests after applying the full patch series, in which\n> case this dangling-pointer bug would be gone, rather than running the\n> tests after each patch.\n\nAs a follow-up, I just applied this patch alone and ran the tests, and\nthey do indeed fail as expected (on my macOS). In\nt2400-worktree-add.sh, alone, 44 out of 71 tests failed, thus I don't\nthink there's a gap in test coverage. So, the most likely explanation\nof how this problem slipped through is that Ævar only tested after\napplying the full series, in which case the dangling pointer bug would\nbe gone, rather than testing after each patch.\n"},{"id":"442250","messageId":"YZ5fZGyMj/lAuy7i@coredump.intra.peff.net","threadId":"56951","inReplyTo":"211124.865ysie2br.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v2] pager: fix crash when pager program doesn't exist","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-11-24T15:51:00Z","receivedAt":"2021-11-24T15:51:04Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Nov 24, 2021 at 02:55:20AM +0100, Ævar Arnfjörð Bjarmason wrote:\n\n> I do wonder re [2] and [3] if a simpler and self-contained/isolated\n> patch in this area might not be a mirage of sorts. I.e. to know whether\n> the approach in [2] and [3] is safe we basically have to reason about\n> all the callers of this API anyway, which is what my larger series does.\n\nAfter thinking on the various solutions, the original snippet I posted\nto just re-initialize the struct in each run seems like the best fit[1].\nIt's true that this \"args/argv\" thing is the source of the actual\nsegfault, so any cleanup changes there would address that. But the root\nof the confusion in setup_pager() is that it inits the child only once,\nbut then uses it multiple times. There could be similar confusion over\nother fields in the struct (though I don't think there is currently). So\nthis seems like the most direct fix, and applies regardless of any\nargs/argv cleanup.\n\nIt would become moot if we start to die() on pager setup, which I'd be\nin favor of. But I think we should fix the segfault bug first, which\nallows us to worry about the larger behavior change separately.\n\n-Peff\n\n[1] https://lore.kernel.org/git/YZhVA8DOjHu90gzs@coredump.intra.peff.net/\n"},{"id":"442330","messageId":"cover-v3-0.9-00000000000-20211125T224833Z-avarab@gmail.com","threadId":"56951","inReplyTo":"cover-v2-0.9-00000000000-20211123T115551Z-avarab@gmail.com","subject":"[PATCH v3 0/9] run-command API: get rid of \"argv\" and \"env\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-25T22:52:15Z","receivedAt":"2021-11-25T22:54:32Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"See v2[1] for the overall summary of this topic.\n\nNew since v2:\n\n * Ejected my old 1/9 in favor of Eric Sunshine's version at\n   [2]. Thanks a lot Eric. And this time I ran the full tests for\n   every commit with \"rebase -x\", so it all passes now.\n\n * Minor typo correction, also pointed out by Eric.\n\n1. https://lore.kernel.org/git/cover-v2-0.9-00000000000-20211123T115551Z-avarab@gmail.com/\n2. https://lore.kernel.org/git/YZ3RUkGpixYAREcI@flurp.local/\n\nEric Sunshine (1):\n  worktree: stop being overly intimate with run_command() internals\n\nÆvar Arnfjörð Bjarmason (8):\n  upload-archive: use regular \"struct child_process\" pattern\n  run-command API users: use strvec_pushv(), not argv assignment\n  run-command tests: use strvec_pushv(), not argv assignment\n  run-command API users: use strvec_pushl(), not argv construction\n  run-command API users: use strvec_push(), not argv construction\n  run-command API: remove \"argv\" member, always use \"args\"\n  difftool: use \"env_array\" to simplify memory management\n  run-command API: remove \"env\" member, always use \"env_array\"\n\n add-patch.c                 |  4 +-\n archive-tar.c               |  9 ++---\n builtin/add.c               |  7 +---\n builtin/difftool.c          | 14 ++-----\n builtin/fsck.c              | 12 ++----\n builtin/help.c              |  3 +-\n builtin/merge.c             |  3 +-\n builtin/notes.c             |  5 +--\n builtin/receive-pack.c      | 80 ++++++++++++++-----------------------\n builtin/replace.c           |  3 +-\n builtin/upload-archive.c    |  5 ++-\n builtin/worktree.c          | 13 +++---\n connected.c                 |  3 +-\n daemon.c                    | 20 ++++------\n diff.c                      |  8 +---\n editor.c                    |  8 ++--\n http-backend.c              |  2 +-\n http.c                      |  5 ++-\n object-file.c               |  2 +-\n prompt.c                    |  7 +---\n remote-curl.c               |  2 +-\n run-command.c               | 62 +++++++++++++---------------\n run-command.h               | 54 ++++++++++++-------------\n sequencer.c                 | 10 ++---\n sub-process.c               |  2 +-\n t/helper/test-run-command.c |  9 +++--\n t/helper/test-subprocess.c  |  2 +-\n trace2/tr2_tgt_event.c      |  2 +-\n trace2/tr2_tgt_normal.c     |  2 +-\n trace2/tr2_tgt_perf.c       |  4 +-\n trailer.c                   |  2 +-\n transport.c                 | 11 +++--\n upload-pack.c               |  5 +--\n 33 files changed, 157 insertions(+), 223 deletions(-)\n\nRange-diff against v2:\n 1:  9cc220ce5a3 !  1:  1c3f9de33ad worktree: remove redundant NULL-ing of \"cp.argv\n    @@\n      ## Metadata ##\n    -Author: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n    +Author: Eric Sunshine <sunshine@sunshineco.com>\n     \n      ## Commit message ##\n    -    worktree: remove redundant NULL-ing of \"cp.argv\n    +    worktree: stop being overly intimate with run_command() internals\n     \n    -    The clearing of \"argv\" was added in 7f44e3d1de0 (worktree: make setup\n    -    of new HEAD distinct from worktree population, 2015-07-17) when the\n    -    \"cp\" variable wasn't initialized. It hasn't been needed since\n    -    542aa25d974 (use CHILD_PROCESS_INIT to initialize automatic variables,\n    -    2016-08-05).\n    +    add_worktree() reuses a `child_process` for three run_command()\n    +    invocations, but to do so, it has overly-intimate knowledge of\n    +    run-command.c internals. In particular, it knows that it must reset\n    +    child_process::argv to NULL for each subsequent invocation[*] in order\n    +    for start_command() to latch the newly-populated child_process::args for\n    +    each invocation, even though this behavior is not a part of the\n    +    documented API. Beyond having overly-intimate knowledge of run-command.c\n    +    internals, the reuse of one `child_process` for three run_command()\n    +    invocations smells like an unnecessary micro-optimization. Therefore,\n    +    stop sharing one `child_process` and instead use a new one for each\n    +    run_command() call.\n     \n    -    Let's remove it to make a later change that gets rid of the \"argv\"\n    -    member from \"struct child_process\" smaller.\n    +    [*] If child_process::argv is not reset to NULL, then subsequent\n    +    run_command() invocations will instead incorrectly access a dangling\n    +    pointer to freed memory which had been allocated by child_process::args\n    +    on the previous run. This is due to the following code in\n    +    start_command():\n     \n    +        if (!cmd->argv)\n    +            cmd->argv = cmd->args.v;\n    +\n    +    Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n     \n      ## builtin/worktree.c ##\n    @@ builtin/worktree.c: static int add_worktree(const char *path, const char *refnam\n      \n      \tif (opts->checkout) {\n     -\t\tcp.argv = NULL;\n    - \t\tstrvec_clear(&cp.args);\n    +-\t\tstrvec_clear(&cp.args);\n    ++\t\tstruct child_process cp = CHILD_PROCESS_INIT;\n    ++\t\tcp.git_cmd = 1;\n      \t\tstrvec_pushl(&cp.args, \"reset\", \"--hard\", \"--no-recurse-submodules\", NULL);\n      \t\tif (opts->quiet)\n    + \t\t\tstrvec_push(&cp.args, \"--quiet\");\n     @@ builtin/worktree.c: static int add_worktree(const char *path, const char *refname,\n    + \t\tconst char *hook = find_hook(\"post-checkout\");\n    + \t\tif (hook) {\n    + \t\t\tconst char *env[] = { \"GIT_DIR\", \"GIT_WORK_TREE\", NULL };\n    +-\t\t\tcp.git_cmd = 0;\n    ++\t\t\tstruct child_process cp = CHILD_PROCESS_INIT;\n    + \t\t\tcp.no_stdin = 1;\n      \t\t\tcp.stdout_to_stderr = 1;\n      \t\t\tcp.dir = path;\n      \t\t\tcp.env = env;\n 2:  bfa65e5afd7 =  2:  d3a418b3809 upload-archive: use regular \"struct child_process\" pattern\n 3:  61e4eb8e173 =  3:  595ff9a775d run-command API users: use strvec_pushv(), not argv assignment\n 4:  a2ee10e214c !  4:  764c9b813fb run-command tests: use strvec_pushv(), not argv assignment\n    @@ Commit message\n         run-command tests: use strvec_pushv(), not argv assignment\n     \n         As in the preceding commit change this API user to use strvec_pushv()\n    -    instead of assigning to the \"argv\" member directly. This leaves is\n    +    instead of assigning to the \"argv\" member directly. This leaves us\n         without test coverage of how the \"argv\" assignment in this API works,\n         but we'll be removing it in a subsequent commit.\n     \n 5:  2b446606eb9 =  5:  45803236764 run-command API users: use strvec_pushl(), not argv construction\n 6:  fad420dc563 =  6:  7bdf963ed04 run-command API users: use strvec_push(), not argv construction\n 7:  67ab5114ed7 =  7:  275535a447e run-command API: remove \"argv\" member, always use \"args\"\n 8:  b8387a4a76d =  8:  203f44a91f4 difftool: use \"env_array\" to simplify memory management\n 9:  6bd9f508a3d !  9:  4e4db79ea53 run-command API: remove \"env\" member, always use \"env_array\"\n    @@ editor.c\n     +#include \"strvec.h\"\n      #include \"run-command.h\"\n      #include \"sigchain.h\"\n    - #include \"compat/terminal.h\"\n    + \n     @@ editor.c: static int launch_specified_editor(const char *editor, const char *path,\n      \t\tstrbuf_realpath(&realpath, path, 1);\n      \n    @@ editor.c: static int launch_specified_editor(const char *editor, const char *pat\n     +\t\t\tstrvec_pushv(&p.env_array, (const char **)env);\n      \t\tp.use_shell = 1;\n      \t\tp.trace2_child_class = \"editor\";\n    - \t\tterm_fail = save_term(1);\n    + \t\tif (start_command(&p) < 0) {\n     \n      ## object-file.c ##\n     @@ object-file.c: static void fill_alternate_refs_command(struct child_process *cmd,\n-- \n2.34.1.838.g779e9098efb\n\n"},{"id":"442331","messageId":"patch-v3-1.9-1c3f9de33ad-20211125T224833Z-avarab@gmail.com","threadId":"56951","inReplyTo":"cover-v3-0.9-00000000000-20211125T224833Z-avarab@gmail.com","subject":"[PATCH v3 1/9] worktree: stop being overly intimate with run_command() internals","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-25T22:52:16Z","receivedAt":"2021-11-25T22:54:38Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Eric Sunshine <sunshine@sunshineco.com>\n\nadd_worktree() reuses a `child_process` for three run_command()\ninvocations, but to do so, it has overly-intimate knowledge of\nrun-command.c internals. In particular, it knows that it must reset\nchild_process::argv to NULL for each subsequent invocation[*] in order\nfor start_command() to latch the newly-populated child_process::args for\neach invocation, even though this behavior is not a part of the\ndocumented API. Beyond having overly-intimate knowledge of run-command.c\ninternals, the reuse of one `child_process` for three run_command()\ninvocations smells like an unnecessary micro-optimization. Therefore,\nstop sharing one `child_process` and instead use a new one for each\nrun_command() call.\n\n[*] If child_process::argv is not reset to NULL, then subsequent\nrun_command() invocations will instead incorrectly access a dangling\npointer to freed memory which had been allocated by child_process::args\non the previous run. This is due to the following code in\nstart_command():\n\n    if (!cmd->argv)\n        cmd->argv = cmd->args.v;\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/worktree.c | 7 +++----\n 1 file changed, 3 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex d22ece93e1a..9edd3e2829b 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -355,8 +355,8 @@ static int add_worktree(const char *path, const char *refname,\n \t\tgoto done;\n \n \tif (opts->checkout) {\n-\t\tcp.argv = NULL;\n-\t\tstrvec_clear(&cp.args);\n+\t\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\t\tcp.git_cmd = 1;\n \t\tstrvec_pushl(&cp.args, \"reset\", \"--hard\", \"--no-recurse-submodules\", NULL);\n \t\tif (opts->quiet)\n \t\t\tstrvec_push(&cp.args, \"--quiet\");\n@@ -385,12 +385,11 @@ static int add_worktree(const char *path, const char *refname,\n \t\tconst char *hook = find_hook(\"post-checkout\");\n \t\tif (hook) {\n \t\t\tconst char *env[] = { \"GIT_DIR\", \"GIT_WORK_TREE\", NULL };\n-\t\t\tcp.git_cmd = 0;\n+\t\t\tstruct child_process cp = CHILD_PROCESS_INIT;\n \t\t\tcp.no_stdin = 1;\n \t\t\tcp.stdout_to_stderr = 1;\n \t\t\tcp.dir = path;\n \t\t\tcp.env = env;\n-\t\t\tcp.argv = NULL;\n \t\t\tcp.trace2_hook_name = \"post-checkout\";\n \t\t\tstrvec_pushl(&cp.args, absolute_path(hook),\n \t\t\t\t     oid_to_hex(null_oid()),\n-- \n2.34.1.838.g779e9098efb\n\n"},{"id":"442332","messageId":"patch-v3-3.9-595ff9a775d-20211125T224833Z-avarab@gmail.com","threadId":"56951","inReplyTo":"cover-v3-0.9-00000000000-20211125T224833Z-avarab@gmail.com","subject":"[PATCH v3 3/9] run-command API users: use strvec_pushv(), not argv assignment","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-25T22:52:18Z","receivedAt":"2021-11-25T22:54:38Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Migrate those run-command API users that assign directly to the \"argv\"\nmember to use a strvec_pushv() of \"args\" instead.\n\nIn these cases it did not make sense to further refactor these\ncallers, e.g. daemon.c could be made to construct the arguments closer\nto handle(), but that would require moving the construction from its\ncmd_main() and pass \"argv\" through two intermediate functions.\n\nIt would be possible for a change like this to introduce a regression\nif we were doing:\n\n      cp.argv = argv;\n      argv[1] = \"foo\";\n\nAnd changed the code, as is being done here, to:\n\n      strvec_pushv(&cp.args, argv);\n      argv[1] = \"foo\";\n\nBut as viewing this change with the \"-W\" flag reveals none of these\nfunctions modify variable that's being pushed afterwards in a way that\nwould introduce such a logic error.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n add-patch.c                | 4 ++--\n daemon.c                   | 2 +-\n http-backend.c             | 2 +-\n http.c                     | 5 +++--\n remote-curl.c              | 2 +-\n run-command.c              | 2 +-\n t/helper/test-subprocess.c | 2 +-\n 7 files changed, 10 insertions(+), 9 deletions(-)\n\ndiff --git a/add-patch.c b/add-patch.c\nindex 8c41cdfe39b..573eef0cc4a 100644\n--- a/add-patch.c\n+++ b/add-patch.c\n@@ -413,7 +413,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n \t\tstrvec_push(&args, ps->items[i].original);\n \n \tsetup_child_process(s, &cp, NULL);\n-\tcp.argv = args.v;\n+\tstrvec_pushv(&cp.args, args.v);\n \tres = capture_command(&cp, plain, 0);\n \tif (res) {\n \t\tstrvec_clear(&args);\n@@ -431,7 +431,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)\n \n \t\tsetup_child_process(s, &colored_cp, NULL);\n \t\txsnprintf((char *)args.v[color_arg_index], 8, \"--color\");\n-\t\tcolored_cp.argv = args.v;\n+\t\tstrvec_pushv(&colored_cp.args, args.v);\n \t\tcolored = &s->colored;\n \t\tres = capture_command(&colored_cp, colored, 0);\n \t\tstrvec_clear(&args);\ndiff --git a/daemon.c b/daemon.c\nindex b1fcbe0d6fa..8df21f2130c 100644\n--- a/daemon.c\n+++ b/daemon.c\n@@ -922,7 +922,7 @@ static void handle(int incoming, struct sockaddr *addr, socklen_t addrlen)\n #endif\n \t}\n \n-\tcld.argv = cld_argv.v;\n+\tstrvec_pushv(&cld.args, cld_argv.v);\n \tcld.in = incoming;\n \tcld.out = dup(incoming);\n \ndiff --git a/http-backend.c b/http-backend.c\nindex 3d6e2ff17f8..4dd4d939f8a 100644\n--- a/http-backend.c\n+++ b/http-backend.c\n@@ -480,7 +480,7 @@ static void run_service(const char **argv, int buffer_input)\n \t\tstrvec_pushf(&cld.env_array,\n \t\t\t     \"GIT_COMMITTER_EMAIL=%s@http.%s\", user, host);\n \n-\tcld.argv = argv;\n+\tstrvec_pushv(&cld.args, argv);\n \tif (buffer_input || gzipped_request || req_len >= 0)\n \t\tcld.in = -1;\n \tcld.git_cmd = 1;\ndiff --git a/http.c b/http.c\nindex f92859f43fa..229da4d1488 100644\n--- a/http.c\n+++ b/http.c\n@@ -2126,8 +2126,9 @@ int finish_http_pack_request(struct http_pack_request *preq)\n \n \tip.git_cmd = 1;\n \tip.in = tmpfile_fd;\n-\tip.argv = preq->index_pack_args ? preq->index_pack_args\n-\t\t\t\t\t: default_index_pack_args;\n+\tstrvec_pushv(&ip.args, preq->index_pack_args ?\n+\t\t     preq->index_pack_args :\n+\t\t     default_index_pack_args);\n \n \tif (preq->preserve_index_pack_stdout)\n \t\tip.out = 0;\ndiff --git a/remote-curl.c b/remote-curl.c\nindex d69156312bd..0dabef2dd7c 100644\n--- a/remote-curl.c\n+++ b/remote-curl.c\n@@ -1061,7 +1061,7 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads,\n \tclient.in = -1;\n \tclient.out = -1;\n \tclient.git_cmd = 1;\n-\tclient.argv = client_argv;\n+\tstrvec_pushv(&client.args, client_argv);\n \tif (start_command(&client))\n \t\texit(1);\n \twrite_or_die(client.in, preamble->buf, preamble->len);\ndiff --git a/run-command.c b/run-command.c\nindex f40df01c772..620a06ca2f5 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -1039,7 +1039,7 @@ int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,\n \t\t\t\t const char *const *env, const char *tr2_class)\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n-\tcmd.argv = argv;\n+\tstrvec_pushv(&cmd.args, argv);\n \tcmd.no_stdin = opt & RUN_COMMAND_NO_STDIN ? 1 : 0;\n \tcmd.git_cmd = opt & RUN_GIT_CMD ? 1 : 0;\n \tcmd.stdout_to_stderr = opt & RUN_COMMAND_STDOUT_TO_STDERR ? 1 : 0;\ndiff --git a/t/helper/test-subprocess.c b/t/helper/test-subprocess.c\nindex 92b69de6352..ff22f2fa2c5 100644\n--- a/t/helper/test-subprocess.c\n+++ b/t/helper/test-subprocess.c\n@@ -15,6 +15,6 @@ int cmd__subprocess(int argc, const char **argv)\n \t\targv++;\n \t}\n \tcp.git_cmd = 1;\n-\tcp.argv = (const char **)argv + 1;\n+\tstrvec_pushv(&cp.args, (const char **)argv + 1);\n \treturn run_command(&cp);\n }\n-- \n2.34.1.838.g779e9098efb\n\n"},{"id":"442333","messageId":"patch-v3-2.9-d3a418b3809-20211125T224833Z-avarab@gmail.com","threadId":"56951","inReplyTo":"cover-v3-0.9-00000000000-20211125T224833Z-avarab@gmail.com","subject":"[PATCH v3 2/9] upload-archive: use regular \"struct child_process\" pattern","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-25T22:52:17Z","receivedAt":"2021-11-25T22:54:38Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"This pattern added [1] in seems to have been intentional, but since\n[2] and [3] we've wanted do initialization of what's now the \"struct\nstrvec\" \"args\" and \"env_array\" members. Let's not trample on that\ninitialization here.\n\n1. 1bc01efed17 (upload-archive: use start_command instead of fork,\n   2011-11-19)\n2. c460c0ecdca (run-command: store an optional argv_array, 2014-05-15)\n3. 9a583dc39e (run-command: add env_array, an optional argv_array for\n   env, 2014-10-19)\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/upload-archive.c | 5 +++--\n 1 file changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/upload-archive.c b/builtin/upload-archive.c\nindex 24654b4c9bf..98d028dae67 100644\n--- a/builtin/upload-archive.c\n+++ b/builtin/upload-archive.c\n@@ -77,7 +77,7 @@ static ssize_t process_input(int child_fd, int band)\n \n int cmd_upload_archive(int argc, const char **argv, const char *prefix)\n {\n-\tstruct child_process writer = { argv };\n+\tstruct child_process writer = CHILD_PROCESS_INIT;\n \n \tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n \t\tusage(upload_archive_usage);\n@@ -89,9 +89,10 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix)\n \t * multiplexed out to our fd#1.  If the child dies, we tell the other\n \t * end over channel #3.\n \t */\n-\targv[0] = \"upload-archive--writer\";\n \twriter.out = writer.err = -1;\n \twriter.git_cmd = 1;\n+\tstrvec_push(&writer.args, \"upload-archive--writer\");\n+\tstrvec_pushv(&writer.args, argv + 1);\n \tif (start_command(&writer)) {\n \t\tint err = errno;\n \t\tpacket_write_fmt(1, \"NACK unable to spawn subprocess\\n\");\n-- \n2.34.1.838.g779e9098efb\n\n"},{"id":"442334","messageId":"patch-v3-4.9-764c9b813fb-20211125T224833Z-avarab@gmail.com","threadId":"56951","inReplyTo":"cover-v3-0.9-00000000000-20211125T224833Z-avarab@gmail.com","subject":"[PATCH v3 4/9] run-command tests: use strvec_pushv(), not argv assignment","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-25T22:52:19Z","receivedAt":"2021-11-25T22:54:41Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"As in the preceding commit change this API user to use strvec_pushv()\ninstead of assigning to the \"argv\" member directly. This leaves us\nwithout test coverage of how the \"argv\" assignment in this API works,\nbut we'll be removing it in a subsequent commit.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n t/helper/test-run-command.c | 9 +++++----\n 1 file changed, 5 insertions(+), 4 deletions(-)\n\ndiff --git a/t/helper/test-run-command.c b/t/helper/test-run-command.c\nindex 3c4fb862234..913775a14b7 100644\n--- a/t/helper/test-run-command.c\n+++ b/t/helper/test-run-command.c\n@@ -31,7 +31,7 @@ static int parallel_next(struct child_process *cp,\n \tif (number_callbacks >= 4)\n \t\treturn 0;\n \n-\tstrvec_pushv(&cp->args, d->argv);\n+\tstrvec_pushv(&cp->args, d->args.v);\n \tstrbuf_addstr(err, \"preloaded output of a child\\n\");\n \tnumber_callbacks++;\n \treturn 1;\n@@ -274,7 +274,7 @@ static int quote_stress_test(int argc, const char **argv)\n \t\tif (i < skip)\n \t\t\tcontinue;\n \n-\t\tcp.argv = args.v;\n+\t\tstrvec_pushv(&cp.args, args.v);\n \t\tstrbuf_reset(&out);\n \t\tif (pipe_command(&cp, NULL, 0, &out, 0, NULL, 0) < 0)\n \t\t\treturn error(\"Failed to spawn child process\");\n@@ -396,7 +396,7 @@ int cmd__run_command(int argc, const char **argv)\n \t}\n \tif (argc < 3)\n \t\treturn 1;\n-\tproc.argv = (const char **)argv + 2;\n+\tstrvec_pushv(&proc.args, (const char **)argv + 2);\n \n \tif (!strcmp(argv[1], \"start-command-ENOENT\")) {\n \t\tif (start_command(&proc) < 0 && errno == ENOENT)\n@@ -408,7 +408,8 @@ int cmd__run_command(int argc, const char **argv)\n \t\texit(run_command(&proc));\n \n \tjobs = atoi(argv[2]);\n-\tproc.argv = (const char **)argv + 3;\n+\tstrvec_clear(&proc.args);\n+\tstrvec_pushv(&proc.args, (const char **)argv + 3);\n \n \tif (!strcmp(argv[1], \"run-command-parallel\"))\n \t\texit(run_processes_parallel(jobs, parallel_next,\n-- \n2.34.1.838.g779e9098efb\n\n"},{"id":"442335","messageId":"patch-v3-6.9-7bdf963ed04-20211125T224833Z-avarab@gmail.com","threadId":"56951","inReplyTo":"cover-v3-0.9-00000000000-20211125T224833Z-avarab@gmail.com","subject":"[PATCH v3 6/9] run-command API users: use strvec_push(), not argv construction","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-25T22:52:21Z","receivedAt":"2021-11-25T22:56:34Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Change a pattern of hardcoding an \"argv\" array size, populating it and\nassigning to the \"argv\" member of \"struct child_process\" to instead\nuse \"strvec_push()\" to add data to the \"args\" member.\n\nAs noted in the preceding commit this moves us further towards being\nable to remove the \"argv\" member in a subsequent commit\n\nThese callers could have used strvec_pushl(), but moving to\nstrvec_push() makes the diff easier to read, and keeps the arguments\naligned as before.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n archive-tar.c          |  9 +++------\n builtin/receive-pack.c | 31 ++++++++++++-------------------\n daemon.c               | 18 +++++++-----------\n diff.c                 |  8 ++------\n prompt.c               |  7 ++-----\n transport.c            | 11 +++++------\n 6 files changed, 31 insertions(+), 53 deletions(-)\n\ndiff --git a/archive-tar.c b/archive-tar.c\nindex 05d2455870d..3c74db17468 100644\n--- a/archive-tar.c\n+++ b/archive-tar.c\n@@ -430,7 +430,6 @@ static int write_tar_filter_archive(const struct archiver *ar,\n {\n \tstruct strbuf cmd = STRBUF_INIT;\n \tstruct child_process filter = CHILD_PROCESS_INIT;\n-\tconst char *argv[2];\n \tint r;\n \n \tif (!ar->data)\n@@ -440,14 +439,12 @@ static int write_tar_filter_archive(const struct archiver *ar,\n \tif (args->compression_level >= 0)\n \t\tstrbuf_addf(&cmd, \" -%d\", args->compression_level);\n \n-\targv[0] = cmd.buf;\n-\targv[1] = NULL;\n-\tfilter.argv = argv;\n+\tstrvec_push(&filter.args, cmd.buf);\n \tfilter.use_shell = 1;\n \tfilter.in = -1;\n \n \tif (start_command(&filter) < 0)\n-\t\tdie_errno(_(\"unable to start '%s' filter\"), argv[0]);\n+\t\tdie_errno(_(\"unable to start '%s' filter\"), cmd.buf);\n \tclose(1);\n \tif (dup2(filter.in, 1) < 0)\n \t\tdie_errno(_(\"unable to redirect descriptor\"));\n@@ -457,7 +454,7 @@ static int write_tar_filter_archive(const struct archiver *ar,\n \n \tclose(1);\n \tif (finish_command(&filter) != 0)\n-\t\tdie(_(\"'%s' filter reported error\"), argv[0]);\n+\t\tdie(_(\"'%s' filter reported error\"), cmd.buf);\n \n \tstrbuf_release(&cmd);\n \treturn r;\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 6149d507965..48c99c8ee45 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -812,16 +812,13 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,\n {\n \tstruct child_process proc = CHILD_PROCESS_INIT;\n \tstruct async muxer;\n-\tconst char *argv[2];\n \tint code;\n+\tconst char *hook_path = find_hook(hook_name);\n \n-\targv[0] = find_hook(hook_name);\n-\tif (!argv[0])\n+\tif (!hook_path)\n \t\treturn 0;\n \n-\targv[1] = NULL;\n-\n-\tproc.argv = argv;\n+\tstrvec_push(&proc.args, hook_path);\n \tproc.in = -1;\n \tproc.stdout_to_stderr = 1;\n \tproc.trace2_hook_name = hook_name;\n@@ -943,23 +940,21 @@ static int run_receive_hook(struct command *commands,\n \n static int run_update_hook(struct command *cmd)\n {\n-\tconst char *argv[5];\n \tstruct child_process proc = CHILD_PROCESS_INIT;\n \tint code;\n+\tconst char *hook_path = find_hook(\"update\");\n \n-\targv[0] = find_hook(\"update\");\n-\tif (!argv[0])\n+\tif (!hook_path)\n \t\treturn 0;\n \n-\targv[1] = cmd->ref_name;\n-\targv[2] = oid_to_hex(&cmd->old_oid);\n-\targv[3] = oid_to_hex(&cmd->new_oid);\n-\targv[4] = NULL;\n+\tstrvec_push(&proc.args, hook_path);\n+\tstrvec_push(&proc.args, cmd->ref_name);\n+\tstrvec_push(&proc.args, oid_to_hex(&cmd->old_oid));\n+\tstrvec_push(&proc.args, oid_to_hex(&cmd->new_oid));\n \n \tproc.no_stdin = 1;\n \tproc.stdout_to_stderr = 1;\n \tproc.err = use_sideband ? -1 : 0;\n-\tproc.argv = argv;\n \tproc.trace2_hook_name = \"update\";\n \n \tcode = start_command(&proc);\n@@ -1117,22 +1112,20 @@ static int run_proc_receive_hook(struct command *commands,\n \tstruct child_process proc = CHILD_PROCESS_INIT;\n \tstruct async muxer;\n \tstruct command *cmd;\n-\tconst char *argv[2];\n \tstruct packet_reader reader;\n \tstruct strbuf cap = STRBUF_INIT;\n \tstruct strbuf errmsg = STRBUF_INIT;\n \tint hook_use_push_options = 0;\n \tint version = 0;\n \tint code;\n+\tconst char *hook_path = find_hook(\"proc-receive\");\n \n-\targv[0] = find_hook(\"proc-receive\");\n-\tif (!argv[0]) {\n+\tif (!hook_path) {\n \t\trp_error(\"cannot find hook 'proc-receive'\");\n \t\treturn -1;\n \t}\n-\targv[1] = NULL;\n \n-\tproc.argv = argv;\n+\tstrvec_push(&proc.args, hook_path);\n \tproc.in = -1;\n \tproc.out = -1;\n \tproc.trace2_hook_name = \"proc-receive\";\ndiff --git a/daemon.c b/daemon.c\nindex 8df21f2130c..4a000ee4afa 100644\n--- a/daemon.c\n+++ b/daemon.c\n@@ -326,22 +326,18 @@ static int run_access_hook(struct daemon_service *service, const char *dir,\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n-\tconst char *argv[8];\n-\tconst char **arg = argv;\n \tchar *eol;\n \tint seen_errors = 0;\n \n-\t*arg++ = access_hook;\n-\t*arg++ = service->name;\n-\t*arg++ = path;\n-\t*arg++ = hi->hostname.buf;\n-\t*arg++ = get_canon_hostname(hi);\n-\t*arg++ = get_ip_address(hi);\n-\t*arg++ = hi->tcp_port.buf;\n-\t*arg = NULL;\n+\tstrvec_push(&child.args, access_hook);\n+\tstrvec_push(&child.args, service->name);\n+\tstrvec_push(&child.args, path);\n+\tstrvec_push(&child.args, hi->hostname.buf);\n+\tstrvec_push(&child.args, get_canon_hostname(hi));\n+\tstrvec_push(&child.args, get_ip_address(hi));\n+\tstrvec_push(&child.args, hi->tcp_port.buf);\n \n \tchild.use_shell = 1;\n-\tchild.argv = argv;\n \tchild.no_stdin = 1;\n \tchild.no_stderr = 1;\n \tchild.out = -1;\ndiff --git a/diff.c b/diff.c\nindex 861282db1c3..41076857428 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -6921,19 +6921,15 @@ static char *run_textconv(struct repository *r,\n \t\t\t  size_t *outsize)\n {\n \tstruct diff_tempfile *temp;\n-\tconst char *argv[3];\n-\tconst char **arg = argv;\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n \tint err = 0;\n \n \ttemp = prepare_temp_file(r, spec->path, spec);\n-\t*arg++ = pgm;\n-\t*arg++ = temp->name;\n-\t*arg = NULL;\n+\tstrvec_push(&child.args, pgm);\n+\tstrvec_push(&child.args, temp->name);\n \n \tchild.use_shell = 1;\n-\tchild.argv = argv;\n \tchild.out = -1;\n \tif (start_command(&child)) {\n \t\tremove_tempfile();\ndiff --git a/prompt.c b/prompt.c\nindex 5ded21a017f..50df17279d1 100644\n--- a/prompt.c\n+++ b/prompt.c\n@@ -8,15 +8,12 @@\n static char *do_askpass(const char *cmd, const char *prompt)\n {\n \tstruct child_process pass = CHILD_PROCESS_INIT;\n-\tconst char *args[3];\n \tstatic struct strbuf buffer = STRBUF_INIT;\n \tint err = 0;\n \n-\targs[0] = cmd;\n-\targs[1]\t= prompt;\n-\targs[2] = NULL;\n+\tstrvec_push(&pass.args, cmd);\n+\tstrvec_push(&pass.args, prompt);\n \n-\tpass.argv = args;\n \tpass.out = -1;\n \n \tif (start_command(&pass))\ndiff --git a/transport.c b/transport.c\nindex e4f1decae20..92ab9a3fa6b 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1204,16 +1204,15 @@ static int run_pre_push_hook(struct transport *transport,\n \tstruct ref *r;\n \tstruct child_process proc = CHILD_PROCESS_INIT;\n \tstruct strbuf buf;\n-\tconst char *argv[4];\n+\tconst char *hook_path = find_hook(\"pre-push\");\n \n-\tif (!(argv[0] = find_hook(\"pre-push\")))\n+\tif (!hook_path)\n \t\treturn 0;\n \n-\targv[1] = transport->remote->name;\n-\targv[2] = transport->url;\n-\targv[3] = NULL;\n+\tstrvec_push(&proc.args, hook_path);\n+\tstrvec_push(&proc.args, transport->remote->name);\n+\tstrvec_push(&proc.args, transport->url);\n \n-\tproc.argv = argv;\n \tproc.in = -1;\n \tproc.trace2_hook_name = \"pre-push\";\n \n-- \n2.34.1.838.g779e9098efb\n\n"},{"id":"442336","messageId":"patch-v3-5.9-45803236764-20211125T224833Z-avarab@gmail.com","threadId":"56951","inReplyTo":"cover-v3-0.9-00000000000-20211125T224833Z-avarab@gmail.com","subject":"[PATCH v3 5/9] run-command API users: use strvec_pushl(), not argv construction","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-25T22:52:20Z","receivedAt":"2021-11-25T22:56:34Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Change a pattern of hardcoding an \"argv\" array size, populating it and\nassigning to the \"argv\" member of \"struct child_process\" to instead\nuse \"strvec_pushl()\" to add data to the \"args\" member.\n\nThis implements the same behavior as before in fewer lines of code,\nand moves us further towards being able to remove the \"argv\" member in\na subsequent commit.\n\nSince we've entirely removed the \"argv\" variable(s) we can be sure\nthat no potential logic errors of the type discussed in a preceding\ncommit are being introduced here, i.e. ones where the local \"argv\" was\nbeing modified after the assignment to \"struct child_process\"'s\n\"argv\".\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/add.c          |  7 ++-----\n builtin/fsck.c         | 12 ++++--------\n builtin/help.c         |  3 +--\n builtin/merge.c        |  3 +--\n builtin/notes.c        |  5 ++---\n builtin/receive-pack.c | 38 +++++++++++++-------------------------\n builtin/replace.c      |  3 +--\n editor.c               |  4 +---\n sequencer.c            | 10 +++-------\n upload-pack.c          |  5 +----\n 10 files changed, 29 insertions(+), 61 deletions(-)\n\ndiff --git a/builtin/add.c b/builtin/add.c\nindex ef6b619c45e..a010b2c325f 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -302,15 +302,11 @@ int interactive_add(const char **argv, const char *prefix, int patch)\n static int edit_patch(int argc, const char **argv, const char *prefix)\n {\n \tchar *file = git_pathdup(\"ADD_EDIT.patch\");\n-\tconst char *apply_argv[] = { \"apply\", \"--recount\", \"--cached\",\n-\t\tNULL, NULL };\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \tstruct rev_info rev;\n \tint out;\n \tstruct stat st;\n \n-\tapply_argv[3] = file;\n-\n \tgit_config(git_diff_basic_config, NULL); /* no \"diff\" UI options */\n \n \tif (read_cache() < 0)\n@@ -338,7 +334,8 @@ static int edit_patch(int argc, const char **argv, const char *prefix)\n \t\tdie(_(\"Empty patch. Aborted.\"));\n \n \tchild.git_cmd = 1;\n-\tchild.argv = apply_argv;\n+\tstrvec_pushl(&child.args, \"apply\", \"--recount\", \"--cached\", file,\n+\t\t     NULL);\n \tif (run_command(&child))\n \t\tdie(_(\"Could not apply '%s'\"), file);\n \ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 27b9e78094d..9e54892311d 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -944,15 +944,13 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)\n \n \tif (the_repository->settings.core_commit_graph) {\n \t\tstruct child_process commit_graph_verify = CHILD_PROCESS_INIT;\n-\t\tconst char *verify_argv[] = { \"commit-graph\", \"verify\", NULL, NULL, NULL };\n \n \t\tprepare_alt_odb(the_repository);\n \t\tfor (odb = the_repository->objects->odb; odb; odb = odb->next) {\n \t\t\tchild_process_init(&commit_graph_verify);\n-\t\t\tcommit_graph_verify.argv = verify_argv;\n \t\t\tcommit_graph_verify.git_cmd = 1;\n-\t\t\tverify_argv[2] = \"--object-dir\";\n-\t\t\tverify_argv[3] = odb->path;\n+\t\t\tstrvec_pushl(&commit_graph_verify.args, \"commit-graph\",\n+\t\t\t\t     \"verify\", \"--object-dir\", odb->path, NULL);\n \t\t\tif (run_command(&commit_graph_verify))\n \t\t\t\terrors_found |= ERROR_COMMIT_GRAPH;\n \t\t}\n@@ -960,15 +958,13 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)\n \n \tif (the_repository->settings.core_multi_pack_index) {\n \t\tstruct child_process midx_verify = CHILD_PROCESS_INIT;\n-\t\tconst char *midx_argv[] = { \"multi-pack-index\", \"verify\", NULL, NULL, NULL };\n \n \t\tprepare_alt_odb(the_repository);\n \t\tfor (odb = the_repository->objects->odb; odb; odb = odb->next) {\n \t\t\tchild_process_init(&midx_verify);\n-\t\t\tmidx_verify.argv = midx_argv;\n \t\t\tmidx_verify.git_cmd = 1;\n-\t\t\tmidx_argv[2] = \"--object-dir\";\n-\t\t\tmidx_argv[3] = odb->path;\n+\t\t\tstrvec_pushl(&midx_verify.args, \"multi-pack-index\",\n+\t\t\t\t     \"verify\", \"--object-dir\", odb->path, NULL);\n \t\t\tif (run_command(&midx_verify))\n \t\t\t\terrors_found |= ERROR_MULTI_PACK_INDEX;\n \t\t}\ndiff --git a/builtin/help.c b/builtin/help.c\nindex 75cd2fb407f..d387131dd83 100644\n--- a/builtin/help.c\n+++ b/builtin/help.c\n@@ -212,11 +212,10 @@ static int check_emacsclient_version(void)\n {\n \tstruct strbuf buffer = STRBUF_INIT;\n \tstruct child_process ec_process = CHILD_PROCESS_INIT;\n-\tconst char *argv_ec[] = { \"emacsclient\", \"--version\", NULL };\n \tint version;\n \n \t/* emacsclient prints its version number on stderr */\n-\tec_process.argv = argv_ec;\n+\tstrvec_pushl(&ec_process.args, \"emacsclient\", \"--version\", NULL);\n \tec_process.err = -1;\n \tec_process.stdout_to_stderr = 1;\n \tif (start_command(&ec_process))\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex ea3112e0c0b..5f0476b0b76 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -310,10 +310,9 @@ static int save_state(struct object_id *stash)\n \tint len;\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n \tstruct strbuf buffer = STRBUF_INIT;\n-\tconst char *argv[] = {\"stash\", \"create\", NULL};\n \tint rc = -1;\n \n-\tcp.argv = argv;\n+\tstrvec_pushl(&cp.args, \"stash\", \"create\", NULL);\n \tcp.out = -1;\n \tcp.git_cmd = 1;\n \ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 71c59583a17..85d1abad884 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -134,14 +134,13 @@ static void copy_obj_to_fd(int fd, const struct object_id *oid)\n \n static void write_commented_object(int fd, const struct object_id *object)\n {\n-\tconst char *show_args[5] =\n-\t\t{\"show\", \"--stat\", \"--no-notes\", oid_to_hex(object), NULL};\n \tstruct child_process show = CHILD_PROCESS_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct strbuf cbuf = STRBUF_INIT;\n \n \t/* Invoke \"git show --stat --no-notes $object\" */\n-\tshow.argv = show_args;\n+\tstrvec_pushl(&show.args, \"show\", \"--stat\", \"--no-notes\",\n+\t\t     oid_to_hex(object), NULL);\n \tshow.no_stdin = 1;\n \tshow.out = -1;\n \tshow.err = 0;\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 49b846d9605..6149d507965 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1370,22 +1370,10 @@ static const char *push_to_deploy(unsigned char *sha1,\n \t\t\t\t  struct strvec *env,\n \t\t\t\t  const char *work_tree)\n {\n-\tconst char *update_refresh[] = {\n-\t\t\"update-index\", \"-q\", \"--ignore-submodules\", \"--refresh\", NULL\n-\t};\n-\tconst char *diff_files[] = {\n-\t\t\"diff-files\", \"--quiet\", \"--ignore-submodules\", \"--\", NULL\n-\t};\n-\tconst char *diff_index[] = {\n-\t\t\"diff-index\", \"--quiet\", \"--cached\", \"--ignore-submodules\",\n-\t\tNULL, \"--\", NULL\n-\t};\n-\tconst char *read_tree[] = {\n-\t\t\"read-tree\", \"-u\", \"-m\", NULL, NULL\n-\t};\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \n-\tchild.argv = update_refresh;\n+\tstrvec_pushl(&child.args, \"update-index\", \"-q\", \"--ignore-submodules\",\n+\t\t     \"--refresh\", NULL);\n \tchild.env = env->v;\n \tchild.dir = work_tree;\n \tchild.no_stdin = 1;\n@@ -1396,7 +1384,8 @@ static const char *push_to_deploy(unsigned char *sha1,\n \n \t/* run_command() does not clean up completely; reinitialize */\n \tchild_process_init(&child);\n-\tchild.argv = diff_files;\n+\tstrvec_pushl(&child.args, \"diff-files\", \"--quiet\",\n+\t\t     \"--ignore-submodules\", \"--\", NULL);\n \tchild.env = env->v;\n \tchild.dir = work_tree;\n \tchild.no_stdin = 1;\n@@ -1405,11 +1394,12 @@ static const char *push_to_deploy(unsigned char *sha1,\n \tif (run_command(&child))\n \t\treturn \"Working directory has unstaged changes\";\n \n-\t/* diff-index with either HEAD or an empty tree */\n-\tdiff_index[4] = head_has_history() ? \"HEAD\" : empty_tree_oid_hex();\n-\n \tchild_process_init(&child);\n-\tchild.argv = diff_index;\n+\tstrvec_pushl(&child.args, \"diff-index\", \"--quiet\", \"--cached\",\n+\t\t     \"--ignore-submodules\",\n+\t\t     /* diff-index with either HEAD or an empty tree */\n+\t\t     head_has_history() ? \"HEAD\" : empty_tree_oid_hex(),\n+\t\t     \"--\", NULL);\n \tchild.env = env->v;\n \tchild.no_stdin = 1;\n \tchild.no_stdout = 1;\n@@ -1418,9 +1408,9 @@ static const char *push_to_deploy(unsigned char *sha1,\n \tif (run_command(&child))\n \t\treturn \"Working directory has staged changes\";\n \n-\tread_tree[3] = hash_to_hex(sha1);\n \tchild_process_init(&child);\n-\tchild.argv = read_tree;\n+\tstrvec_pushl(&child.args, \"read-tree\", \"-u\", \"-m\", hash_to_hex(sha1),\n+\t\t     NULL);\n \tchild.env = env->v;\n \tchild.dir = work_tree;\n \tchild.no_stdin = 1;\n@@ -2575,16 +2565,14 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\trun_update_post_hook(commands);\n \t\tstring_list_clear(&push_options, 0);\n \t\tif (auto_gc) {\n-\t\t\tconst char *argv_gc_auto[] = {\n-\t\t\t\t\"gc\", \"--auto\", \"--quiet\", NULL,\n-\t\t\t};\n \t\t\tstruct child_process proc = CHILD_PROCESS_INIT;\n \n \t\t\tproc.no_stdin = 1;\n \t\t\tproc.stdout_to_stderr = 1;\n \t\t\tproc.err = use_sideband ? -1 : 0;\n \t\t\tproc.git_cmd = proc.close_object_store = 1;\n-\t\t\tproc.argv = argv_gc_auto;\n+\t\t\tstrvec_pushl(&proc.args, \"gc\", \"--auto\", \"--quiet\",\n+\t\t\t\t     NULL);\n \n \t\t\tif (!start_command(&proc)) {\n \t\t\t\tif (use_sideband)\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 946938d011e..6ff1734d587 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -258,11 +258,10 @@ static int import_object(struct object_id *oid, enum object_type type,\n \t\treturn error_errno(_(\"unable to open %s for reading\"), filename);\n \n \tif (!raw && type == OBJ_TREE) {\n-\t\tconst char *argv[] = { \"mktree\", NULL };\n \t\tstruct child_process cmd = CHILD_PROCESS_INIT;\n \t\tstruct strbuf result = STRBUF_INIT;\n \n-\t\tcmd.argv = argv;\n+\t\tstrvec_push(&cmd.args, \"mktree\");\n \t\tcmd.git_cmd = 1;\n \t\tcmd.in = fd;\n \t\tcmd.out = -1;\ndiff --git a/editor.c b/editor.c\nindex fdd3eeafa94..d92a8d9ab5b 100644\n--- a/editor.c\n+++ b/editor.c\n@@ -55,7 +55,6 @@ static int launch_specified_editor(const char *editor, const char *path,\n \n \tif (strcmp(editor, \":\")) {\n \t\tstruct strbuf realpath = STRBUF_INIT;\n-\t\tconst char *args[] = { editor, NULL, NULL };\n \t\tstruct child_process p = CHILD_PROCESS_INIT;\n \t\tint ret, sig;\n \t\tint print_waiting_for_editor = advice_enabled(ADVICE_WAITING_FOR_EDITOR) && isatty(2);\n@@ -77,9 +76,8 @@ static int launch_specified_editor(const char *editor, const char *path,\n \t\t}\n \n \t\tstrbuf_realpath(&realpath, path, 1);\n-\t\targs[1] = realpath.buf;\n \n-\t\tp.argv = args;\n+\t\tstrvec_pushl(&p.args, editor, realpath.buf, NULL);\n \t\tp.env = env;\n \t\tp.use_shell = 1;\n \t\tp.trace2_child_class = \"editor\";\ndiff --git a/sequencer.c b/sequencer.c\nindex ea96837cde3..6e02210db7a 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1164,18 +1164,14 @@ static int run_rewrite_hook(const struct object_id *oldoid,\n \t\t\t    const struct object_id *newoid)\n {\n \tstruct child_process proc = CHILD_PROCESS_INIT;\n-\tconst char *argv[3];\n \tint code;\n \tstruct strbuf sb = STRBUF_INIT;\n+\tconst char *hook_path = find_hook(\"post-rewrite\");\n \n-\targv[0] = find_hook(\"post-rewrite\");\n-\tif (!argv[0])\n+\tif (!hook_path)\n \t\treturn 0;\n \n-\targv[1] = \"amend\";\n-\targv[2] = NULL;\n-\n-\tproc.argv = argv;\n+\tstrvec_pushl(&proc.args, hook_path, \"amend\", NULL);\n \tproc.in = -1;\n \tproc.stdout_to_stderr = 1;\n \tproc.trace2_hook_name = \"post-rewrite\";\ndiff --git a/upload-pack.c b/upload-pack.c\nindex c78d55bc674..9b5db32623f 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -596,14 +596,11 @@ static int do_reachable_revlist(struct child_process *cmd,\n \t\t\t\tstruct object_array *reachable,\n \t\t\t\tenum allow_uor allow_uor)\n {\n-\tstatic const char *argv[] = {\n-\t\t\"rev-list\", \"--stdin\", NULL,\n-\t};\n \tstruct object *o;\n \tFILE *cmd_in = NULL;\n \tint i;\n \n-\tcmd->argv = argv;\n+\tstrvec_pushl(&cmd->args, \"rev-list\", \"--stdin\", NULL);\n \tcmd->git_cmd = 1;\n \tcmd->no_stderr = 1;\n \tcmd->in = -1;\n-- \n2.34.1.838.g779e9098efb\n\n"},{"id":"442337","messageId":"patch-v3-7.9-275535a447e-20211125T224833Z-avarab@gmail.com","threadId":"56951","inReplyTo":"cover-v3-0.9-00000000000-20211125T224833Z-avarab@gmail.com","subject":"[PATCH v3 7/9] run-command API: remove \"argv\" member, always use \"args\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-25T22:52:22Z","receivedAt":"2021-11-25T22:56:37Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Remove the \"argv\" member from the run-command API, ever since \"args\"\nwas added in c460c0ecdca (run-command: store an optional argv_array,\n2014-05-15) being able to provide either \"argv\" or \"args\" has led to\nsome confusion and bugs.\n\nIf we hadn't gone in that direction and only had an \"argv\" our\nproblems wouldn't have been solved either, as noted in [1] (and in the\ndocumentation amended here) it comes with inherent memory management\nissues: The caller would have to hang on to the \"argv\" until the\nrun-command API was finished. If the \"argv\" was an argument to main()\nthis wasn't an issue, but if it it was manually constructed using the\nAPI might be painful.\n\nWe also have a recent report[2] of a user of the API segfaulting,\nwhich is a direct result of it being complex to use. This commit\naddresses the root cause of that bug.\n\nThis change is larger than I'd like, but there's no easy way to avoid\nit that wouldn't involve even more verbose intermediate steps. We use\nthe \"argv\" as the source of truth over the \"args\", so we need to\nchange all parts of run-command.[ch] itself, as well as the trace2\nlogging at the same time.\n\nThe resulting Windows-specific code in start_command() is a bit nasty,\nas we're now assigning to a strvec's \"v\" member, instead of to our own\n\"argv\". There was a suggestion of some alternate approaches in reply\nto an earlier version of this commit[3], but let's leave larger a\nlarger and needless refactoring of this code for now.\n\n1. http://lore.kernel.org/git/YT6BnnXeAWn8BycF@coredump.intra.peff.net\n2. https://lore.kernel.org/git/20211120194048.12125-1-ematsumiya@suse.de/\n3. https://lore.kernel.org/git/patch-5.5-ea1011f7473-20211122T153605Z-avarab@gmail.com/\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n run-command.c           | 42 ++++++++++++++++++++---------------------\n run-command.h           | 20 ++++++++------------\n sub-process.c           |  2 +-\n trace2/tr2_tgt_event.c  |  2 +-\n trace2/tr2_tgt_normal.c |  2 +-\n trace2/tr2_tgt_perf.c   |  4 ++--\n 6 files changed, 33 insertions(+), 39 deletions(-)\n\ndiff --git a/run-command.c b/run-command.c\nindex 620a06ca2f5..99dc93e7300 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -380,7 +380,7 @@ static void child_err_spew(struct child_process *cmd, struct child_err *cerr)\n \tswitch (cerr->err) {\n \tcase CHILD_ERR_CHDIR:\n \t\terror_errno(\"exec '%s': cd to '%s' failed\",\n-\t\t\t    cmd->argv[0], cmd->dir);\n+\t\t\t    cmd->args.v[0], cmd->dir);\n \t\tbreak;\n \tcase CHILD_ERR_DUP2:\n \t\terror_errno(\"dup2() in child failed\");\n@@ -392,12 +392,12 @@ static void child_err_spew(struct child_process *cmd, struct child_err *cerr)\n \t\terror_errno(\"sigprocmask failed restoring signals\");\n \t\tbreak;\n \tcase CHILD_ERR_ENOENT:\n-\t\terror_errno(\"cannot run %s\", cmd->argv[0]);\n+\t\terror_errno(\"cannot run %s\", cmd->args.v[0]);\n \t\tbreak;\n \tcase CHILD_ERR_SILENT:\n \t\tbreak;\n \tcase CHILD_ERR_ERRNO:\n-\t\terror_errno(\"cannot exec '%s'\", cmd->argv[0]);\n+\t\terror_errno(\"cannot exec '%s'\", cmd->args.v[0]);\n \t\tbreak;\n \t}\n \tset_error_routine(old_errfn);\n@@ -405,7 +405,7 @@ static void child_err_spew(struct child_process *cmd, struct child_err *cerr)\n \n static int prepare_cmd(struct strvec *out, const struct child_process *cmd)\n {\n-\tif (!cmd->argv[0])\n+\tif (!cmd->args.v[0])\n \t\tBUG(\"command is empty\");\n \n \t/*\n@@ -415,11 +415,11 @@ static int prepare_cmd(struct strvec *out, const struct child_process *cmd)\n \tstrvec_push(out, SHELL_PATH);\n \n \tif (cmd->git_cmd) {\n-\t\tprepare_git_cmd(out, cmd->argv);\n+\t\tprepare_git_cmd(out, cmd->args.v);\n \t} else if (cmd->use_shell) {\n-\t\tprepare_shell_cmd(out, cmd->argv);\n+\t\tprepare_shell_cmd(out, cmd->args.v);\n \t} else {\n-\t\tstrvec_pushv(out, cmd->argv);\n+\t\tstrvec_pushv(out, cmd->args.v);\n \t}\n \n \t/*\n@@ -663,7 +663,7 @@ static void trace_run_command(const struct child_process *cp)\n \t\ttrace_add_env(&buf, cp->env);\n \tif (cp->git_cmd)\n \t\tstrbuf_addstr(&buf, \" git\");\n-\tsq_quote_argv_pretty(&buf, cp->argv);\n+\tsq_quote_argv_pretty(&buf, cp->args.v);\n \n \ttrace_printf(\"%s\", buf.buf);\n \tstrbuf_release(&buf);\n@@ -676,8 +676,6 @@ int start_command(struct child_process *cmd)\n \tint failed_errno;\n \tchar *str;\n \n-\tif (!cmd->argv)\n-\t\tcmd->argv = cmd->args.v;\n \tif (!cmd->env)\n \t\tcmd->env = cmd->env_array.v;\n \n@@ -729,7 +727,7 @@ int start_command(struct child_process *cmd)\n \t\t\tstr = \"standard error\";\n fail_pipe:\n \t\t\terror(\"cannot create %s pipe for %s: %s\",\n-\t\t\t\tstr, cmd->argv[0], strerror(failed_errno));\n+\t\t\t\tstr, cmd->args.v[0], strerror(failed_errno));\n \t\t\tchild_process_clear(cmd);\n \t\t\terrno = failed_errno;\n \t\t\treturn -1;\n@@ -758,7 +756,7 @@ int start_command(struct child_process *cmd)\n \t\tfailed_errno = errno;\n \t\tcmd->pid = -1;\n \t\tif (!cmd->silent_exec_failure)\n-\t\t\terror_errno(\"cannot run %s\", cmd->argv[0]);\n+\t\t\terror_errno(\"cannot run %s\", cmd->args.v[0]);\n \t\tgoto end_of_spawn;\n \t}\n \n@@ -868,7 +866,7 @@ int start_command(struct child_process *cmd)\n \t}\n \tatfork_parent(&as);\n \tif (cmd->pid < 0)\n-\t\terror_errno(\"cannot fork() for %s\", cmd->argv[0]);\n+\t\terror_errno(\"cannot fork() for %s\", cmd->args.v[0]);\n \telse if (cmd->clean_on_exit)\n \t\tmark_child_for_cleanup(cmd->pid, cmd);\n \n@@ -885,7 +883,7 @@ int start_command(struct child_process *cmd)\n \t\t * At this point we know that fork() succeeded, but exec()\n \t\t * failed. Errors have been reported to our stderr.\n \t\t */\n-\t\twait_or_whine(cmd->pid, cmd->argv[0], 0);\n+\t\twait_or_whine(cmd->pid, cmd->args.v[0], 0);\n \t\tchild_err_spew(cmd, &cerr);\n \t\tfailed_errno = errno;\n \t\tcmd->pid = -1;\n@@ -902,7 +900,7 @@ int start_command(struct child_process *cmd)\n #else\n {\n \tint fhin = 0, fhout = 1, fherr = 2;\n-\tconst char **sargv = cmd->argv;\n+\tconst char **sargv = cmd->args.v;\n \tstruct strvec nargv = STRVEC_INIT;\n \n \tif (cmd->no_stdin)\n@@ -929,20 +927,20 @@ int start_command(struct child_process *cmd)\n \t\tfhout = dup(cmd->out);\n \n \tif (cmd->git_cmd)\n-\t\tcmd->argv = prepare_git_cmd(&nargv, cmd->argv);\n+\t\tcmd->args.v = prepare_git_cmd(&nargv, sargv);\n \telse if (cmd->use_shell)\n-\t\tcmd->argv = prepare_shell_cmd(&nargv, cmd->argv);\n+\t\tcmd->args.v = prepare_shell_cmd(&nargv, sargv);\n \n-\tcmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, (char**) cmd->env,\n+\tcmd->pid = mingw_spawnvpe(cmd->args.v[0], cmd->args.v, (char**) cmd->env,\n \t\t\tcmd->dir, fhin, fhout, fherr);\n \tfailed_errno = errno;\n \tif (cmd->pid < 0 && (!cmd->silent_exec_failure || errno != ENOENT))\n-\t\terror_errno(\"cannot spawn %s\", cmd->argv[0]);\n+\t\terror_errno(\"cannot spawn %s\", cmd->args.v[0]);\n \tif (cmd->clean_on_exit && cmd->pid >= 0)\n \t\tmark_child_for_cleanup(cmd->pid, cmd);\n \n \tstrvec_clear(&nargv);\n-\tcmd->argv = sargv;\n+\tcmd->args.v = sargv;\n \tif (fhin != 0)\n \t\tclose(fhin);\n \tif (fhout != 1)\n@@ -992,7 +990,7 @@ int start_command(struct child_process *cmd)\n \n int finish_command(struct child_process *cmd)\n {\n-\tint ret = wait_or_whine(cmd->pid, cmd->argv[0], 0);\n+\tint ret = wait_or_whine(cmd->pid, cmd->args.v[0], 0);\n \ttrace2_child_exit(cmd, ret);\n \tchild_process_clear(cmd);\n \tinvalidate_lstat_cache();\n@@ -1001,7 +999,7 @@ int finish_command(struct child_process *cmd)\n \n int finish_command_in_signal(struct child_process *cmd)\n {\n-\tint ret = wait_or_whine(cmd->pid, cmd->argv[0], 1);\n+\tint ret = wait_or_whine(cmd->pid, cmd->args.v[0], 1);\n \ttrace2_child_exit(cmd, ret);\n \treturn ret;\n }\ndiff --git a/run-command.h b/run-command.h\nindex 49878262584..c0d1210cc63 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -44,21 +44,17 @@\n struct child_process {\n \n \t/**\n-\t * The .argv member is set up as an array of string pointers (NULL\n-\t * terminated), of which .argv[0] is the program name to run (usually\n-\t * without a path). If the command to run is a git command, set argv[0] to\n-\t * the command name without the 'git-' prefix and set .git_cmd = 1.\n+\t * The .args is a `struct strvec', use that API to manipulate\n+\t * it, e.g. strvec_pushv() to add an existing \"const char **\"\n+\t * vector.\n \t *\n-\t * Note that the ownership of the memory pointed to by .argv stays with the\n-\t * caller, but it should survive until `finish_command` completes. If the\n-\t * .argv member is NULL, `start_command` will point it at the .args\n-\t * `strvec` (so you may use one or the other, but you must use exactly\n-\t * one). The memory in .args will be cleaned up automatically during\n-\t * `finish_command` (or during `start_command` when it is unsuccessful).\n+\t * If the command to run is a git command, set the first\n+\t * element in the strvec to the command name without the\n+\t * 'git-' prefix and set .git_cmd = 1.\n \t *\n+\t * The memory in .args will be cleaned up automatically during\n+\t * `finish_command` (or during `start_command` when it is unsuccessful).\n \t */\n-\tconst char **argv;\n-\n \tstruct strvec args;\n \tstruct strvec env_array;\n \tpid_t pid;\ndiff --git a/sub-process.c b/sub-process.c\nindex dfa790d3ff9..cae56ae6b80 100644\n--- a/sub-process.c\n+++ b/sub-process.c\n@@ -187,7 +187,7 @@ static int handshake_capabilities(struct child_process *process,\n \t\t\t\t*supported_capabilities |= capabilities[i].flag;\n \t\t} else {\n \t\t\tdie(\"subprocess '%s' requested unsupported capability '%s'\",\n-\t\t\t    process->argv[0], p);\n+\t\t\t    process->args.v[0], p);\n \t\t}\n \t}\n \ndiff --git a/trace2/tr2_tgt_event.c b/trace2/tr2_tgt_event.c\nindex 3a0014417cc..bd17ecdc321 100644\n--- a/trace2/tr2_tgt_event.c\n+++ b/trace2/tr2_tgt_event.c\n@@ -354,7 +354,7 @@ static void fn_child_start_fl(const char *file, int line,\n \tjw_object_inline_begin_array(&jw, \"argv\");\n \tif (cmd->git_cmd)\n \t\tjw_array_string(&jw, \"git\");\n-\tjw_array_argv(&jw, cmd->argv);\n+\tjw_array_argv(&jw, cmd->args.v);\n \tjw_end(&jw);\n \tjw_end(&jw);\n \ndiff --git a/trace2/tr2_tgt_normal.c b/trace2/tr2_tgt_normal.c\nindex 58d9e430f05..6e429a3fb9e 100644\n--- a/trace2/tr2_tgt_normal.c\n+++ b/trace2/tr2_tgt_normal.c\n@@ -232,7 +232,7 @@ static void fn_child_start_fl(const char *file, int line,\n \tstrbuf_addch(&buf_payload, ' ');\n \tif (cmd->git_cmd)\n \t\tstrbuf_addstr(&buf_payload, \"git \");\n-\tsq_append_quote_argv_pretty(&buf_payload, cmd->argv);\n+\tsq_append_quote_argv_pretty(&buf_payload, cmd->args.v);\n \n \tnormal_io_write_fl(file, line, &buf_payload);\n \tstrbuf_release(&buf_payload);\ndiff --git a/trace2/tr2_tgt_perf.c b/trace2/tr2_tgt_perf.c\nindex e4acca13d64..2ff9cf70835 100644\n--- a/trace2/tr2_tgt_perf.c\n+++ b/trace2/tr2_tgt_perf.c\n@@ -335,10 +335,10 @@ static void fn_child_start_fl(const char *file, int line,\n \tstrbuf_addstr(&buf_payload, \" argv:[\");\n \tif (cmd->git_cmd) {\n \t\tstrbuf_addstr(&buf_payload, \"git\");\n-\t\tif (cmd->argv[0])\n+\t\tif (cmd->args.nr)\n \t\t\tstrbuf_addch(&buf_payload, ' ');\n \t}\n-\tsq_append_quote_argv_pretty(&buf_payload, cmd->argv);\n+\tsq_append_quote_argv_pretty(&buf_payload, cmd->args.v);\n \tstrbuf_addch(&buf_payload, ']');\n \n \tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n-- \n2.34.1.838.g779e9098efb\n\n"},{"id":"442338","messageId":"patch-v3-8.9-203f44a91f4-20211125T224833Z-avarab@gmail.com","threadId":"56951","inReplyTo":"cover-v3-0.9-00000000000-20211125T224833Z-avarab@gmail.com","subject":"[PATCH v3 8/9] difftool: use \"env_array\" to simplify memory management","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-25T22:52:23Z","receivedAt":"2021-11-25T22:56:39Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Amend code added in 03831ef7b50 (difftool: implement the functionality\nin the builtin, 2017-01-19) to use the \"env_array\" in the\nrun_command.[ch] API. Now we no longer need to manage our own\n\"index_env\" buffer.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/difftool.c | 14 ++++----------\n 1 file changed, 4 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nindex 4931c108451..4ee40fe3a06 100644\n--- a/builtin/difftool.c\n+++ b/builtin/difftool.c\n@@ -202,15 +202,10 @@ static void changed_files(struct hashmap *result, const char *index_path,\n {\n \tstruct child_process update_index = CHILD_PROCESS_INIT;\n \tstruct child_process diff_files = CHILD_PROCESS_INIT;\n-\tstruct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT;\n-\tconst char *git_dir = absolute_path(get_git_dir()), *env[] = {\n-\t\tNULL, NULL\n-\t};\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tconst char *git_dir = absolute_path(get_git_dir());\n \tFILE *fp;\n \n-\tstrbuf_addf(&index_env, \"GIT_INDEX_FILE=%s\", index_path);\n-\tenv[0] = index_env.buf;\n-\n \tstrvec_pushl(&update_index.args,\n \t\t     \"--git-dir\", git_dir, \"--work-tree\", workdir,\n \t\t     \"update-index\", \"--really-refresh\", \"-q\",\n@@ -222,7 +217,7 @@ static void changed_files(struct hashmap *result, const char *index_path,\n \tupdate_index.use_shell = 0;\n \tupdate_index.clean_on_exit = 1;\n \tupdate_index.dir = workdir;\n-\tupdate_index.env = env;\n+\tstrvec_pushf(&update_index.env_array, \"GIT_INDEX_FILE=%s\", index_path);\n \t/* Ignore any errors of update-index */\n \trun_command(&update_index);\n \n@@ -235,7 +230,7 @@ static void changed_files(struct hashmap *result, const char *index_path,\n \tdiff_files.clean_on_exit = 1;\n \tdiff_files.out = -1;\n \tdiff_files.dir = workdir;\n-\tdiff_files.env = env;\n+\tstrvec_pushf(&diff_files.env_array, \"GIT_INDEX_FILE=%s\", index_path);\n \tif (start_command(&diff_files))\n \t\tdie(\"could not obtain raw diff\");\n \tfp = xfdopen(diff_files.out, \"r\");\n@@ -248,7 +243,6 @@ static void changed_files(struct hashmap *result, const char *index_path,\n \tfclose(fp);\n \tif (finish_command(&diff_files))\n \t\tdie(\"diff-files did not exit properly\");\n-\tstrbuf_release(&index_env);\n \tstrbuf_release(&buf);\n }\n \n-- \n2.34.1.838.g779e9098efb\n\n"},{"id":"442339","messageId":"patch-v3-9.9-4e4db79ea53-20211125T224833Z-avarab@gmail.com","threadId":"56951","inReplyTo":"cover-v3-0.9-00000000000-20211125T224833Z-avarab@gmail.com","subject":"[PATCH v3 9/9] run-command API: remove \"env\" member, always use \"env_array\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-25T22:52:24Z","receivedAt":"2021-11-25T22:56:39Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Remove the \"env\" member from \"struct child_process\" in favor of always\nusing the \"env_array\". As with the preceding removal of \"argv\" in\nfavor of \"args\" this gets rid of current and future oddities around\nmemory management at the API boundary (see the amended API docs).\n\nFor some of the conversions we can replace patterns like:\n\n    child.env = env->v;\n\nWith:\n\n    strvec_pushv(&child.env_array, env->v);\n\nBut for others we need to guard the strvec_pushv() with a NULL check,\nsince we're not passing in the \"v\" member of a \"struct strvec\",\ne.g. in the case of tmp_objdir_env()'s return value.\n\nIdeally we'd rename the \"env_array\" member to simply \"env\" as a\nfollow-up, since it and \"args\" are now inconsistent in not having an\n\"_array\" suffix, and seemingly without any good reason, unless we look\nat the history of how they came to be.\n\nBut as we've currently got 122 in-tree hits for a \"git grep env_array\"\nlet's leave that for now (and possibly forever). Doing that rename\nwould be too disruptive.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/receive-pack.c | 11 ++++++-----\n builtin/worktree.c     |  6 +++---\n connected.c            |  3 ++-\n editor.c               |  4 +++-\n object-file.c          |  2 +-\n run-command.c          | 20 +++++++-------------\n run-command.h          | 34 +++++++++++++++++-----------------\n trailer.c              |  2 +-\n 8 files changed, 40 insertions(+), 42 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 48c99c8ee45..3979752ceca 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1367,7 +1367,7 @@ static const char *push_to_deploy(unsigned char *sha1,\n \n \tstrvec_pushl(&child.args, \"update-index\", \"-q\", \"--ignore-submodules\",\n \t\t     \"--refresh\", NULL);\n-\tchild.env = env->v;\n+\tstrvec_pushv(&child.env_array, env->v);\n \tchild.dir = work_tree;\n \tchild.no_stdin = 1;\n \tchild.stdout_to_stderr = 1;\n@@ -1379,7 +1379,7 @@ static const char *push_to_deploy(unsigned char *sha1,\n \tchild_process_init(&child);\n \tstrvec_pushl(&child.args, \"diff-files\", \"--quiet\",\n \t\t     \"--ignore-submodules\", \"--\", NULL);\n-\tchild.env = env->v;\n+\tstrvec_pushv(&child.env_array, env->v);\n \tchild.dir = work_tree;\n \tchild.no_stdin = 1;\n \tchild.stdout_to_stderr = 1;\n@@ -1393,7 +1393,7 @@ static const char *push_to_deploy(unsigned char *sha1,\n \t\t     /* diff-index with either HEAD or an empty tree */\n \t\t     head_has_history() ? \"HEAD\" : empty_tree_oid_hex(),\n \t\t     \"--\", NULL);\n-\tchild.env = env->v;\n+\tstrvec_pushv(&child.env_array, env->v);\n \tchild.no_stdin = 1;\n \tchild.no_stdout = 1;\n \tchild.stdout_to_stderr = 0;\n@@ -1404,7 +1404,7 @@ static const char *push_to_deploy(unsigned char *sha1,\n \tchild_process_init(&child);\n \tstrvec_pushl(&child.args, \"read-tree\", \"-u\", \"-m\", hash_to_hex(sha1),\n \t\t     NULL);\n-\tchild.env = env->v;\n+\tstrvec_pushv(&child.env_array, env->v);\n \tchild.dir = work_tree;\n \tchild.no_stdin = 1;\n \tchild.no_stdout = 1;\n@@ -2202,7 +2202,8 @@ static const char *unpack(int err_fd, struct shallow_info *si)\n \t\t\tclose(err_fd);\n \t\treturn \"unable to create temporary object directory\";\n \t}\n-\tchild.env = tmp_objdir_env(tmp_objdir);\n+\tif (tmp_objdir)\n+\t\tstrvec_pushv(&child.env_array, tmp_objdir_env(tmp_objdir));\n \n \t/*\n \t * Normally we just pass the tmp_objdir environment to the child\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 9edd3e2829b..962d71cf987 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -349,7 +349,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\t\tstrvec_push(&cp.args, \"--quiet\");\n \t}\n \n-\tcp.env = child_env.v;\n+\tstrvec_pushv(&cp.env_array, child_env.v);\n \tret = run_command(&cp);\n \tif (ret)\n \t\tgoto done;\n@@ -360,7 +360,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\tstrvec_pushl(&cp.args, \"reset\", \"--hard\", \"--no-recurse-submodules\", NULL);\n \t\tif (opts->quiet)\n \t\t\tstrvec_push(&cp.args, \"--quiet\");\n-\t\tcp.env = child_env.v;\n+\t\tstrvec_pushv(&cp.env_array, child_env.v);\n \t\tret = run_command(&cp);\n \t\tif (ret)\n \t\t\tgoto done;\n@@ -389,7 +389,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\t\tcp.no_stdin = 1;\n \t\t\tcp.stdout_to_stderr = 1;\n \t\t\tcp.dir = path;\n-\t\t\tcp.env = env;\n+\t\t\tstrvec_pushv(&cp.env_array, env);\n \t\t\tcp.trace2_hook_name = \"post-checkout\";\n \t\t\tstrvec_pushl(&cp.args, absolute_path(hook),\n \t\t\t\t     oid_to_hex(null_oid()),\ndiff --git a/connected.c b/connected.c\nindex 35bd4a26382..ed3025e7a2a 100644\n--- a/connected.c\n+++ b/connected.c\n@@ -109,7 +109,8 @@ int check_connected(oid_iterate_fn fn, void *cb_data,\n \t\t\t     _(\"Checking connectivity\"));\n \n \trev_list.git_cmd = 1;\n-\trev_list.env = opt->env;\n+\tif (opt->env)\n+\t\tstrvec_pushv(&rev_list.env_array, opt->env);\n \trev_list.in = -1;\n \trev_list.no_stdout = 1;\n \tif (opt->err_fd)\ndiff --git a/editor.c b/editor.c\nindex d92a8d9ab5b..8b9648281d7 100644\n--- a/editor.c\n+++ b/editor.c\n@@ -1,6 +1,7 @@\n #include \"cache.h\"\n #include \"config.h\"\n #include \"strbuf.h\"\n+#include \"strvec.h\"\n #include \"run-command.h\"\n #include \"sigchain.h\"\n \n@@ -78,7 +79,8 @@ static int launch_specified_editor(const char *editor, const char *path,\n \t\tstrbuf_realpath(&realpath, path, 1);\n \n \t\tstrvec_pushl(&p.args, editor, realpath.buf, NULL);\n-\t\tp.env = env;\n+\t\tif (env)\n+\t\t\tstrvec_pushv(&p.env_array, (const char **)env);\n \t\tp.use_shell = 1;\n \t\tp.trace2_child_class = \"editor\";\n \t\tif (start_command(&p) < 0) {\ndiff --git a/object-file.c b/object-file.c\nindex c3d866a287e..2fc1bed417c 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -797,7 +797,7 @@ static void fill_alternate_refs_command(struct child_process *cmd,\n \t\t}\n \t}\n \n-\tcmd->env = local_repo_env;\n+\tstrvec_pushv(&cmd->env_array, (const char **)local_repo_env);\n \tcmd->out = -1;\n }\n \ndiff --git a/run-command.c b/run-command.c\nindex 99dc93e7300..ebade086700 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -655,12 +655,7 @@ static void trace_run_command(const struct child_process *cp)\n \t\tsq_quote_buf_pretty(&buf, cp->dir);\n \t\tstrbuf_addch(&buf, ';');\n \t}\n-\t/*\n-\t * The caller is responsible for initializing cp->env from\n-\t * cp->env_array if needed. We only check one place.\n-\t */\n-\tif (cp->env)\n-\t\ttrace_add_env(&buf, cp->env);\n+\ttrace_add_env(&buf, cp->env_array.v);\n \tif (cp->git_cmd)\n \t\tstrbuf_addstr(&buf, \" git\");\n \tsq_quote_argv_pretty(&buf, cp->args.v);\n@@ -676,9 +671,6 @@ int start_command(struct child_process *cmd)\n \tint failed_errno;\n \tchar *str;\n \n-\tif (!cmd->env)\n-\t\tcmd->env = cmd->env_array.v;\n-\n \t/*\n \t * In case of errors we must keep the promise to close FDs\n \t * that have been passed in via ->in and ->out.\n@@ -768,7 +760,7 @@ int start_command(struct child_process *cmd)\n \t\tset_cloexec(null_fd);\n \t}\n \n-\tchildenv = prep_childenv(cmd->env);\n+\tchildenv = prep_childenv(cmd->env_array.v);\n \tatfork_prepare(&as);\n \n \t/*\n@@ -931,7 +923,7 @@ int start_command(struct child_process *cmd)\n \telse if (cmd->use_shell)\n \t\tcmd->args.v = prepare_shell_cmd(&nargv, sargv);\n \n-\tcmd->pid = mingw_spawnvpe(cmd->args.v[0], cmd->args.v, (char**) cmd->env,\n+\tcmd->pid = mingw_spawnvpe(cmd->args.v[0], cmd->args.v, (char**) cmd->env_array.v,\n \t\t\tcmd->dir, fhin, fhout, fherr);\n \tfailed_errno = errno;\n \tif (cmd->pid < 0 && (!cmd->silent_exec_failure || errno != ENOENT))\n@@ -1047,7 +1039,8 @@ int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,\n \tcmd.wait_after_clean = opt & RUN_WAIT_AFTER_CLEAN ? 1 : 0;\n \tcmd.close_object_store = opt & RUN_CLOSE_OBJECT_STORE ? 1 : 0;\n \tcmd.dir = dir;\n-\tcmd.env = env;\n+\tif (env)\n+\t\tstrvec_pushv(&cmd.env_array, (const char **)env);\n \tcmd.trace2_child_class = tr2_class;\n \treturn run_command(&cmd);\n }\n@@ -1333,7 +1326,8 @@ int run_hook_ve(const char *const *env, const char *name, va_list args)\n \tstrvec_push(&hook.args, p);\n \twhile ((p = va_arg(args, const char *)))\n \t\tstrvec_push(&hook.args, p);\n-\thook.env = env;\n+\tif (env)\n+\t\tstrvec_pushv(&hook.env_array, (const char **)env);\n \thook.no_stdin = 1;\n \thook.stdout_to_stderr = 1;\n \thook.trace2_hook_name = name;\ndiff --git a/run-command.h b/run-command.h\nindex c0d1210cc63..2be5f5d6422 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -56,6 +56,23 @@ struct child_process {\n \t * `finish_command` (or during `start_command` when it is unsuccessful).\n \t */\n \tstruct strvec args;\n+\n+\t/**\n+\t * Like .args the .env_array is a `struct strvec'.\n+\t *\n+\t * To modify the environment of the sub-process, specify an array of\n+\t * environment settings. Each string in the array manipulates the\n+\t * environment.\n+\t *\n+\t * - If the string is of the form \"VAR=value\", i.e. it contains '='\n+\t *   the variable is added to the child process's environment.\n+\t *\n+\t * - If the string does not contain '=', it names an environment\n+\t *   variable that will be removed from the child process's environment.\n+\t *\n+\t * The memory in .env_array will be cleaned up automatically during\n+\t * `finish_command` (or during `start_command` when it is unsuccessful).\n+\t */\n \tstruct strvec env_array;\n \tpid_t pid;\n \n@@ -92,23 +109,6 @@ struct child_process {\n \t */\n \tconst char *dir;\n \n-\t/**\n-\t * To modify the environment of the sub-process, specify an array of\n-\t * string pointers (NULL terminated) in .env:\n-\t *\n-\t * - If the string is of the form \"VAR=value\", i.e. it contains '='\n-\t *   the variable is added to the child process's environment.\n-\t *\n-\t * - If the string does not contain '=', it names an environment\n-\t *   variable that will be removed from the child process's environment.\n-\t *\n-\t * If the .env member is NULL, `start_command` will point it at the\n-\t * .env_array `strvec` (so you may use one or the other, but not both).\n-\t * The memory in .env_array will be cleaned up automatically during\n-\t * `finish_command` (or during `start_command` when it is unsuccessful).\n-\t */\n-\tconst char *const *env;\n-\n \tunsigned no_stdin:1;\n \tunsigned no_stdout:1;\n \tunsigned no_stderr:1;\ndiff --git a/trailer.c b/trailer.c\nindex 7c7cb61a945..1b12f77d945 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -236,7 +236,7 @@ static char *apply_command(struct conf_info *conf, const char *arg)\n \t\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n \t\tstrvec_push(&cp.args, cmd.buf);\n \t}\n-\tcp.env = local_repo_env;\n+\tstrvec_pushv(&cp.env_array, (const char **)local_repo_env);\n \tcp.no_stdin = 1;\n \tcp.use_shell = 1;\n \n-- \n2.34.1.838.g779e9098efb\n\n"},{"id":"442354","messageId":"CAPig+cRi6SeuV7k_+9JCcnf79daLZp5B=EyHK-KxC1VGN0B4ig@mail.gmail.com","threadId":"56951","inReplyTo":"patch-v3-1.9-1c3f9de33ad-20211125T224833Z-avarab@gmail.com","subject":"Re: [PATCH v3 1/9] worktree: stop being overly intimate with run_command() internals","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2021-11-26T09:48:15Z","receivedAt":"2021-11-26T09:50:27Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, Nov 25, 2021 at 5:52 PM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n> From: Eric Sunshine <sunshine@sunshineco.com>\n>\n> add_worktree() reuses a `child_process` for three run_command()\n> invocations, but to do so, it has overly-intimate knowledge of\n> run-command.c internals. In particular, it knows that it must reset\n> child_process::argv to NULL for each subsequent invocation[*] in order\n> for start_command() to latch the newly-populated child_process::args for\n> each invocation, even though this behavior is not a part of the\n> documented API. Beyond having overly-intimate knowledge of run-command.c\n> internals, the reuse of one `child_process` for three run_command()\n> invocations smells like an unnecessary micro-optimization. Therefore,\n> stop sharing one `child_process` and instead use a new one for each\n> run_command() call.\n\nIf people feel uncomfortable with the way this patch shadows `cp` in\nnested blocks, an alternative would be to call child_process_init(&cp)\nto reuse the existing `cp`, similar to the fix[1] applied to pager.c\nwhen reusing a `child_process`. I don't feel strongly about it either\nway.\n\n[1]: https://lore.kernel.org/git/20211125000239.2336-1-ematsumiya@suse.de/\n\n> [*] If child_process::argv is not reset to NULL, then subsequent\n> run_command() invocations will instead incorrectly access a dangling\n> pointer to freed memory which had been allocated by child_process::args\n> on the previous run. This is due to the following code in\n> start_command():\n>\n>     if (!cmd->argv)\n>         cmd->argv = cmd->args.v;\n>\n> Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> ---\n>  builtin/worktree.c | 7 +++----\n>  1 file changed, 3 insertions(+), 4 deletions(-)\n>\n> diff --git a/builtin/worktree.c b/builtin/worktree.c\n> index d22ece93e1a..9edd3e2829b 100644\n> --- a/builtin/worktree.c\n> +++ b/builtin/worktree.c\n> @@ -355,8 +355,8 @@ static int add_worktree(const char *path, const char *refname,\n>                 goto done;\n>\n>         if (opts->checkout) {\n> -               cp.argv = NULL;\n> -               strvec_clear(&cp.args);\n> +               struct child_process cp = CHILD_PROCESS_INIT;\n> +               cp.git_cmd = 1;\n>                 strvec_pushl(&cp.args, \"reset\", \"--hard\", \"--no-recurse-submodules\", NULL);\n>                 if (opts->quiet)\n>                         strvec_push(&cp.args, \"--quiet\");\n> @@ -385,12 +385,11 @@ static int add_worktree(const char *path, const char *refname,\n>                 const char *hook = find_hook(\"post-checkout\");\n>                 if (hook) {\n>                         const char *env[] = { \"GIT_DIR\", \"GIT_WORK_TREE\", NULL };\n> -                       cp.git_cmd = 0;\n> +                       struct child_process cp = CHILD_PROCESS_INIT;\n>                         cp.no_stdin = 1;\n>                         cp.stdout_to_stderr = 1;\n>                         cp.dir = path;\n>                         cp.env = env;\n> -                       cp.argv = NULL;\n>                         cp.trace2_hook_name = \"post-checkout\";\n>                         strvec_pushl(&cp.args, absolute_path(hook),\n>                                      oid_to_hex(null_oid()),\n> --\n> 2.34.1.838.g779e9098efb\n"}]}