Skip to content

Commit 04a56ae

Browse files
nasamuffingitster
authored andcommitted
hook: run a list of hooks
Teach hook.[hc] to run lists of hooks to prepare for multihook support. Currently, the hook list contains only one entry representing the "legacy" hook from the hookdir, but next commits will allow users to supply more than one executable/command for a single hook event in addition to these default "legacy" hooks. All hook commands still run sequentially. A further patch series will enable running them in parallel by increasing .jobs > 1 where possible. Each hook command requires its own internal state copy, even when running sequentially, so add an API to allow hooks to duplicate/free their internal void *pp_task_cb state. Signed-off-by: Emily Shaffer <emilyshaffer@google.com> Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 468b5f7 commit 04a56ae

5 files changed

Lines changed: 210 additions & 30 deletions

File tree

builtin/receive-pack.c

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -849,7 +849,8 @@ struct receive_hook_feed_state {
849849

850850
static int feed_receive_hook_cb(int hook_stdin_fd, void *pp_cb UNUSED, void *pp_task_cb)
851851
{
852-
struct receive_hook_feed_state *state = pp_task_cb;
852+
struct string_list_item *h = pp_task_cb;
853+
struct receive_hook_feed_state *state = h->util;
853854
struct command *cmd = state->cmd;
854855

855856
strbuf_reset(&state->buf);
@@ -901,6 +902,24 @@ static int feed_receive_hook_cb(int hook_stdin_fd, void *pp_cb UNUSED, void *pp_
901902
return state->cmd ? 0 : 1; /* 0 = more to come, 1 = EOF */
902903
}
903904

905+
static void *copy_receive_hook_feed_state(const void *data)
906+
{
907+
const struct receive_hook_feed_state *orig = data;
908+
struct receive_hook_feed_state *new_data = xmalloc(sizeof(*new_data));
909+
memcpy(new_data, orig, sizeof(*new_data));
910+
strbuf_init(&new_data->buf, 0);
911+
return new_data;
912+
}
913+
914+
static void free_receive_hook_feed_state(void *data)
915+
{
916+
struct receive_hook_feed_state *d = data;
917+
if (!d)
918+
return;
919+
strbuf_release(&d->buf);
920+
free(d);
921+
}
922+
904923
static int run_receive_hook(struct command *commands,
905924
const char *hook_name,
906925
int skip_broken,
@@ -944,6 +963,8 @@ static int run_receive_hook(struct command *commands,
944963
strbuf_init(&feed_state.buf, 0);
945964
opt.feed_pipe_cb_data = &feed_state;
946965
opt.feed_pipe = feed_receive_hook_cb;
966+
opt.copy_feed_pipe_cb_data = copy_receive_hook_feed_state;
967+
opt.free_feed_pipe_cb_data = free_receive_hook_feed_state;
947968

948969
ret = run_hooks_opt(the_repository, hook_name, &opt);
949970

hook.c

Lines changed: 108 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,49 @@ const char *find_hook(struct repository *r, const char *name)
4747
return path.buf;
4848
}
4949

50+
/*
51+
* Provides a list of hook commands to run for the 'hookname' event.
52+
*
53+
* This function consolidates hooks from two sources:
54+
* 1. The config-based hooks (not yet implemented).
55+
* 2. The "traditional" hook found in the repository hooks directory
56+
* (e.g., .git/hooks/pre-commit).
57+
*
58+
* The list is ordered by execution priority.
59+
*
60+
* The caller is responsible for freeing the memory of the returned list
61+
* using string_list_clear() and free().
62+
*/
63+
static struct string_list *list_hooks(struct repository *r, const char *hookname)
64+
{
65+
struct string_list *hook_head;
66+
67+
if (!hookname)
68+
BUG("null hookname was provided to hook_list()!");
69+
70+
hook_head = xmalloc(sizeof(struct string_list));
71+
string_list_init_dup(hook_head);
72+
73+
/*
74+
* Add the default hook from hookdir. It does not have a friendly name
75+
* like the hooks specified via configs, so add it with an empty name.
76+
*/
77+
if (r->gitdir && find_hook(r, hookname))
78+
string_list_append(hook_head, "");
79+
80+
return hook_head;
81+
}
82+
5083
int hook_exists(struct repository *r, const char *name)
5184
{
52-
return !!find_hook(r, name);
85+
int exists = 0;
86+
struct string_list *hooks = list_hooks(r, name);
87+
88+
exists = hooks->nr > 0;
89+
90+
string_list_clear(hooks, 1);
91+
free(hooks);
92+
return exists;
5393
}
5494

5595
static int pick_next_hook(struct child_process *cp,
@@ -58,10 +98,11 @@ static int pick_next_hook(struct child_process *cp,
5898
void **pp_task_cb)
5999
{
60100
struct hook_cb_data *hook_cb = pp_cb;
61-
const char *hook_path = hook_cb->hook_path;
101+
struct string_list *hook_list = hook_cb->hook_command_list;
102+
struct string_list_item *to_run = hook_cb->next_hook++;
62103

63-
if (!hook_path)
64-
return 0;
104+
if (!to_run || to_run >= hook_list->items + hook_list->nr)
105+
return 0; /* no hook left to run */
65106

66107
cp->no_stdin = 1;
67108
strvec_pushv(&cp->env, hook_cb->options->env.v);
@@ -85,33 +126,50 @@ static int pick_next_hook(struct child_process *cp,
85126
cp->trace2_hook_name = hook_cb->hook_name;
86127
cp->dir = hook_cb->options->dir;
87128

88-
strvec_push(&cp->args, hook_path);
129+
/* find hook commands */
130+
if (!*to_run->string) {
131+
/* ...from hookdir signified by empty name */
132+
const char *hook_path = find_hook(hook_cb->repository,
133+
hook_cb->hook_name);
134+
if (!hook_path)
135+
BUG("hookdir in hook list but no hook present in filesystem");
136+
137+
if (hook_cb->options->dir)
138+
hook_path = absolute_path(hook_path);
139+
140+
strvec_push(&cp->args, hook_path);
141+
}
142+
143+
if (!cp->args.nr)
144+
BUG("configured hook must have at least one command");
145+
89146
strvec_pushv(&cp->args, hook_cb->options->args.v);
90147

91148
/*
92149
* Provide per-hook internal state via task_cb for easy access, so
93150
* hook callbacks don't have to go through hook_cb->options.
94151
*/
95-
*pp_task_cb = hook_cb->options->feed_pipe_cb_data;
96-
97-
/*
98-
* This pick_next_hook() will be called again, we're only
99-
* running one hook, so indicate that no more work will be
100-
* done.
101-
*/
102-
hook_cb->hook_path = NULL;
152+
*pp_task_cb = to_run;
103153

104154
return 1;
105155
}
106156

107-
static int notify_start_failure(struct strbuf *out UNUSED,
157+
static int notify_start_failure(struct strbuf *out,
108158
void *pp_cb,
109-
void *pp_task_cp UNUSED)
159+
void *pp_task_cb)
110160
{
111161
struct hook_cb_data *hook_cb = pp_cb;
162+
struct string_list_item *hook = pp_task_cb;
112163

113164
hook_cb->rc |= 1;
114165

166+
if (out && hook) {
167+
if (*hook->string)
168+
strbuf_addf(out, _("Couldn't start hook '%s'\n"), hook->string);
169+
else
170+
strbuf_addstr(out, _("Couldn't start hook from hooks directory\n"));
171+
}
172+
115173
return 1;
116174
}
117175

@@ -145,8 +203,9 @@ int run_hooks_opt(struct repository *r, const char *hook_name,
145203
.rc = 0,
146204
.hook_name = hook_name,
147205
.options = options,
206+
.hook_command_list = list_hooks(r, hook_name),
207+
.repository = r,
148208
};
149-
const char *const hook_path = find_hook(r, hook_name);
150209
int ret = 0;
151210
const struct run_process_parallel_opts opts = {
152211
.tr2_category = "hook",
@@ -172,26 +231,50 @@ int run_hooks_opt(struct repository *r, const char *hook_name,
172231
if (!options->jobs)
173232
BUG("run_hooks_opt must be called with options.jobs >= 1");
174233

234+
/*
235+
* Ensure cb_data copy and free functions are either provided together,
236+
* or neither one is provided.
237+
*/
238+
if ((options->copy_feed_pipe_cb_data && !options->free_feed_pipe_cb_data) ||
239+
(!options->copy_feed_pipe_cb_data && options->free_feed_pipe_cb_data))
240+
BUG("copy_feed_pipe_cb_data and free_feed_pipe_cb_data must be set together");
241+
175242
if (options->invoked_hook)
176243
*options->invoked_hook = 0;
177244

178-
if (!hook_path && !options->error_if_missing)
179-
goto cleanup;
180-
181-
if (!hook_path) {
182-
ret = error("cannot find a hook named %s", hook_name);
245+
if (!cb_data.hook_command_list->nr) {
246+
if (options->error_if_missing)
247+
ret = error("cannot find a hook named %s", hook_name);
183248
goto cleanup;
184249
}
185250

186-
cb_data.hook_path = hook_path;
187-
if (options->dir) {
188-
strbuf_add_absolute_path(&abs_path, hook_path);
189-
cb_data.hook_path = abs_path.buf;
251+
/*
252+
* Initialize the iterator/cursor which holds the next hook to run.
253+
* run_process_parallel() calls pick_next_hook() which increments it for
254+
* each hook command in the list until all hooks have been run.
255+
*/
256+
cb_data.next_hook = cb_data.hook_command_list->items;
257+
258+
/*
259+
* Give each hook its own copy of the initial void *pp_task_cb state, if
260+
* a copy callback was provided.
261+
*/
262+
if (options->copy_feed_pipe_cb_data) {
263+
struct string_list_item *item;
264+
for_each_string_list_item(item, cb_data.hook_command_list)
265+
item->util = options->copy_feed_pipe_cb_data(options->feed_pipe_cb_data);
190266
}
191267

192268
run_processes_parallel(&opts);
193269
ret = cb_data.rc;
194270
cleanup:
271+
if (options->free_feed_pipe_cb_data) {
272+
struct string_list_item *item;
273+
for_each_string_list_item(item, cb_data.hook_command_list)
274+
options->free_feed_pipe_cb_data(item->util);
275+
}
276+
string_list_clear(cb_data.hook_command_list, 0);
277+
free(cb_data.hook_command_list);
195278
strbuf_release(&abs_path);
196279
run_hooks_opt_clear(options);
197280
return ret;

hook.h

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define HOOK_H
33
#include "strvec.h"
44
#include "run-command.h"
5+
#include "string-list.h"
56

67
struct repository;
78

@@ -86,12 +87,28 @@ struct run_hooks_opt
8687
* Opaque data pointer used to keep internal state across callback calls.
8788
*
8889
* It can be accessed directly via the third callback arg 'pp_task_cb':
89-
* struct ... *state = pp_task_cb;
90+
* struct ... *state = ((struct string_list_item *)pp_task_cb)->util;
9091
*
9192
* The caller is responsible for managing the memory for this data.
9293
* Only useful when using `run_hooks_opt.feed_pipe`, otherwise ignore it.
9394
*/
9495
void *feed_pipe_cb_data;
96+
97+
/**
98+
* Some hooks need a copy of the initial `feed_pipe_cb_data` state, so
99+
* they can keep track of progress without affecting one another.
100+
*
101+
* If provided, this function will be called to copy `feed_pipe_cb_data`
102+
* for each hook.
103+
*/
104+
void *(*copy_feed_pipe_cb_data)(const void *data);
105+
106+
/**
107+
* Called to free the memory duplicated by `copy_feed_pipe_cb_data`.
108+
*
109+
* Must always be provided when `copy_feed_pipe_cb_data` is provided.
110+
*/
111+
void (*free_feed_pipe_cb_data)(void *data);
95112
};
96113

97114
#define RUN_HOOKS_OPT_INIT { \
@@ -105,8 +122,25 @@ struct hook_cb_data {
105122
/* rc reflects the cumulative failure state */
106123
int rc;
107124
const char *hook_name;
108-
const char *hook_path;
125+
126+
/**
127+
* A list of hook commands/paths to run for the 'hook_name' event.
128+
*
129+
* The 'string' member of each item contains the executable path (e.g.
130+
* "/path/to/.git/hooks/pre-commit") or command.
131+
*/
132+
struct string_list *hook_command_list;
133+
134+
/**
135+
* Iterator/cursor for the above list, pointing to the next hook to run.
136+
*
137+
* The 'util' member of the string_list_item holds the per-hook state
138+
* data (feed_pipe_cb_data) which is passed to callbacks via 'pp_task_cb'.
139+
*/
140+
struct string_list_item *next_hook;
141+
109142
struct run_hooks_opt *options;
143+
struct repository *repository;
110144
};
111145

112146
/*

refs.c

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2472,7 +2472,8 @@ static int transaction_hook_feed_stdin(int hook_stdin_fd, void *pp_cb, void *pp_
24722472
{
24732473
struct hook_cb_data *hook_cb = pp_cb;
24742474
struct ref_transaction *transaction = hook_cb->options->feed_pipe_ctx;
2475-
struct transaction_feed_cb_data *feed_cb_data = pp_task_cb;
2475+
struct string_list_item *hook = pp_task_cb;
2476+
struct transaction_feed_cb_data *feed_cb_data = hook->util;
24762477
struct strbuf *buf = &feed_cb_data->buf;
24772478
struct ref_update *update;
24782479
size_t i = feed_cb_data->index++;
@@ -2511,6 +2512,24 @@ static int transaction_hook_feed_stdin(int hook_stdin_fd, void *pp_cb, void *pp_
25112512
return 0; /* no more input to feed */
25122513
}
25132514

2515+
static void *copy_transaction_feed_cb_data(const void *data)
2516+
{
2517+
struct transaction_feed_cb_data *new_data = xmalloc(sizeof(struct transaction_feed_cb_data));
2518+
memcpy(new_data, data, sizeof(struct transaction_feed_cb_data));
2519+
strbuf_init(&new_data->buf, 0);
2520+
new_data->index = 0; /* a fresh iterator for each hook */
2521+
return new_data;
2522+
}
2523+
2524+
static void free_transaction_feed_cb_data(void *data)
2525+
{
2526+
struct transaction_feed_cb_data *d = data;
2527+
if (!d)
2528+
return;
2529+
strbuf_release(&d->buf);
2530+
free(d);
2531+
}
2532+
25142533
static int run_transaction_hook(struct ref_transaction *transaction,
25152534
const char *state)
25162535
{
@@ -2523,6 +2542,8 @@ static int run_transaction_hook(struct ref_transaction *transaction,
25232542
opt.feed_pipe = transaction_hook_feed_stdin;
25242543
opt.feed_pipe_ctx = transaction;
25252544
opt.feed_pipe_cb_data = &feed_ctx;
2545+
opt.copy_feed_pipe_cb_data = copy_transaction_feed_cb_data;
2546+
opt.free_feed_pipe_cb_data = free_transaction_feed_cb_data;
25262547

25272548
strbuf_init(&feed_ctx.buf, 0);
25282549

transport.c

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1323,7 +1323,8 @@ struct feed_pre_push_hook_data {
13231323

13241324
static int pre_push_hook_feed_stdin(int hook_stdin_fd, void *pp_cb UNUSED, void *pp_task_cb)
13251325
{
1326-
struct feed_pre_push_hook_data *data = pp_task_cb;
1326+
struct string_list_item *h = pp_task_cb;
1327+
struct feed_pre_push_hook_data *data = h->util;
13271328
const struct ref *r = data->refs;
13281329
int ret = 0;
13291330

@@ -1357,6 +1358,24 @@ static int pre_push_hook_feed_stdin(int hook_stdin_fd, void *pp_cb UNUSED, void
13571358
return 0;
13581359
}
13591360

1361+
static void *copy_pre_push_hook_data(const void *data)
1362+
{
1363+
const struct feed_pre_push_hook_data *orig = data;
1364+
struct feed_pre_push_hook_data *new_data = xmalloc(sizeof(*new_data));
1365+
strbuf_init(&new_data->buf, 0);
1366+
new_data->refs = orig->refs;
1367+
return new_data;
1368+
}
1369+
1370+
static void free_pre_push_hook_data(void *data)
1371+
{
1372+
struct feed_pre_push_hook_data *d = data;
1373+
if (!d)
1374+
return;
1375+
strbuf_release(&d->buf);
1376+
free(d);
1377+
}
1378+
13601379
static int run_pre_push_hook(struct transport *transport,
13611380
struct ref *remote_refs)
13621381
{
@@ -1372,6 +1391,8 @@ static int run_pre_push_hook(struct transport *transport,
13721391

13731392
opt.feed_pipe = pre_push_hook_feed_stdin;
13741393
opt.feed_pipe_cb_data = &data;
1394+
opt.copy_feed_pipe_cb_data = copy_pre_push_hook_data;
1395+
opt.free_feed_pipe_cb_data = free_pre_push_hook_data;
13751396

13761397
/*
13771398
* pre-push hooks expect stdout & stderr to be separate, so don't merge

0 commit comments

Comments
 (0)