Coverage Report

Created: 2026-10-17 07:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/git/hook.c
Line
Count
Source
1
#include "git-compat-util.h"
2
#include "abspath.h"
3
#include "advice.h"
4
#include "config.h"
5
#include "environment.h"
6
#include "gettext.h"
7
#include "hook.h"
8
#include "hook-list.h"
9
#include "parse.h"
10
#include "path.h"
11
#include "run-command.h"
12
#include "setup.h"
13
#include "strbuf.h"
14
#include "strmap.h"
15
#include "thread-utils.h"
16
17
bool is_known_hook(const char *name)
18
0
{
19
0
  const char **h;
20
0
  for (h = hook_name_list; *h; h++)
21
0
    if (!strcmp(*h, name))
22
0
      return true;
23
0
  return false;
24
0
}
25
26
const char *find_hook(struct repository *r, const char *name)
27
0
{
28
0
  static struct strbuf path = STRBUF_INIT;
29
30
0
  int found_hook;
31
32
0
  if (!r || !r->gitdir)
33
0
    return NULL;
34
35
0
  repo_git_path_replace(r, &path, "hooks/%s", name);
36
0
  found_hook = access(path.buf, X_OK) >= 0;
37
#ifdef STRIP_EXTENSION
38
  if (!found_hook) {
39
    int err = errno;
40
41
    strbuf_addstr(&path, STRIP_EXTENSION);
42
    found_hook = access(path.buf, X_OK) >= 0;
43
    if (!found_hook)
44
      errno = err;
45
  }
46
#endif
47
48
0
  if (!found_hook) {
49
0
    if (errno == EACCES && advice_enabled(ADVICE_IGNORED_HOOK)) {
50
0
      static struct string_list advise_given = STRING_LIST_INIT_DUP;
51
52
0
      if (!string_list_lookup(&advise_given, name)) {
53
0
        string_list_insert(&advise_given, name);
54
0
        advise(_("The '%s' hook was ignored because "
55
0
           "it's not set as executable.\n"
56
0
           "You can disable this warning with "
57
0
           "`git config set advice.ignoredHook false`."),
58
0
               path.buf);
59
0
      }
60
0
    }
61
0
    return NULL;
62
0
  }
63
0
  return path.buf;
64
0
}
65
66
void hook_free(void *p, const char *str UNUSED)
67
0
{
68
0
  struct hook *h = p;
69
70
0
  if (!h)
71
0
    return;
72
73
0
  if (h->kind == HOOK_TRADITIONAL) {
74
0
    free((void *)h->u.traditional.path);
75
0
  } else if (h->kind == HOOK_CONFIGURED) {
76
0
    free((void *)h->u.configured.friendly_name);
77
0
    free((void *)h->u.configured.command);
78
0
  }
79
80
0
  if (h->data_free && h->feed_pipe_cb_data)
81
0
    h->data_free(h->feed_pipe_cb_data);
82
83
0
  free(h);
84
0
}
85
86
/* Helper to detect and add default "traditional" hooks from the hookdir. */
87
static void list_hooks_add_default(struct repository *r, const char *hookname,
88
           struct string_list *hook_list,
89
           struct run_hooks_opt *options)
90
0
{
91
0
  const char *hook_path = find_hook(r, hookname);
92
0
  struct hook *h;
93
94
0
  if (!hook_path)
95
0
    return;
96
97
0
  CALLOC_ARRAY(h, 1);
98
99
  /*
100
   * If the hook is to run in a specific dir, a relative path can
101
   * become invalid in that dir, so convert to an absolute path.
102
   */
103
0
  if (options && options->dir)
104
0
    hook_path = absolute_path(hook_path);
105
106
  /*
107
   * Setup per-hook internal state callback data.
108
   * When provided, the alloc/free callbacks are always provided
109
   * together, so use them to alloc/free the internal hook state.
110
   */
111
0
  if (options && options->feed_pipe_cb_data_alloc) {
112
0
    h->feed_pipe_cb_data = options->feed_pipe_cb_data_alloc(options->feed_pipe_ctx);
113
0
    h->data_free = options->feed_pipe_cb_data_free;
114
0
  }
115
116
0
  h->kind = HOOK_TRADITIONAL;
117
0
  h->u.traditional.path = xstrdup(hook_path);
118
119
0
  string_list_append(hook_list, hook_path)->util = h;
120
0
}
121
122
/*
123
 * Cache entry stored as the .util pointer of string_list items inside the
124
 * hook config cache.
125
 */
126
struct hook_config_cache_entry {
127
  char *command;
128
  enum config_scope scope;
129
  bool disabled;
130
  bool parallel;
131
};
132
133
/*
134
 * Callback struct to collect all hook.* keys in a single config pass.
135
 * commands: friendly-name to command map.
136
 * event_hooks: event-name to list of friendly-names map.
137
 * disabled_hooks: set of all names with hook.<name>.enabled = false; after
138
 *                 parsing, names that are not friendly-names become event-level
139
 *                 disables stored in r->disabled_events. This collects all.
140
 * parallel_hooks: friendly-name to parallel flag.
141
 * event_jobs: event-name to per-event jobs count (stored as uintptr_t, NULL == unset).
142
 * jobs: value of the global hook.jobs key. Defaults to 0 if unset (stored in r->hook_jobs).
143
 */
144
struct hook_all_config_cb {
145
  struct strmap commands;
146
  struct strmap event_hooks;
147
  struct string_list disabled_hooks;
148
  struct strmap parallel_hooks;
149
  struct strmap event_jobs;
150
  unsigned int jobs;
151
};
152
153
/* repo_config() callback that collects all hook.* configuration in one pass. */
154
static int hook_config_lookup_all(const char *key, const char *value,
155
          const struct config_context *ctx,
156
          void *cb_data)
157
0
{
158
0
  struct hook_all_config_cb *data = cb_data;
159
0
  const char *name, *subkey;
160
0
  char *hook_name;
161
0
  size_t name_len = 0;
162
163
0
  if (parse_config_key(key, "hook", &name, &name_len, &subkey))
164
0
    return 0;
165
166
  /* Handle plain hook.<key> entries that have no hook name component. */
167
0
  if (!name) {
168
0
    if (!strcmp(subkey, "jobs") && value) {
169
0
      int v;
170
0
      if (!git_parse_int(value, &v))
171
0
        warning(_("hook.jobs must be an integer, ignoring: '%s'"), value);
172
0
      else if (v == -1)
173
0
        data->jobs = online_cpus();
174
0
      else if (v > 0)
175
0
        data->jobs = v;
176
0
      else
177
0
        warning(_("hook.jobs must be a positive integer"
178
0
            " or -1, ignoring: '%s'"),
179
0
          value);
180
0
    }
181
0
    return 0;
182
0
  }
183
184
0
  if (!value)
185
0
    return config_error_nonbool(key);
186
187
  /* Extract name, ensuring it is null-terminated. */
188
0
  hook_name = xmemdupz(name, name_len);
189
190
0
  if (!strcmp(subkey, "event")) {
191
0
    if (!*value) {
192
      /* Empty values reset previous events for this hook. */
193
0
      struct hashmap_iter iter;
194
0
      struct strmap_entry *e;
195
196
0
      strmap_for_each_entry(&data->event_hooks, &iter, e)
197
0
        unsorted_string_list_remove(e->value, hook_name, 0);
198
0
    } else {
199
0
      struct string_list *hooks;
200
201
0
      if (is_known_hook(hook_name))
202
0
        die(_("hook friendly-name '%s' collides with "
203
0
              "a known event name; please choose a "
204
0
              "different friendly-name"),
205
0
            hook_name);
206
207
0
      if (!strcmp(hook_name, value))
208
0
        warning(_("hook friendly-name '%s' is the "
209
0
            "same as its event; this may cause "
210
0
            "ambiguity with hook.%s.enabled"),
211
0
          hook_name, hook_name);
212
213
0
      hooks = strmap_get(&data->event_hooks, value);
214
215
0
      if (!hooks) {
216
0
        CALLOC_ARRAY(hooks, 1);
217
0
        string_list_init_dup(hooks);
218
0
        strmap_put(&data->event_hooks, value, hooks);
219
0
      }
220
221
      /* Re-insert if necessary to preserve last-seen order. */
222
0
      unsorted_string_list_remove(hooks, hook_name, 0);
223
224
0
      if (!ctx->kvi)
225
0
        BUG("hook config callback called without key-value info");
226
227
      /*
228
       * Stash the config scope in the util pointer for
229
       * later retrieval in build_hook_config_map(). This
230
       * intermediate struct is transient and never leaves
231
       * that function, so we pack the enum value into the
232
       * pointer rather than heap-allocating a wrapper.
233
       */
234
0
      string_list_append(hooks, hook_name)->util =
235
0
        (void *)(uintptr_t)ctx->kvi->scope;
236
0
    }
237
0
  } else if (!strcmp(subkey, "command")) {
238
    /* Store command overwriting the old value */
239
0
    char *old = strmap_put(&data->commands, hook_name,
240
0
               xstrdup(value));
241
0
    free(old);
242
0
  } else if (!strcmp(subkey, "enabled")) {
243
0
    switch (git_parse_maybe_bool(value)) {
244
0
    case 0: /* disabled */
245
0
      if (!unsorted_string_list_lookup(&data->disabled_hooks,
246
0
               hook_name))
247
0
        string_list_append(&data->disabled_hooks,
248
0
               hook_name);
249
0
      break;
250
0
    case 1: /* enabled: undo a prior disabled entry */
251
0
      unsorted_string_list_remove(&data->disabled_hooks,
252
0
                hook_name, 0);
253
0
      break;
254
0
    default:
255
0
      break; /* ignore unrecognised values */
256
0
    }
257
0
  } else if (!strcmp(subkey, "parallel")) {
258
0
    int v = git_parse_maybe_bool(value);
259
0
    if (v >= 0)
260
0
      strmap_put(&data->parallel_hooks, hook_name,
261
0
           (void *)(uintptr_t)v);
262
0
    else
263
0
      warning(_("hook.%s.parallel must be a boolean,"
264
0
          " ignoring: '%s'"),
265
0
        hook_name, value);
266
0
  } else if (!strcmp(subkey, "jobs")) {
267
0
    int v;
268
0
    if (!git_parse_int(value, &v))
269
0
      warning(_("hook.%s.jobs must be an integer,"
270
0
          " ignoring: '%s'"),
271
0
        hook_name, value);
272
0
    else if (v == -1)
273
0
      strmap_put(&data->event_jobs, hook_name,
274
0
           (void *)(uintptr_t)online_cpus());
275
0
    else if (v > 0)
276
0
      strmap_put(&data->event_jobs, hook_name,
277
0
           (void *)(uintptr_t)v);
278
0
    else
279
0
      warning(_("hook.%s.jobs must be a positive"
280
0
          " integer or -1, ignoring: '%s'"),
281
0
        hook_name, value);
282
0
  }
283
284
0
  free(hook_name);
285
0
  return 0;
286
0
}
287
288
/*
289
 * The hook config cache maps each hook event name to a string_list where
290
 * every item's string is the hook's friendly-name and its util pointer is
291
 * the corresponding command string. Both strings are owned by the map.
292
 *
293
 * Disabled hooks are kept in the cache with entry->disabled set, so that
294
 * "git hook list" can display them. A non-disabled hook missing a command
295
 * is fatal; a disabled hook missing a command emits a warning and is kept
296
 * in the cache with entry->command = NULL.
297
 */
298
void hook_cache_clear(struct strmap *cache)
299
0
{
300
0
  struct hashmap_iter iter;
301
0
  struct strmap_entry *e;
302
303
0
  strmap_for_each_entry(cache, &iter, e) {
304
0
    struct string_list *hooks = e->value;
305
0
    for (size_t i = 0; i < hooks->nr; i++) {
306
0
      struct hook_config_cache_entry *entry = hooks->items[i].util;
307
0
      free(entry->command);
308
0
      free(entry);
309
0
    }
310
0
    string_list_clear(hooks, 0);
311
0
    free(hooks);
312
0
  }
313
0
  strmap_clear(cache, 0);
314
0
}
315
316
/*
317
 * Return true if `name` is a hook friendly-name, i.e. it has at least one of
318
 * .command, .event, or .parallel configured. These are the reliable clues
319
 * that distinguish a friendly-name from an event name. Note: .enabled is
320
 * deliberately excluded because it can appear under both namespaces.
321
 */
322
static int is_friendly_name(struct hook_all_config_cb *cb, const char *name)
323
0
{
324
0
  struct hashmap_iter iter;
325
0
  struct strmap_entry *e;
326
327
0
  if (strmap_get(&cb->commands, name) || strmap_get(&cb->parallel_hooks, name))
328
0
    return 1;
329
330
0
  strmap_for_each_entry(&cb->event_hooks, &iter, e) {
331
0
    if (unsorted_string_list_lookup(e->value, name))
332
0
      return 1;
333
0
  }
334
335
0
  return 0;
336
0
}
337
338
/* Warn if any name in event_jobs is also a hook friendly-name. */
339
static void warn_jobs_on_friendly_names(struct hook_all_config_cb *cb_data)
340
0
{
341
0
  struct hashmap_iter iter;
342
0
  struct strmap_entry *e;
343
344
0
  strmap_for_each_entry(&cb_data->event_jobs, &iter, e) {
345
0
    if (is_friendly_name(cb_data, e->key))
346
0
      warning(_("hook.%s.jobs is set but '%s' looks like a "
347
0
          "hook friendly-name, not an event name; "
348
0
          "hook.<event>.jobs uses the event name "
349
0
          "(e.g. hook.post-receive.jobs), so this "
350
0
          "setting will be ignored"), e->key, e->key);
351
0
  }
352
0
}
353
354
/* Populate `cache` with the complete hook configuration */
355
static void build_hook_config_map(struct repository *r, struct strmap *cache)
356
0
{
357
0
  struct hook_all_config_cb cb_data = { 0 };
358
0
  struct hashmap_iter iter;
359
0
  struct strmap_entry *e;
360
361
0
  strmap_init(&cb_data.commands);
362
0
  strmap_init(&cb_data.event_hooks);
363
0
  string_list_init_dup(&cb_data.disabled_hooks);
364
0
  strmap_init(&cb_data.parallel_hooks);
365
0
  strmap_init(&cb_data.event_jobs);
366
367
  /* Parse all configs in one run, capturing hook.* including hook.jobs. */
368
0
  repo_config(r, hook_config_lookup_all, &cb_data);
369
370
0
  warn_jobs_on_friendly_names(&cb_data);
371
372
  /*
373
   * Populate disabled_events: names in disabled_hooks that are not
374
   * friendly-names are event-level switches (hook.<event>.enabled = false).
375
   * Names that are friendly-names are already handled per-hook via the
376
   * hook_config_cache_entry.disabled flag below.
377
   */
378
0
  if (r) {
379
0
    string_list_clear(&r->disabled_events, 0);
380
0
    string_list_init_dup(&r->disabled_events);
381
0
    for (size_t i = 0; i < cb_data.disabled_hooks.nr; i++) {
382
0
      const char *n = cb_data.disabled_hooks.items[i].string;
383
0
      if (!is_friendly_name(&cb_data, n))
384
0
        string_list_append(&r->disabled_events, n);
385
0
    }
386
0
  }
387
388
  /* Construct the cache from parsed configs. */
389
0
  strmap_for_each_entry(&cb_data.event_hooks, &iter, e) {
390
0
    struct string_list *hook_names = e->value;
391
0
    struct string_list *hooks;
392
393
0
    CALLOC_ARRAY(hooks, 1);
394
0
    string_list_init_dup(hooks);
395
396
0
    for (size_t i = 0; i < hook_names->nr; i++) {
397
0
      const char *hname = hook_names->items[i].string;
398
0
      enum config_scope scope =
399
0
        (enum config_scope)(uintptr_t)hook_names->items[i].util;
400
0
      struct hook_config_cache_entry *entry;
401
0
      char *command;
402
403
0
      bool is_par = !!strmap_get(&cb_data.parallel_hooks, hname);
404
0
      bool is_disabled =
405
0
        !!unsorted_string_list_lookup(
406
0
          &cb_data.disabled_hooks, hname);
407
408
0
      command = strmap_get(&cb_data.commands, hname);
409
0
      if (!command) {
410
0
        if (is_disabled)
411
0
          warning(_("disabled hook '%s' has no "
412
0
              "command configured"), hname);
413
0
        else
414
0
          die(_("'hook.%s.command' must be configured or "
415
0
                "'hook.%s.event' must be removed;"
416
0
                " aborting."), hname, hname);
417
0
      }
418
419
      /* util stores a cache entry; owned by the cache. */
420
0
      CALLOC_ARRAY(entry, 1);
421
0
      entry->command = xstrdup_or_null(command);
422
0
      entry->scope = scope;
423
0
      entry->disabled = is_disabled;
424
0
      entry->parallel = is_par;
425
0
      string_list_append(hooks, hname)->util = entry;
426
0
    }
427
428
0
    strmap_put(cache, e->key, hooks);
429
0
  }
430
431
0
  if (r) {
432
0
    r->hook_jobs = cb_data.jobs;
433
0
    r->event_jobs = cb_data.event_jobs;
434
0
  }
435
436
0
  strmap_clear(&cb_data.commands, 1);
437
0
  strmap_clear(&cb_data.parallel_hooks, 0); /* values are uintptr_t, not heap ptrs */
438
0
  string_list_clear(&cb_data.disabled_hooks, 0);
439
0
  strmap_for_each_entry(&cb_data.event_hooks, &iter, e) {
440
0
    string_list_clear(e->value, 0);
441
0
    free(e->value);
442
0
  }
443
0
  strmap_clear(&cb_data.event_hooks, 0);
444
0
}
445
446
/*
447
 * Return the hook config map for `r`, populating it first if needed.
448
 *
449
 * Out-of-repo calls (r->gitdir == NULL) allocate and return a temporary
450
 * cache map; the caller is responsible for freeing it with
451
 * hook_cache_clear() + free().
452
 */
453
static struct strmap *get_hook_config_cache(struct repository *r)
454
0
{
455
0
  struct strmap *cache = NULL;
456
457
0
  if (r && r->gitdir) {
458
    /*
459
     * For in-repo calls, the map is stored in r->hook_config_cache,
460
     * so repeated invocations don't parse the configs, so allocate
461
     * it just once on the first call.
462
     */
463
0
    if (!r->hook_config_cache) {
464
0
      CALLOC_ARRAY(r->hook_config_cache, 1);
465
0
      strmap_init(r->hook_config_cache);
466
0
      build_hook_config_map(r, r->hook_config_cache);
467
0
    }
468
0
    cache = r->hook_config_cache;
469
0
  } else {
470
    /*
471
     * Out-of-repo calls (no gitdir) allocate and return a temporary
472
     * cache which gets freed immediately by the caller.
473
     */
474
0
    CALLOC_ARRAY(cache, 1);
475
0
    strmap_init(cache);
476
0
    build_hook_config_map(r, cache);
477
0
  }
478
479
0
  return cache;
480
0
}
481
482
static void list_hooks_add_configured(struct repository *r,
483
              const char *hookname,
484
              struct string_list *list,
485
              struct run_hooks_opt *options)
486
0
{
487
0
  struct strmap *cache = get_hook_config_cache(r);
488
0
  struct string_list *configured_hooks = strmap_get(cache, hookname);
489
0
  bool event_is_disabled = r ? !!unsorted_string_list_lookup(&r->disabled_events,
490
0
                   hookname) : 0;
491
492
  /* Iterate through configured hooks and initialize internal states */
493
0
  for (size_t i = 0; configured_hooks && i < configured_hooks->nr; i++) {
494
0
    const char *friendly_name = configured_hooks->items[i].string;
495
0
    struct hook_config_cache_entry *entry = configured_hooks->items[i].util;
496
0
    struct hook *hook;
497
498
0
    CALLOC_ARRAY(hook, 1);
499
500
    /*
501
     * When provided, the alloc/free callbacks are always provided
502
     * together, so use them to alloc/free the internal hook state.
503
     */
504
0
    if (options && options->feed_pipe_cb_data_alloc) {
505
0
      hook->feed_pipe_cb_data =
506
0
        options->feed_pipe_cb_data_alloc(
507
0
          options->feed_pipe_ctx);
508
0
      hook->data_free = options->feed_pipe_cb_data_free;
509
0
    }
510
511
0
    hook->kind = HOOK_CONFIGURED;
512
0
    hook->u.configured.friendly_name = xstrdup(friendly_name);
513
0
    hook->u.configured.command =
514
0
      entry->command ? xstrdup(entry->command) : NULL;
515
0
    hook->u.configured.scope = entry->scope;
516
0
    hook->u.configured.disabled = entry->disabled;
517
0
    hook->u.configured.event_disabled = event_is_disabled;
518
0
    hook->parallel = entry->parallel;
519
520
0
    string_list_append(list, friendly_name)->util = hook;
521
0
  }
522
523
  /*
524
   * Cleanup temporary cache for out-of-repo calls since they can't be
525
   * stored persistently. Next out-of-repo calls will have to re-parse.
526
   */
527
0
  if (!r || !r->gitdir) {
528
0
    hook_cache_clear(cache);
529
0
    free(cache);
530
0
    if (r)
531
0
      string_list_clear(&r->disabled_events, 0);
532
0
  }
533
0
}
534
535
struct string_list *list_hooks(struct repository *r, const char *hookname,
536
             struct run_hooks_opt *options)
537
0
{
538
0
  struct string_list *hook_head;
539
540
0
  if (!hookname)
541
0
    BUG("null hookname was provided to hook_list()!");
542
543
0
  CALLOC_ARRAY(hook_head, 1);
544
0
  string_list_init_dup(hook_head);
545
546
  /* Add hooks from the config, e.g. hook.myhook.event = pre-commit */
547
0
  list_hooks_add_configured(r, hookname, hook_head, options);
548
549
  /* Add the default "traditional" hooks from hookdir. */
550
0
  list_hooks_add_default(r, hookname, hook_head, options);
551
552
0
  return hook_head;
553
0
}
554
555
int hook_exists(struct repository *r, const char *name)
556
0
{
557
0
  struct string_list *hooks = list_hooks(r, name, NULL);
558
0
  int exists = 0;
559
560
0
  for (size_t i = 0; i < hooks->nr; i++) {
561
0
    struct hook *h = hooks->items[i].util;
562
0
    if (h->kind == HOOK_TRADITIONAL ||
563
0
        (!h->u.configured.disabled && !h->u.configured.event_disabled)) {
564
0
      exists = 1;
565
0
      break;
566
0
    }
567
0
  }
568
0
  string_list_clear_func(hooks, hook_free);
569
0
  free(hooks);
570
0
  return exists;
571
0
}
572
573
static int pick_next_hook(struct child_process *cp,
574
        struct strbuf *out UNUSED,
575
        void *pp_cb,
576
        void **pp_task_cb)
577
0
{
578
0
  struct hook_cb_data *hook_cb = pp_cb;
579
0
  struct string_list *hook_list = hook_cb->hook_command_list;
580
0
  struct hook *h;
581
582
0
  do {
583
0
    if (hook_cb->hook_to_run_index >= hook_list->nr)
584
0
      return 0;
585
0
    h = hook_list->items[hook_cb->hook_to_run_index++].util;
586
0
  } while (h->kind == HOOK_CONFIGURED &&
587
0
     (h->u.configured.disabled || h->u.configured.event_disabled));
588
589
0
  cp->no_stdin = 1;
590
0
  strvec_pushv(&cp->env, hook_cb->options->env.v);
591
592
0
  if (hook_cb->options->path_to_stdin && hook_cb->options->feed_pipe)
593
0
    BUG("options path_to_stdin and feed_pipe are mutually exclusive");
594
595
  /* reopen the file for stdin; run_command closes it. */
596
0
  if (hook_cb->options->path_to_stdin) {
597
0
    cp->no_stdin = 0;
598
0
    cp->in = xopen(hook_cb->options->path_to_stdin, O_RDONLY);
599
0
  }
600
601
0
  if (hook_cb->options->feed_pipe) {
602
0
    cp->no_stdin = 0;
603
    /* start_command() will allocate a pipe / stdin fd for us */
604
0
    cp->in = -1;
605
0
  }
606
607
0
  cp->stdout_to_stderr = hook_cb->options->stdout_to_stderr;
608
0
  cp->trace2_hook_name = hook_cb->hook_name;
609
0
  cp->dir = hook_cb->options->dir;
610
611
  /* Add hook exec paths or commands */
612
0
  if (h->kind == HOOK_TRADITIONAL) {
613
0
    strvec_push(&cp->args, h->u.traditional.path);
614
0
  } else if (h->kind == HOOK_CONFIGURED) {
615
    /* to enable oneliners, let config-specified hooks run in shell. */
616
0
    cp->use_shell = true;
617
0
    if (!h->u.configured.command)
618
0
      BUG("non-disabled HOOK_CONFIGURED hook has no command");
619
0
    strvec_push(&cp->args, h->u.configured.command);
620
0
  } else {
621
0
    BUG("unknown hook kind");
622
0
  }
623
624
0
  if (!cp->args.nr)
625
0
    BUG("hook must have at least one command or exec path");
626
627
0
  strvec_pushv(&cp->args, hook_cb->options->args.v);
628
629
  /*
630
   * Provide per-hook internal state via task_cb for easy access, so
631
   * hook callbacks don't have to go through hook_cb->options.
632
   */
633
0
  *pp_task_cb = h->feed_pipe_cb_data;
634
635
0
  return 1;
636
0
}
637
638
static int notify_start_failure(struct strbuf *out UNUSED,
639
        void *pp_cb,
640
        void *pp_task_cp UNUSED)
641
0
{
642
0
  struct hook_cb_data *hook_cb = pp_cb;
643
644
0
  hook_cb->rc |= 1;
645
646
0
  return 1;
647
0
}
648
649
static int notify_hook_finished(int result,
650
        struct strbuf *out UNUSED,
651
        void *pp_cb,
652
        void *pp_task_cb UNUSED)
653
0
{
654
0
  struct hook_cb_data *hook_cb = pp_cb;
655
0
  struct run_hooks_opt *opt = hook_cb->options;
656
657
0
  hook_cb->rc |= result;
658
659
0
  if (opt->invoked_hook)
660
0
    *opt->invoked_hook = 1;
661
662
0
  return 0;
663
0
}
664
665
static void run_hooks_opt_clear(struct run_hooks_opt *options)
666
0
{
667
0
  strvec_clear(&options->env);
668
0
  strvec_clear(&options->args);
669
0
}
670
671
/*
672
 * When running in parallel, stdout must be merged into stderr so
673
 * run-command can buffer and de-interleave outputs correctly. This
674
 * applies even to hooks like pre-push that normally keep stdout and
675
 * stderr separate: the user has opted into parallelism, so the output
676
 * stream behavior changes accordingly.
677
 */
678
static void merge_output_if_parallel(struct run_hooks_opt *options)
679
0
{
680
0
  if (options->jobs > 1)
681
0
    options->stdout_to_stderr = 1;
682
0
}
683
684
static void warn_non_parallel_hooks_override(unsigned int jobs,
685
               struct string_list *hook_list)
686
0
{
687
  /* Don't warn for hooks running sequentially. */
688
0
  if (jobs == 1)
689
0
    return;
690
691
0
  for (size_t i = 0; i < hook_list->nr; i++) {
692
0
    struct hook *h = hook_list->items[i].util;
693
0
    if (h->kind == HOOK_CONFIGURED && !h->parallel)
694
0
      warning(_("hook '%s' is not marked as parallel=true, "
695
0
          "running in parallel anyway due to -j%u"),
696
0
        h->u.configured.friendly_name, jobs);
697
0
  }
698
0
}
699
700
/* Resolve a hook.jobs config key, handling -1 as online_cpus(). */
701
static void resolve_hook_config_jobs(struct repository *r,
702
             const char *key,
703
             unsigned int *jobs)
704
0
{
705
0
  int v;
706
707
0
  if (repo_config_get_int(r, key, &v))
708
0
    return;
709
710
0
  if (v == -1)
711
0
    *jobs = online_cpus();
712
0
  else if (v > 0)
713
0
    *jobs = v;
714
0
  else
715
0
    warning(_("%s must be a positive integer or -1,"
716
0
        " ignoring: %d"), key, v);
717
0
}
718
719
/* Determine how many jobs to use for hook execution. */
720
static unsigned int get_hook_jobs(struct repository *r,
721
          struct run_hooks_opt *options,
722
          const char *hook_name,
723
          struct string_list *hook_list)
724
0
{
725
  /*
726
   * An explicit job count overrides everything else: this covers both
727
   * FORCE_SERIAL callers (for hooks that must never run in parallel)
728
   * and the -j flag from the CLI. The CLI override is intentional: users
729
   * may want to serialize hooks declared parallel or to parallelize more
730
   * aggressively than the default.
731
   */
732
0
  if (options->jobs)
733
0
    goto cleanup;
734
735
  /*
736
   * Use hook.jobs from the already-parsed config cache (in-repo), or
737
   * fallback to a direct config lookup (out-of-repo).
738
   * Default to 1 (serial execution) on failure.
739
   */
740
0
  options->jobs = 1;
741
0
  if (r) {
742
0
    if (r->gitdir && r->hook_config_cache) {
743
0
      void *event_jobs;
744
745
0
      if (r->hook_jobs)
746
0
        options->jobs = r->hook_jobs;
747
748
0
      event_jobs = strmap_get(&r->event_jobs, hook_name);
749
0
      if (event_jobs)
750
0
        options->jobs = (unsigned int)(uintptr_t)event_jobs;
751
0
    } else {
752
0
      char *key;
753
754
0
      resolve_hook_config_jobs(r, "hook.jobs", &options->jobs);
755
756
0
      key = xstrfmt("hook.%s.jobs", hook_name);
757
0
      resolve_hook_config_jobs(r, key, &options->jobs);
758
0
      free(key);
759
0
    }
760
0
  }
761
762
  /*
763
   * Cap to serial any configured hook not marked as parallel = true.
764
   * This enforces the parallel = false default, even for "traditional"
765
   * hooks from the hookdir which cannot be marked parallel = true.
766
   * The same restriction applies whether jobs came from hook.jobs or
767
   * hook.<event>.jobs.
768
   */
769
0
  for (size_t i = 0; i < hook_list->nr; i++) {
770
0
    struct hook *h = hook_list->items[i].util;
771
0
    if (h->kind == HOOK_CONFIGURED && !h->parallel) {
772
0
      options->jobs = 1;
773
0
      break;
774
0
    }
775
0
  }
776
777
0
cleanup:
778
0
  merge_output_if_parallel(options);
779
0
  warn_non_parallel_hooks_override(options->jobs, hook_list);
780
0
  return options->jobs;
781
0
}
782
783
int run_hooks_opt(struct repository *r, const char *hook_name,
784
      struct run_hooks_opt *options)
785
0
{
786
0
  struct string_list *hook_list = list_hooks(r, hook_name, options);
787
0
  struct hook_cb_data cb_data = {
788
0
    .rc = 0,
789
0
    .hook_name = hook_name,
790
0
    .hook_command_list = hook_list,
791
0
    .options = options,
792
0
  };
793
0
  int ret = 0;
794
0
  unsigned int jobs = get_hook_jobs(r, options, hook_name, hook_list);
795
0
  const struct run_process_parallel_opts opts = {
796
0
    .tr2_category = "hook",
797
0
    .tr2_label = hook_name,
798
799
0
    .processes = jobs,
800
0
    .ungroup = jobs == 1,
801
802
0
    .get_next_task = pick_next_hook,
803
0
    .start_failure = notify_start_failure,
804
0
    .feed_pipe = options->feed_pipe,
805
0
    .task_finished = notify_hook_finished,
806
807
0
    .data = &cb_data,
808
0
  };
809
810
0
  if (!options)
811
0
    BUG("a struct run_hooks_opt must be provided to run_hooks");
812
813
0
  if (options->path_to_stdin && options->feed_pipe)
814
0
    BUG("options path_to_stdin and feed_pipe are mutually exclusive");
815
816
  /*
817
   * Ensure cb_data copy and free functions are either provided together,
818
   * or neither one is provided.
819
   */
820
0
  if (!options->feed_pipe_cb_data_alloc != !options->feed_pipe_cb_data_free)
821
0
    BUG("feed_pipe_cb_data_alloc and feed_pipe_cb_data_free must be set together");
822
823
0
  if (options->invoked_hook)
824
0
    *options->invoked_hook = 0;
825
826
0
  if (!cb_data.hook_command_list->nr) {
827
0
    if (options->error_if_missing)
828
0
      ret = error("cannot find a hook named %s", hook_name);
829
0
    goto cleanup;
830
0
  }
831
832
0
  run_processes_parallel(&opts);
833
0
  ret = cb_data.rc;
834
0
cleanup:
835
0
  string_list_clear_func(cb_data.hook_command_list, hook_free);
836
0
  free(cb_data.hook_command_list);
837
0
  run_hooks_opt_clear(options);
838
0
  return ret;
839
0
}
840
841
int run_hooks(struct repository *r, const char *hook_name)
842
0
{
843
0
  struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
844
845
0
  return run_hooks_opt(r, hook_name, &opt);
846
0
}
847
848
int run_hooks_l(struct repository *r, const char *hook_name, ...)
849
0
{
850
0
  struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
851
0
  va_list ap;
852
0
  const char *arg;
853
854
0
  va_start(ap, hook_name);
855
0
  while ((arg = va_arg(ap, const char *)))
856
0
    strvec_push(&opt.args, arg);
857
0
  va_end(ap);
858
859
0
  return run_hooks_opt(r, hook_name, &opt);
860
0
}