hook: add internal state alloc/free callbacks

Some hooks use opaque structs to keep internal state between callbacks. Because hooks ran sequentially (jobs == 1) with one command per hook, these internal states could be allocated on the stack for each hook run. Next commits add the ability to run multiple commands for each hook, so the states cannot be shared or stored on the stack anymore, especially since down the line we will also enable parallel execution (jobs > 1). Add alloc/free helpers for each hook, doing a "deep" alloc/init & free of their internal opaque struct. The alloc callback takes a context pointer, to initialize the struct at at the time of resource acquisition. These callbacks must always be provided together: no alloc without free and no free without alloc, otherwise a BUG() is triggered. Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Adrian Ratiu committed Feb 19, 2026 at 00:23 UTC ee2fbfd6b28fba20bc936ad1c2cb2617ba251025
5 files changed +102 -20
builtin/receive-pack.c
+26 -7
@@ -901,6 +901,26 @@ static int feed_receive_hook_cb(int hook_stdin_fd, void *pp_cb UNUSED, void *pp_
901 return state->cmd ? 0 : 1; /* 0 = more to come, 1 = EOF */
902 }
903
904 +static void *receive_hook_feed_state_alloc(void *feed_pipe_ctx)
905 +{
906 + struct receive_hook_feed_state *init_state = feed_pipe_ctx;
907 + struct receive_hook_feed_state *data = xcalloc(1, sizeof(*data));
908 + data->report = init_state->report;
909 + data->cmd = init_state->cmd;
910 + data->skip_broken = init_state->skip_broken;
911 + strbuf_init(&data->buf, 0);
912 + return data;
913 +}
914 +
915 +static void receive_hook_feed_state_free(void *data)
916 +{
917 + struct receive_hook_feed_state *d = data;
918 + if (!d)
919 + return;
920 + strbuf_release(&d->buf);
921 + free(d);
922 +}
923 +
924 static int run_receive_hook(struct command *commands,
925 const char *hook_name,
926 int skip_broken,
@@ -908,7 +928,7 @@ static int run_receive_hook(struct command *commands,
928 {
929 struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
930 struct command *iter = commands;
911 - struct receive_hook_feed_state feed_state;
931 + struct receive_hook_feed_state feed_init_state = { 0 };
932 struct async sideband_async;
933 int sideband_async_started = 0;
934 int saved_stderr = -1;
@@ -938,16 +958,15 @@ static int run_receive_hook(struct command *commands,
958 prepare_sideband_async(&sideband_async, &saved_stderr, &sideband_async_started);
959
960 /* set up stdin callback */
941 - feed_state.cmd = commands;
942 - feed_state.skip_broken = skip_broken;
943 - feed_state.report = NULL;
944 - strbuf_init(&feed_state.buf, 0);
945 - opt.feed_pipe_cb_data = &feed_state;
961 + feed_init_state.cmd = commands;
962 + feed_init_state.skip_broken = skip_broken;
963 + opt.feed_pipe_ctx = &feed_init_state;
964 opt.feed_pipe = feed_receive_hook_cb;
965 + opt.feed_pipe_cb_data_alloc = receive_hook_feed_state_alloc;
966 + opt.feed_pipe_cb_data_free = receive_hook_feed_state_free;
967
968 ret = run_hooks_opt(the_repository, hook_name, &opt);
969
950 - strbuf_release(&feed_state.buf);
970 finish_sideband_async(&sideband_async, saved_stderr, sideband_async_started);
971
972 return ret;
hook.c
+13
@@ -133,6 +133,8 @@ static int notify_hook_finished(int result,
133
134 static void run_hooks_opt_clear(struct run_hooks_opt *options)
135 {
136 + if (options->feed_pipe_cb_data_free)
137 + options->feed_pipe_cb_data_free(options->feed_pipe_cb_data);
138 strvec_clear(&options->env);
139 strvec_clear(&options->args);
140 }
@@ -172,6 +174,17 @@ int run_hooks_opt(struct repository *r, const char *hook_name,
174 if (!options->jobs)
175 BUG("run_hooks_opt must be called with options.jobs >= 1");
176
177 + /*
178 + * Ensure cb_data copy and free functions are either provided together,
179 + * or neither one is provided.
180 + */
181 + if ((options->feed_pipe_cb_data_alloc && !options->feed_pipe_cb_data_free) ||
182 + (!options->feed_pipe_cb_data_alloc && options->feed_pipe_cb_data_free))
183 + BUG("feed_pipe_cb_data_alloc and feed_pipe_cb_data_free must be set together");
184 +
185 + if (options->feed_pipe_cb_data_alloc)
186 + options->feed_pipe_cb_data = options->feed_pipe_cb_data_alloc(options->feed_pipe_ctx);
187 +
188 if (options->invoked_hook)
189 *options->invoked_hook = 0;
190
hook.h
+24 -1
@@ -5,6 +5,9 @@
5
6 struct repository;
7
8 +typedef void (*cb_data_free_fn)(void *data);
9 +typedef void *(*cb_data_alloc_fn)(void *init_ctx);
10 +
11 struct run_hooks_opt
12 {
13 /* Environment vars to be set for each hook */
@@ -88,10 +91,30 @@ struct run_hooks_opt
91 * It can be accessed directly via the third callback arg 'pp_task_cb':
92 * struct ... *state = pp_task_cb;
93 *
91 - * The caller is responsible for managing the memory for this data.
94 + * The caller is responsible for managing the memory for this data by
95 + * providing alloc/free callbacks to `run_hooks_opt`.
96 + *
97 * Only useful when using `run_hooks_opt.feed_pipe`, otherwise ignore it.
98 */
99 void *feed_pipe_cb_data;
100 +
101 + /**
102 + * Some hooks need to create a fresh `feed_pipe_cb_data` internal state,
103 + * so they can keep track of progress without affecting one another.
104 + *
105 + * If provided, this function will be called to alloc & initialize the
106 + * `feed_pipe_cb_data` for each hook.
107 + *
108 + * The `feed_pipe_ctx` pointer can be used to pass initialization data.
109 + */
110 + cb_data_alloc_fn feed_pipe_cb_data_alloc;
111 +
112 + /**
113 + * Called to free the memory initialized by `feed_pipe_cb_data_alloc`.
114 + *
115 + * Must always be provided when `feed_pipe_cb_data_alloc` is provided.
116 + */
117 + cb_data_free_fn feed_pipe_cb_data_free;
118 };
119
120 #define RUN_HOOKS_OPT_INIT { \
refs.c
+19 -5
@@ -2511,24 +2511,38 @@ static int transaction_hook_feed_stdin(int hook_stdin_fd, void *pp_cb, void *pp_
2511 return 0; /* no more input to feed */
2512 }
2513
2514 +static void *transaction_feed_cb_data_alloc(void *feed_pipe_ctx UNUSED)
2515 +{
2516 + struct transaction_feed_cb_data *data = xmalloc(sizeof(*data));
2517 + strbuf_init(&data->buf, 0);
2518 + data->index = 0;
2519 + return data;
2520 +}
2521 +
2522 +static void transaction_feed_cb_data_free(void *data)
2523 +{
2524 + struct transaction_feed_cb_data *d = data;
2525 + if (!d)
2526 + return;
2527 + strbuf_release(&d->buf);
2528 + free(d);
2529 +}
2530 +
2531 static int run_transaction_hook(struct ref_transaction *transaction,
2532 const char *state)
2533 {
2534 struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
2518 - struct transaction_feed_cb_data feed_ctx = { 0 };
2535 int ret = 0;
2536
2537 strvec_push(&opt.args, state);
2538
2539 opt.feed_pipe = transaction_hook_feed_stdin;
2540 opt.feed_pipe_ctx = transaction;
2525 - opt.feed_pipe_cb_data = &feed_ctx;
2526 -
2527 - strbuf_init(&feed_ctx.buf, 0);
2541 + opt.feed_pipe_cb_data_alloc = transaction_feed_cb_data_alloc;
2542 + opt.feed_pipe_cb_data_free = transaction_feed_cb_data_free;
2543
2544 ret = run_hooks_opt(transaction->ref_store->repo, "reference-transaction", &opt);
2545
2531 - strbuf_release(&feed_ctx.buf);
2546 return ret;
2547 }
2548
transport.c
+20 -7
@@ -1357,21 +1357,36 @@ static int pre_push_hook_feed_stdin(int hook_stdin_fd, void *pp_cb UNUSED, void
1357 return 0;
1358 }
1359
1360 +static void *pre_push_hook_data_alloc(void *feed_pipe_ctx)
1361 +{
1362 + struct feed_pre_push_hook_data *data = xmalloc(sizeof(*data));
1363 + strbuf_init(&data->buf, 0);
1364 + data->refs = (struct ref *)feed_pipe_ctx;
1365 + return data;
1366 +}
1367 +
1368 +static void pre_push_hook_data_free(void *data)
1369 +{
1370 + struct feed_pre_push_hook_data *d = data;
1371 + if (!d)
1372 + return;
1373 + strbuf_release(&d->buf);
1374 + free(d);
1375 +}
1376 +
1377 static int run_pre_push_hook(struct transport *transport,
1378 struct ref *remote_refs)
1379 {
1380 struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
1364 - struct feed_pre_push_hook_data data;
1381 int ret = 0;
1382
1383 strvec_push(&opt.args, transport->remote->name);
1384 strvec_push(&opt.args, transport->url);
1385
1370 - strbuf_init(&data.buf, 0);
1371 - data.refs = remote_refs;
1372 -
1386 opt.feed_pipe = pre_push_hook_feed_stdin;
1374 - opt.feed_pipe_cb_data = &data;
1387 + opt.feed_pipe_ctx = remote_refs;
1388 + opt.feed_pipe_cb_data_alloc = pre_push_hook_data_alloc;
1389 + opt.feed_pipe_cb_data_free = pre_push_hook_data_free;
1390
1391 /*
1392 * pre-push hooks expect stdout & stderr to be separate, so don't merge
@@ -1381,8 +1396,6 @@ static int run_pre_push_hook(struct transport *transport,
1396
1397 ret = run_hooks_opt(the_repository, "pre-push", &opt);
1398
1384 - strbuf_release(&data.buf);
1385 -
1399 return ret;
1400 }
1401