Skip to content

Commit bdf49ba

Browse files
nasamuffingitster
authored andcommitted
run-command: allow capturing of collated output
Some callers, for example server-side hooks which wish to relay hook output to clients across a transport, want to capture what would normally print to stderr and do something else with it. Allow that via a callback. By calling the callback regardless of whether there's output available, we allow clients to send e.g. a keepalive if necessary. Because we expose a strbuf, not a fd or FILE*, there's no need to create a temporary pipe or similar - we can just skip the print to stderr and instead hand it to the caller. Signed-off-by: Emily Shaffer <emilyshaffer@google.com> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 865c762 commit bdf49ba

File tree

4 files changed

+64
-8
lines changed

4 files changed

+64
-8
lines changed

run-command.c

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1578,7 +1578,10 @@ static void pp_cleanup(struct parallel_processes *pp,
15781578
* When get_next_task added messages to the buffer in its last
15791579
* iteration, the buffered output is non empty.
15801580
*/
1581-
strbuf_write(&pp->buffered_output, stderr);
1581+
if (opts->consume_sideband)
1582+
opts->consume_sideband(&pp->buffered_output, opts->data);
1583+
else
1584+
strbuf_write(&pp->buffered_output, stderr);
15821585
strbuf_release(&pp->buffered_output);
15831586

15841587
sigchain_pop_common();
@@ -1717,13 +1720,17 @@ static void pp_buffer_stderr(struct parallel_processes *pp,
17171720
}
17181721
}
17191722

1720-
static void pp_output(const struct parallel_processes *pp)
1723+
static void pp_output(const struct parallel_processes *pp,
1724+
const struct run_process_parallel_opts *opts)
17211725
{
17221726
size_t i = pp->output_owner;
17231727

17241728
if (pp->children[i].state == GIT_CP_WORKING &&
17251729
pp->children[i].err.len) {
1726-
strbuf_write(&pp->children[i].err, stderr);
1730+
if (opts->consume_sideband)
1731+
opts->consume_sideband(&pp->children[i].err, opts->data);
1732+
else
1733+
strbuf_write(&pp->children[i].err, stderr);
17271734
strbuf_reset(&pp->children[i].err);
17281735
}
17291736
}
@@ -1771,11 +1778,15 @@ static int pp_collect_finished(struct parallel_processes *pp,
17711778
} else {
17721779
const size_t n = opts->processes;
17731780

1774-
strbuf_write(&pp->children[i].err, stderr);
1781+
/* Output errors, then all other finished child processes */
1782+
if (opts->consume_sideband) {
1783+
opts->consume_sideband(&pp->children[i].err, opts->data);
1784+
opts->consume_sideband(&pp->buffered_output, opts->data);
1785+
} else {
1786+
strbuf_write(&pp->children[i].err, stderr);
1787+
strbuf_write(&pp->buffered_output, stderr);
1788+
}
17751789
strbuf_reset(&pp->children[i].err);
1776-
1777-
/* Output all other finished child processes */
1778-
strbuf_write(&pp->buffered_output, stderr);
17791790
strbuf_reset(&pp->buffered_output);
17801791

17811792
/*
@@ -1817,7 +1828,7 @@ static void pp_handle_child_IO(struct parallel_processes *pp,
18171828
}
18181829
} else {
18191830
pp_buffer_stderr(pp, opts, output_timeout);
1820-
pp_output(pp);
1831+
pp_output(pp, opts);
18211832
}
18221833
}
18231834

@@ -1840,6 +1851,9 @@ void run_processes_parallel(const struct run_process_parallel_opts *opts)
18401851
"max:%"PRIuMAX,
18411852
(uintmax_t)opts->processes);
18421853

1854+
if (opts->ungroup && opts->consume_sideband)
1855+
BUG("ungroup and reading sideband are mutualy exclusive");
1856+
18431857
/*
18441858
* Child tasks might receive input via stdin, terminating early (or not), so
18451859
* ignore the default SIGPIPE which gets handled by each feed_pipe_fn which

run-command.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,20 @@ typedef int (*feed_pipe_fn)(int child_in,
436436
void *pp_cb,
437437
void *pp_task_cb);
438438

439+
/**
440+
* If this callback is provided, instead of collating process output to stderr,
441+
* they will be collated into a new pipe. consume_sideband_fn will be called
442+
* repeatedly. When output is available on that pipe, it will be contained in
443+
* 'output'. But it will be called with an empty 'output' too, to allow for
444+
* keepalives or similar operations if necessary.
445+
*
446+
* pp_cb is the callback cookie as passed into run_processes_parallel.
447+
*
448+
* Since this callback is provided with the collated output, no task cookie is
449+
* provided.
450+
*/
451+
typedef void (*consume_sideband_fn)(struct strbuf *output, void *pp_cb);
452+
439453
/**
440454
* This callback is called on every child process that finished processing.
441455
*
@@ -495,6 +509,12 @@ struct run_process_parallel_opts
495509
*/
496510
feed_pipe_fn feed_pipe;
497511

512+
/*
513+
* consume_sideband: see consume_sideband_fn() above. This can be NULL
514+
* to omit any special handling.
515+
*/
516+
consume_sideband_fn consume_sideband;
517+
498518
/**
499519
* task_finished: See task_finished_fn() above. This can be
500520
* NULL to omit any special handling.

t/helper/test-run-command.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@ static int no_job(struct child_process *cp UNUSED,
5858
return 0;
5959
}
6060

61+
static void test_consume_sideband(struct strbuf *output, void *cb UNUSED)
62+
{
63+
FILE *sideband;
64+
65+
sideband = fopen("./sideband", "a");
66+
67+
strbuf_write(output, sideband);
68+
fclose(sideband);
69+
}
70+
6171
static int task_finished(int result UNUSED,
6272
struct strbuf *err,
6373
void *pp_cb UNUSED,
@@ -198,6 +208,7 @@ static int testsuite(int argc, const char **argv)
198208
.get_next_task = next_test,
199209
.start_failure = test_failed,
200210
.feed_pipe = test_stdin_pipe_feed,
211+
.consume_sideband = test_consume_sideband,
201212
.task_finished = test_finished,
202213
.data = &suite,
203214
};
@@ -514,6 +525,10 @@ int cmd__run_command(int argc, const char **argv)
514525
opts.get_next_task = parallel_next;
515526
opts.task_finished = task_finished_quiet;
516527
opts.feed_pipe = test_stdin_pipe_feed;
528+
} else if (!strcmp(argv[1], "run-command-sideband")) {
529+
opts.get_next_task = parallel_next;
530+
opts.consume_sideband = test_consume_sideband;
531+
opts.task_finished = task_finished_quiet;
517532
} else {
518533
ret = 1;
519534
fprintf(stderr, "check usage\n");

t/t0061-run-command.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,13 @@ test_expect_success 'run_command runs ungrouped in parallel with more tasks than
164164
test_line_count = 4 err
165165
'
166166

167+
test_expect_success 'run_command can divert output' '
168+
test_when_finished rm sideband &&
169+
test-tool run-command run-command-sideband 3 sh -c "printf \"%s\n%s\n\" Hello World" 2>actual &&
170+
test_must_be_empty actual &&
171+
test_cmp expect sideband
172+
'
173+
167174
test_expect_success 'run_command listens to stdin' '
168175
cat >expect <<-\EOF &&
169176
preloaded output of a child

0 commit comments

Comments
 (0)