Skip to content

Commit 840b450

Browse files
nasamuffingitster
authored andcommitted
receive-pack: convert receive hooks to hook API
This converts the last remaining hooks to the new hook API, for the same benefits as the previous conversions (no need to toggle signals, manage custom struct child_process, call find_hook(), prepares for specifyinig hooks via configs, etc.). I noticed a performance degradation when processing large amounts of hook input with just 1 line per callback, due to run-command's ppoll loop, therefore I batched 500 lines per callback, to ensure similar pipe throughput as before and to avoid hook child waiting on stdin. 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 5e5cc0e commit 840b450

File tree

1 file changed

+120
-119
lines changed

1 file changed

+120
-119
lines changed

builtin/receive-pack.c

Lines changed: 120 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -749,7 +749,7 @@ static int check_cert_push_options(const struct string_list *push_options)
749749
return retval;
750750
}
751751

752-
static void prepare_push_cert_sha1(struct child_process *proc)
752+
static void prepare_push_cert_sha1(struct run_hooks_opt *opt)
753753
{
754754
static int already_done;
755755

@@ -775,174 +775,175 @@ static void prepare_push_cert_sha1(struct child_process *proc)
775775
nonce_status = check_nonce(sigcheck.payload);
776776
}
777777
if (!is_null_oid(&push_cert_oid)) {
778-
strvec_pushf(&proc->env, "GIT_PUSH_CERT=%s",
778+
strvec_pushf(&opt->env, "GIT_PUSH_CERT=%s",
779779
oid_to_hex(&push_cert_oid));
780-
strvec_pushf(&proc->env, "GIT_PUSH_CERT_SIGNER=%s",
780+
strvec_pushf(&opt->env, "GIT_PUSH_CERT_SIGNER=%s",
781781
sigcheck.signer ? sigcheck.signer : "");
782-
strvec_pushf(&proc->env, "GIT_PUSH_CERT_KEY=%s",
782+
strvec_pushf(&opt->env, "GIT_PUSH_CERT_KEY=%s",
783783
sigcheck.key ? sigcheck.key : "");
784-
strvec_pushf(&proc->env, "GIT_PUSH_CERT_STATUS=%c",
784+
strvec_pushf(&opt->env, "GIT_PUSH_CERT_STATUS=%c",
785785
sigcheck.result);
786786
if (push_cert_nonce) {
787-
strvec_pushf(&proc->env,
787+
strvec_pushf(&opt->env,
788788
"GIT_PUSH_CERT_NONCE=%s",
789789
push_cert_nonce);
790-
strvec_pushf(&proc->env,
790+
strvec_pushf(&opt->env,
791791
"GIT_PUSH_CERT_NONCE_STATUS=%s",
792792
nonce_status);
793793
if (nonce_status == NONCE_SLOP)
794-
strvec_pushf(&proc->env,
794+
strvec_pushf(&opt->env,
795795
"GIT_PUSH_CERT_NONCE_SLOP=%ld",
796796
nonce_stamp_slop);
797797
}
798798
}
799799
}
800800

801+
struct receive_hook_feed_context {
802+
struct command *cmd;
803+
int skip_broken;
804+
};
805+
801806
struct receive_hook_feed_state {
802807
struct command *cmd;
803808
struct ref_push_report *report;
804809
int skip_broken;
805810
struct strbuf buf;
806-
const struct string_list *push_options;
807811
};
808812

809-
typedef int (*feed_fn)(void *, const char **, size_t *);
810-
static int run_and_feed_hook(const char *hook_name, feed_fn feed,
811-
struct receive_hook_feed_state *feed_state)
813+
static int feed_receive_hook(int hook_stdin_fd, struct receive_hook_feed_state *state, int lines_batch_size)
812814
{
813-
struct child_process proc = CHILD_PROCESS_INIT;
814-
struct async muxer;
815-
int code;
816-
const char *hook_path = find_hook(the_repository, hook_name);
815+
struct command *cmd = state->cmd;
817816

818-
if (!hook_path)
819-
return 0;
817+
strbuf_reset(&state->buf);
820818

821-
strvec_push(&proc.args, hook_path);
822-
proc.in = -1;
823-
proc.stdout_to_stderr = 1;
824-
proc.trace2_hook_name = hook_name;
825-
826-
if (feed_state->push_options) {
827-
size_t i;
828-
for (i = 0; i < feed_state->push_options->nr; i++)
829-
strvec_pushf(&proc.env,
830-
"GIT_PUSH_OPTION_%"PRIuMAX"=%s",
831-
(uintmax_t)i,
832-
feed_state->push_options->items[i].string);
833-
strvec_pushf(&proc.env, "GIT_PUSH_OPTION_COUNT=%"PRIuMAX"",
834-
(uintmax_t)feed_state->push_options->nr);
835-
} else
836-
strvec_pushf(&proc.env, "GIT_PUSH_OPTION_COUNT");
819+
/* batch lines to avoid going through run-command's ppoll for each line */
820+
for (int i = 0; i < lines_batch_size; i++) {
821+
while (cmd &&
822+
state->skip_broken && (cmd->error_string || cmd->did_not_exist))
823+
cmd = cmd->next;
837824

838-
if (tmp_objdir)
839-
strvec_pushv(&proc.env, tmp_objdir_env(tmp_objdir));
825+
if (!cmd)
826+
break; /* no more commands left */
840827

841-
if (use_sideband) {
842-
memset(&muxer, 0, sizeof(muxer));
843-
muxer.proc = copy_to_sideband;
844-
muxer.in = -1;
845-
code = start_async(&muxer);
846-
if (code)
847-
return code;
848-
proc.err = muxer.in;
849-
}
828+
if (!state->report)
829+
state->report = cmd->report;
850830

851-
prepare_push_cert_sha1(&proc);
831+
if (state->report) {
832+
struct object_id *old_oid;
833+
struct object_id *new_oid;
834+
const char *ref_name;
852835

853-
code = start_command(&proc);
854-
if (code) {
855-
if (use_sideband)
856-
finish_async(&muxer);
857-
return code;
858-
}
836+
old_oid = state->report->old_oid ? state->report->old_oid : &cmd->old_oid;
837+
new_oid = state->report->new_oid ? state->report->new_oid : &cmd->new_oid;
838+
ref_name = state->report->ref_name ? state->report->ref_name : cmd->ref_name;
859839

860-
sigchain_push(SIGPIPE, SIG_IGN);
840+
strbuf_addf(&state->buf, "%s %s %s\n",
841+
oid_to_hex(old_oid), oid_to_hex(new_oid),
842+
ref_name);
861843

862-
while (1) {
863-
const char *buf;
864-
size_t n;
865-
if (feed(feed_state, &buf, &n))
866-
break;
867-
if (write_in_full(proc.in, buf, n) < 0)
868-
break;
844+
state->report = state->report->next;
845+
if (!state->report)
846+
cmd = cmd->next;
847+
} else {
848+
strbuf_addf(&state->buf, "%s %s %s\n",
849+
oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),
850+
cmd->ref_name);
851+
cmd = cmd->next;
852+
}
869853
}
870-
close(proc.in);
871-
if (use_sideband)
872-
finish_async(&muxer);
873854

874-
sigchain_pop(SIGPIPE);
855+
state->cmd = cmd;
875856

876-
return finish_command(&proc);
857+
if (state->buf.len > 0) {
858+
int ret = write_in_full(hook_stdin_fd, state->buf.buf, state->buf.len);
859+
if (ret < 0) {
860+
if (errno == EPIPE)
861+
return 1; /* child closed pipe */
862+
return ret;
863+
}
864+
}
865+
866+
return state->cmd ? 0 : 1; /* 0 = more to come, 1 = EOF */
877867
}
878868

879-
static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
869+
static int feed_receive_hook_cb(int hook_stdin_fd, void *pp_cb, void *pp_task_cb UNUSED)
880870
{
881-
struct receive_hook_feed_state *state = state_;
882-
struct command *cmd = state->cmd;
871+
struct hook_cb_data *hook_cb = pp_cb;
872+
struct receive_hook_feed_state *feed_state = hook_cb->options->feed_pipe_cb_data;
883873

884-
while (cmd &&
885-
state->skip_broken && (cmd->error_string || cmd->did_not_exist))
886-
cmd = cmd->next;
887-
if (!cmd)
888-
return -1; /* EOF */
889-
if (!bufp)
890-
return 0; /* OK, can feed something. */
891-
strbuf_reset(&state->buf);
892-
if (!state->report)
893-
state->report = cmd->report;
894-
if (state->report) {
895-
struct object_id *old_oid;
896-
struct object_id *new_oid;
897-
const char *ref_name;
898-
899-
old_oid = state->report->old_oid ? state->report->old_oid : &cmd->old_oid;
900-
new_oid = state->report->new_oid ? state->report->new_oid : &cmd->new_oid;
901-
ref_name = state->report->ref_name ? state->report->ref_name : cmd->ref_name;
902-
strbuf_addf(&state->buf, "%s %s %s\n",
903-
oid_to_hex(old_oid), oid_to_hex(new_oid),
904-
ref_name);
905-
state->report = state->report->next;
906-
if (!state->report)
907-
state->cmd = cmd->next;
908-
} else {
909-
strbuf_addf(&state->buf, "%s %s %s\n",
910-
oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),
911-
cmd->ref_name);
912-
state->cmd = cmd->next;
913-
}
914-
if (bufp) {
915-
*bufp = state->buf.buf;
916-
*sizep = state->buf.len;
874+
/* first-time setup */
875+
if (!hook_cb->options->feed_pipe_cb_data) {
876+
struct receive_hook_feed_context *ctx = hook_cb->options->feed_pipe_ctx;
877+
if (!ctx)
878+
BUG("run_hooks_opt.feed_pipe_ctx required for receive hook");
879+
880+
hook_cb->options->feed_pipe_cb_data = xmalloc(sizeof(struct receive_hook_feed_state));
881+
feed_state = hook_cb->options->feed_pipe_cb_data;
882+
strbuf_init(&feed_state->buf, 0);
883+
feed_state->cmd = ctx->cmd;
884+
feed_state->skip_broken = ctx->skip_broken;
885+
feed_state->report = NULL;
917886
}
918-
return 0;
887+
888+
/* batch 500 lines at once to avoid going through the run-command ppoll loop too often */
889+
if (feed_receive_hook(hook_stdin_fd, feed_state, 500) == 0)
890+
return 0; /* still have more data to feed */
891+
892+
strbuf_release(&feed_state->buf);
893+
894+
if (hook_cb->options->feed_pipe_cb_data)
895+
FREE_AND_NULL(hook_cb->options->feed_pipe_cb_data);
896+
897+
return 1; /* done feeding, run-command can close pipe */
898+
}
899+
900+
static void hook_output_to_sideband(struct strbuf *output, void *cb_data UNUSED)
901+
{
902+
if (output && output->len)
903+
send_sideband(1, 2, output->buf, output->len, use_sideband);
919904
}
920905

921906
static int run_receive_hook(struct command *commands,
922907
const char *hook_name,
923908
int skip_broken,
924909
const struct string_list *push_options)
925910
{
926-
struct receive_hook_feed_state state;
927-
int status;
911+
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
912+
struct receive_hook_feed_context ctx;
913+
struct command *iter = commands;
928914

929-
strbuf_init(&state.buf, 0);
930-
state.cmd = commands;
931-
state.skip_broken = skip_broken;
932-
state.report = NULL;
933-
if (feed_receive_hook(&state, NULL, NULL))
915+
/* if there are no valid commands, don't invoke the hook at all. */
916+
while (iter && skip_broken && (iter->error_string || iter->did_not_exist))
917+
iter = iter->next;
918+
if (!iter)
934919
return 0;
935-
state.cmd = commands;
936-
state.push_options = push_options;
937-
status = run_and_feed_hook(hook_name, feed_receive_hook, &state);
938-
strbuf_release(&state.buf);
939-
return status;
940-
}
941920

942-
static void hook_output_to_sideband(struct strbuf *output, void *cb_data UNUSED)
943-
{
944-
if (output && output->len)
945-
send_sideband(1, 2, output->buf, output->len, use_sideband);
921+
if (push_options) {
922+
int i;
923+
for (i = 0; i < push_options->nr; i++)
924+
strvec_pushf(&opt.env, "GIT_PUSH_OPTION_%d=%s", i,
925+
push_options->items[i].string);
926+
strvec_pushf(&opt.env, "GIT_PUSH_OPTION_COUNT=%"PRIuMAX"",
927+
(uintmax_t)push_options->nr);
928+
} else
929+
strvec_push(&opt.env, "GIT_PUSH_OPTION_COUNT");
930+
931+
if (tmp_objdir)
932+
strvec_pushv(&opt.env, tmp_objdir_env(tmp_objdir));
933+
934+
prepare_push_cert_sha1(&opt);
935+
936+
/* set up sideband printer */
937+
if (use_sideband)
938+
opt.consume_sideband = hook_output_to_sideband;
939+
940+
/* set up stdin callback */
941+
ctx.cmd = commands;
942+
ctx.skip_broken = skip_broken;
943+
opt.feed_pipe = feed_receive_hook_cb;
944+
opt.feed_pipe_ctx = &ctx;
945+
946+
return run_hooks_opt(the_repository, hook_name, &opt);
946947
}
947948

948949
static int run_update_hook(struct command *cmd)

0 commit comments

Comments
 (0)