From: Emily Shaffer <emilyshaffer@google.com>
To: "Ævar Arnfjörð Bjarmason" <avarab@gmail.com>
Cc: Phillip Wood <phillip.wood123@gmail.com>,
Anthony Sottile <asottile@umich.edu>,
Git Mailing List <git@vger.kernel.org>,
Junio C Hamano <gitster@pobox.com>
Subject: Re: git 2.36.0 regression: pre-commit hooks no longer have stdout/stderr as tty
Date: Wed, 20 Apr 2022 09:22:09 -0700 [thread overview]
Message-ID: <CAJoAoZ=ysz6GDjUVCzUC-4OEwkyfrUzDyAws1xPbKXeLufUa0w@mail.gmail.com> (raw)
In-Reply-To: <220420.86wnfk6isy.gmgdl@evledraar.gmail.com>
On Wed, Apr 20, 2022 at 5:28 AM Ævar Arnfjörð Bjarmason
<avarab@gmail.com> wrote:
> >> It *is* true that run-command.c:pp_start_one() sets child_process:err=-1
> >> for the child and run-command.c:run_hook_ve() didn't do that; that -1
> >> means that start_command() will create a new fd for the child's stderr.
> >> Since run_hook_ve() didn't care about the child's stderr before, I
> >> wonder if that is why? Could it be that now that we're processing the
> >> child's stderr, the child no longer thinks stderr is in tty, because the
> >> parent is consuming its output?
> >
> > Exactly, stderr is redirected to a pipe so that we can buffer the
> > output from each process and then write it to the real stdout when the
> > process has finished to avoid the output from different processes
> > getting mixed together. Ideally in this case we'd see that stdout is a
> > tty and create a pty rather than a pipe when buffering the output from
> > the process.
>
> All: I have a fix for this, currently CI-ing, testing etc. Basically it
> just adds an option to run_process_parallel() to stop doing the
> stdout/stderr interception.
>
> It means that for the current jobs=1 we'll behave as before.
>
> For jobs >1 in the future we'll need to decide what we want to do,
> i.e. you can have TTY, or guaranteed non-interleaved output, but not
> both.
>
> I'd think for hooks no interception makes sense, but in any case we can
> defer that until sometime later...
I'm curious what your reasoning is there. I rely on hooks which give
me user-readable output quite frequently, so the interleaving is
important to keep them from being useless if I trigger more than one
hook (e.g. I have separate hooks to check for secret keys and for
debug strings).
Would it make sense to start by setting it based on the number of
hooks available?
Left a quick thought below, but please don't consider it as a full
review - I haven't got time to look much more yet.
>
> Preview of the fix below, this is on top of an earlier change to add the
> "struct run_process_parallel_opts" to pass such options along:
>
> diff --git a/hook.c b/hook.c
> index eadb2d58a7b..1f20e5db447 100644
> --- a/hook.c
> +++ b/hook.c
> @@ -126,6 +126,7 @@ int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)
> struct run_process_parallel_opts run_opts = {
> .tr2_category = "hook",
> .tr2_label = hook_name,
> + .no_buffering = 1,
> };
>
> if (!options)
> diff --git a/run-command.c b/run-command.c
> index 2383375ee07..0f9d84433ad 100644
> --- a/run-command.c
> +++ b/run-command.c
> @@ -1604,7 +1604,7 @@ static void pp_cleanup(struct parallel_processes *pp)
> * <0 no new job was started, user wishes to shutdown early. Use negative code
> * to signal the children.
> */
> -static int pp_start_one(struct parallel_processes *pp)
> +static int pp_start_one(struct parallel_processes *pp, const int no_buffering)
> {
> int i, code;
>
> @@ -1623,9 +1623,12 @@ static int pp_start_one(struct parallel_processes *pp)
> strbuf_reset(&pp->children[i].err);
> return 1;
> }
> - pp->children[i].process.err = -1;
> - pp->children[i].process.stdout_to_stderr = 1;
> - pp->children[i].process.no_stdin = 1;
> +
> + if (!no_buffering) {
> + pp->children[i].process.err = -1;
> + pp->children[i].process.stdout_to_stderr = 1;
> + pp->children[i].process.no_stdin = 1;
> + }
Is it not possible to let run_processes_parallel() callers set these
flags manually (as they are providing a child_process in the "get next
task" callback), and then to decide whether to buffer the output based
on the fd status instead? I'd prefer that rather than an all-or-none
option that may not apply to every process, I think... But I could be
wrong :)
>
> if (start_command(&pp->children[i].process)) {
> code = pp->start_failure(&pp->children[i].err,
> @@ -1681,12 +1684,17 @@ static void pp_output(struct parallel_processes *pp)
> }
> }
>
> -static int pp_collect_finished(struct parallel_processes *pp)
> +static int pp_collect_finished(struct parallel_processes *pp,
> + const int no_buffering)
> {
> int i, code;
> int n = pp->max_processes;
> int result = 0;
>
> + if (no_buffering)
> + for (i = 0; i < pp->max_processes; i++)
> + pp->children[i].state = GIT_CP_WAIT_CLEANUP;
> +
> while (pp->nr_processes > 0) {
> for (i = 0; i < pp->max_processes; i++)
> if (pp->children[i].state == GIT_CP_WAIT_CLEANUP)
> @@ -1741,7 +1749,7 @@ static int pp_collect_finished(struct parallel_processes *pp)
> static int run_processes_parallel_1(int n, get_next_task_fn get_next_task,
> start_failure_fn start_failure,
> task_finished_fn task_finished,
> - void *pp_cb)
> + void *pp_cb, const int no_buffering)
> {
> int i, code;
> int output_timeout = 100;
> @@ -1754,7 +1762,7 @@ static int run_processes_parallel_1(int n, get_next_task_fn get_next_task,
> i < spawn_cap && !pp.shutdown &&
> pp.nr_processes < pp.max_processes;
> i++) {
> - code = pp_start_one(&pp);
> + code = pp_start_one(&pp, no_buffering);
> if (!code)
> continue;
> if (code < 0) {
> @@ -1765,9 +1773,11 @@ static int run_processes_parallel_1(int n, get_next_task_fn get_next_task,
> }
> if (!pp.nr_processes)
> break;
> - pp_buffer_stderr(&pp, output_timeout);
> - pp_output(&pp);
> - code = pp_collect_finished(&pp);
> + if (!no_buffering) {
> + pp_buffer_stderr(&pp, output_timeout);
> + pp_output(&pp);
> + }
> + code = pp_collect_finished(&pp, no_buffering);
> if (code) {
> pp.shutdown = 1;
> if (code < 0)
> @@ -1783,7 +1793,8 @@ static int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task,
> start_failure_fn start_failure,
> task_finished_fn task_finished,
> void *pp_cb, const char *tr2_category,
> - const char *tr2_label)
> + const char *tr2_label,
> + const int no_buffering)
> {
> int result;
>
> @@ -1791,7 +1802,7 @@ static int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task,
> ((n < 1) ? online_cpus() : n));
>
> result = run_processes_parallel_1(n, get_next_task, start_failure,
> - task_finished, pp_cb);
> + task_finished, pp_cb, no_buffering);
>
> trace2_region_leave(tr2_category, tr2_label, NULL);
>
> @@ -1803,6 +1814,8 @@ int run_processes_parallel(int n, get_next_task_fn get_next_task,
> task_finished_fn task_finished, void *pp_cb,
> struct run_process_parallel_opts *opts)
> {
> + const int no_buffering = opts && opts->no_buffering;
> +
> if (!opts)
> goto no_opts;
>
> @@ -1811,12 +1824,13 @@ int run_processes_parallel(int n, get_next_task_fn get_next_task,
> return run_processes_parallel_tr2(n, get_next_task,
> start_failure, task_finished,
> pp_cb, opts->tr2_category,
> - opts->tr2_label);
> + opts->tr2_label,
> + no_buffering);
> }
>
> no_opts:
> return run_processes_parallel_1(n, get_next_task, start_failure,
> - task_finished, pp_cb);
> + task_finished, pp_cb, no_buffering);
> }
>
>
> diff --git a/run-command.h b/run-command.h
> index 9ec57a25de4..062eff81e17 100644
> --- a/run-command.h
> +++ b/run-command.h
> @@ -463,11 +463,17 @@ typedef int (*task_finished_fn)(int result,
> *
> * tr2_category & tr2_label: sets the trace2 category and label for
> * logging. These must either be unset, or both of them must be set.
> + *
> + * no_buffering: Don't redirect stderr to stdout, and don't "buffer"
> + * the output of the N children started. The output will not be
> + * deterministic and may be interleaved, but we won't interfere with
> + * the connection to the TTY.
> */
> struct run_process_parallel_opts
> {
> const char *tr2_category;
> const char *tr2_label;
> + unsigned int no_buffering:1;
> };
>
> /**
> @@ -477,7 +483,8 @@ struct run_process_parallel_opts
> *
> * The children started via this function run in parallel. Their output
> * (both stdout and stderr) is routed to stderr in a manner that output
> - * from different tasks does not interleave.
> + * from different tasks does not interleave. This can be disabled by setting
> + * "no_buffering" in "struct run_process_parallel_opts".
> *
> * start_failure_fn and task_finished_fn can be NULL to omit any
> * special handling.
> diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh
> index ee281909bc3..fb6ad0bf4f7 100755
> --- a/t/t0061-run-command.sh
> +++ b/t/t0061-run-command.sh
> @@ -130,7 +130,7 @@ World
> EOF
>
> test_expect_success 'run_command runs in parallel with more jobs available than tasks' '
> - test-tool run-command run-command-parallel 5 sh -c "printf \"%s\n%s\n\" Hello World" 2>actual &&
> + test-tool run-command run-command-parallel 5 sh -c "printf \"%s\n%s\n\" Hello World" >actual 2>&1 &&
> test_cmp expect actual
> '
>
> diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh
> index 26ed5e11bc8..c0eda4e9237 100755
> --- a/t/t1800-hook.sh
> +++ b/t/t1800-hook.sh
> @@ -4,6 +4,7 @@ test_description='git-hook command'
>
> TEST_PASSES_SANITIZE_LEAK=true
> . ./test-lib.sh
> +. "$TEST_DIRECTORY"/lib-terminal.sh
>
> test_expect_success 'git hook usage' '
> test_expect_code 129 git hook &&
> @@ -120,4 +121,49 @@ test_expect_success 'git -c core.hooksPath=<PATH> hook run' '
> test_cmp expect actual
> '
>
> +test_expect_success TTY 'git hook run: stdout and stderr are connected to a TTY: STDOUT redirect' '
> + rm -rf .git &&
> + test_when_finished "rm -rf .git" &&
> + git init . &&
> +
> + test_hook pre-commit <<-EOF &&
> + {
> + test -t 1 && echo STDOUT TTY || echo STDOUT NO TTY &&
> + test -t 2 && echo STDERR TTY || echo STDERR NO TTY
> + } >actual
> + EOF
> +
> + test_commit A &&
> + test_commit B &&
> + git reset --soft HEAD^ &&
> + cat >expect <<-\EOF &&
> + STDOUT NO TTY
> + STDERR TTY
> + EOF
> + test_terminal git commit -m"msg" &&
> + test_cmp expect actual
> +'
> +
> +test_expect_success TTY 'git hook run: stdout and stderr are connected to a TTY: STDERR redirect' '
> + test_when_finished "rm -rf .git" &&
> + git init . &&
> +
> + test_hook pre-commit <<-EOF &&
> + {
> + test -t 1 && echo >&2 STDOUT TTY || echo >&2 STDOUT NO TTY &&
> + test -t 2 && echo >&2 STDERR TTY || echo >&2 STDERR NO TTY
> + } 2>actual
> + EOF
> +
> + test_commit A &&
> + test_commit B &&
> + git reset --soft HEAD^ &&
> + cat >expect <<-\EOF &&
> + STDOUT TTY
> + STDERR NO TTY
> + EOF
> + test_terminal git commit -m"msg" &&
> + test_cmp expect actual
> +'
> +
> test_done
next prev parent reply other threads:[~2022-04-20 16:22 UTC|newest]
Thread overview: 85+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-04-19 18:59 git 2.36.0 regression: pre-commit hooks no longer have stdout/stderr as tty Anthony Sottile
2022-04-19 23:37 ` Emily Shaffer
2022-04-19 23:52 ` Anthony Sottile
2022-04-20 9:00 ` Phillip Wood
2022-04-20 12:25 ` Ævar Arnfjörð Bjarmason
2022-04-20 16:22 ` Emily Shaffer [this message]
2022-04-20 16:42 ` Junio C Hamano
2022-04-20 17:09 ` Emily Shaffer
2022-04-20 17:25 ` Junio C Hamano
2022-04-20 17:41 ` Emily Shaffer
2022-04-21 12:03 ` Ævar Arnfjörð Bjarmason
2022-04-21 17:24 ` Junio C Hamano
2022-04-21 18:40 ` Junio C Hamano
2022-04-20 4:23 ` Junio C Hamano
2022-04-21 12:25 ` [PATCH 0/6] hook API: connect hooks to the TTY again, fixes a v2.36.0 regression Ævar Arnfjörð Bjarmason
2022-04-21 12:25 ` [PATCH 1/6] run-command API: replace run_processes_parallel_tr2() with opts struct Ævar Arnfjörð Bjarmason
2022-04-23 4:24 ` Junio C Hamano
2022-04-28 23:16 ` Emily Shaffer
2022-04-29 16:44 ` Junio C Hamano
2022-04-21 12:25 ` [PATCH 2/6] run-command tests: test stdout of run_command_parallel() Ævar Arnfjörð Bjarmason
2022-04-23 4:24 ` Junio C Hamano
2022-04-21 12:25 ` [PATCH 3/6] run-command: add an "ungroup" option to run_process_parallel() Ævar Arnfjörð Bjarmason
2022-04-23 3:54 ` Junio C Hamano
2022-04-28 23:26 ` Emily Shaffer
2022-04-21 12:25 ` [PATCH 4/6] hook tests: fix redirection logic error in 96e7225b310 Ævar Arnfjörð Bjarmason
2022-04-23 3:54 ` Junio C Hamano
2022-04-21 12:25 ` [PATCH 5/6] hook API: don't redundantly re-set "no_stdin" and "stdout_to_stderr" Ævar Arnfjörð Bjarmason
2022-04-29 22:54 ` Junio C Hamano
2022-04-21 12:25 ` [PATCH 6/6] hook API: fix v2.36.0 regression: hooks should be connected to a TTY Ævar Arnfjörð Bjarmason
2022-04-28 23:31 ` Emily Shaffer
2022-04-29 23:09 ` Junio C Hamano
2022-04-21 17:35 ` [PATCH 0/6] hook API: connect hooks to the TTY again, fixes a v2.36.0 regression Junio C Hamano
2022-04-21 18:50 ` Ævar Arnfjörð Bjarmason
2022-05-18 20:05 ` [PATCH v2 0/8] " Ævar Arnfjörð Bjarmason
2022-05-18 20:05 ` [PATCH v2 1/8] run-command tests: change if/if/... to if/else if/else Ævar Arnfjörð Bjarmason
2022-05-18 20:05 ` [PATCH v2 2/8] run-command API: use "opts" struct for run_processes_parallel{,_tr2}() Ævar Arnfjörð Bjarmason
2022-05-18 21:45 ` Junio C Hamano
2022-05-25 13:18 ` Emily Shaffer
2022-05-18 20:05 ` [PATCH v2 3/8] run-command tests: test stdout of run_command_parallel() Ævar Arnfjörð Bjarmason
2022-05-18 20:05 ` [PATCH v2 4/8] run-command.c: add an initializer for "struct parallel_processes" Ævar Arnfjörð Bjarmason
2022-05-18 20:05 ` [PATCH v2 5/8] run-command: add an "ungroup" option to run_process_parallel() Ævar Arnfjörð Bjarmason
2022-05-18 21:51 ` Junio C Hamano
2022-05-26 17:18 ` Emily Shaffer
2022-05-27 16:08 ` Junio C Hamano
2022-05-18 20:05 ` [PATCH v2 6/8] hook tests: fix redirection logic error in 96e7225b310 Ævar Arnfjörð Bjarmason
2022-05-18 20:05 ` [PATCH v2 7/8] hook API: don't redundantly re-set "no_stdin" and "stdout_to_stderr" Ævar Arnfjörð Bjarmason
2022-05-18 20:05 ` [PATCH v2 8/8] hook API: fix v2.36.0 regression: hooks should be connected to a TTY Ævar Arnfjörð Bjarmason
2022-05-18 21:53 ` Junio C Hamano
2022-05-26 17:23 ` Emily Shaffer
2022-05-26 18:23 ` Ævar Arnfjörð Bjarmason
2022-05-26 18:54 ` Emily Shaffer
2022-05-25 11:30 ` [PATCH v2 0/8] hook API: connect hooks to the TTY again, fixes a v2.36.0 regression Johannes Schindelin
2022-05-25 13:00 ` Ævar Arnfjörð Bjarmason
2022-05-25 16:57 ` Junio C Hamano
2022-05-26 1:10 ` Junio C Hamano
2022-05-26 10:16 ` Ævar Arnfjörð Bjarmason
2022-05-26 16:36 ` Junio C Hamano
2022-05-26 19:13 ` Ævar Arnfjörð Bjarmason
2022-05-26 19:56 ` Junio C Hamano
2022-05-27 9:14 ` [PATCH v3 0/2] " Ævar Arnfjörð Bjarmason
2022-05-27 9:14 ` [PATCH v3 1/2] run-command: add an "ungroup" option to run_process_parallel() Ævar Arnfjörð Bjarmason
2022-05-27 16:58 ` Junio C Hamano
2022-05-27 9:14 ` [PATCH v3 2/2] hook API: fix v2.36.0 regression: hooks should be connected to a TTY Ævar Arnfjörð Bjarmason
2022-05-27 17:17 ` [PATCH v3 0/2] hook API: connect hooks to the TTY again, fixes a v2.36.0 regression Junio C Hamano
2022-05-31 17:32 ` [PATCH v4 " Ævar Arnfjörð Bjarmason
2022-05-31 17:32 ` [PATCH v4 1/2] run-command: add an "ungroup" option to run_process_parallel() Ævar Arnfjörð Bjarmason
2022-06-01 16:49 ` Johannes Schindelin
2022-06-01 17:09 ` Junio C Hamano
2022-05-31 17:32 ` [PATCH v4 2/2] hook API: fix v2.36.0 regression: hooks should be connected to a TTY Ævar Arnfjörð Bjarmason
2022-06-01 16:50 ` Johannes Schindelin
2022-06-01 16:53 ` [PATCH v4 0/2] hook API: connect hooks to the TTY again, fixes a v2.36.0 regression Johannes Schindelin
2022-06-02 14:07 ` [PATCH v5 " Ævar Arnfjörð Bjarmason
2022-06-02 14:07 ` [PATCH v5 1/2] run-command: add an "ungroup" option to run_process_parallel() Ævar Arnfjörð Bjarmason
2022-06-02 14:07 ` [PATCH v5 2/2] hook API: fix v2.36.0 regression: hooks should be connected to a TTY Ævar Arnfjörð Bjarmason
2022-06-02 20:05 ` [PATCH v5 0/2] hook API: connect hooks to the TTY again, fixes a v2.36.0 regression Junio C Hamano
2022-06-03 8:51 ` Phillip Wood
2022-06-03 9:20 ` Ævar Arnfjörð Bjarmason
2022-06-03 13:21 ` Phillip Wood
2022-06-07 8:49 ` Ævar Arnfjörð Bjarmason
2022-06-07 8:48 ` [PATCH v6 " Ævar Arnfjörð Bjarmason
2022-06-07 8:48 ` [PATCH v6 1/2] run-command: add an "ungroup" option to run_process_parallel() Ævar Arnfjörð Bjarmason
2022-06-17 0:07 ` Emily Shaffer
2022-06-07 8:48 ` [PATCH v6 2/2] hook API: fix v2.36.0 regression: hooks should be connected to a TTY Ævar Arnfjörð Bjarmason
2022-06-07 17:08 ` Junio C Hamano
2022-06-07 17:02 ` [PATCH v6 0/2] hook API: connect hooks to the TTY again, fixes a v2.36.0 regression Junio C Hamano
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: http://vger.kernel.org/majordomo-info.html
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to='CAJoAoZ=ysz6GDjUVCzUC-4OEwkyfrUzDyAws1xPbKXeLufUa0w@mail.gmail.com' \
--to=emilyshaffer@google.com \
--cc=asottile@umich.edu \
--cc=avarab@gmail.com \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=phillip.wood123@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://80x24.org/mirrors/git.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).