116
char *command;
117
enum config_scope scope;
118
bool disabled;
119
+ bool parallel;
120
};
121
122
/*
124
* commands: friendly-name to command map.
125
* event_hooks: event-name to list of friendly-names map.
126
* disabled_hooks: set of friendly-names with hook.<friendly-name>.enabled = false.
127
+ * parallel_hooks: friendly-name to parallel flag.
128
* jobs: value of the global hook.jobs key. Defaults to 0 if unset (stored in r->hook_jobs).
129
*/
130
struct hook_all_config_cb {
131
struct strmap commands;
132
struct strmap event_hooks;
133
struct string_list disabled_hooks;
134
+ struct strmap parallel_hooks;
135
unsigned int jobs;
136
};
137
222
default:
223
break; /* ignore unrecognised values */
224
}
225
+ } else if (!strcmp(subkey, "parallel")) {
226
+ int v = git_parse_maybe_bool(value);
227
+ if (v >= 0)
228
+ strmap_put(&data->parallel_hooks, hook_name,
229
+ (void *)(uintptr_t)v);
230
+ else
231
+ warning(_("hook.%s.parallel must be a boolean,"
232
+ " ignoring: '%s'"),
233
+ hook_name, value);
234
}
235
236
free(hook_name);
275
strmap_init(&cb_data.commands);
276
strmap_init(&cb_data.event_hooks);
277
string_list_init_dup(&cb_data.disabled_hooks);
278
+ strmap_init(&cb_data.parallel_hooks);
279
280
/* Parse all configs in one run, capturing hook.* including hook.jobs. */
281
repo_config(r, hook_config_lookup_all, &cb_data);
295
struct hook_config_cache_entry *entry;
296
char *command;
297
298
+ bool is_par = !!strmap_get(&cb_data.parallel_hooks, hname);
299
bool is_disabled =
300
!!unsorted_string_list_lookup(
301
&cb_data.disabled_hooks, hname);
316
entry->command = xstrdup_or_null(command);
317
entry->scope = scope;
318
entry->disabled = is_disabled;
319
+ entry->parallel = is_par;
320
string_list_append(hooks, hname)->util = entry;
321
}
322
327
r->hook_jobs = cb_data.jobs;
328
329
strmap_clear(&cb_data.commands, 1);
330
+ strmap_clear(&cb_data.parallel_hooks, 0); /* values are uintptr_t, not heap ptrs */
331
string_list_clear(&cb_data.disabled_hooks, 0);
332
strmap_for_each_entry(&cb_data.event_hooks, &iter, e) {
333
string_list_clear(e->value, 0);
405
entry->command ? xstrdup(entry->command) : NULL;
406
hook->u.configured.scope = entry->scope;
407
hook->u.configured.disabled = entry->disabled;
408
+ hook->parallel = entry->parallel;
409
410
string_list_append(list, friendly_name)->util = hook;
411
}
555
strvec_clear(&options->args);
556
}
557
558
+/* Determine how many jobs to use for hook execution. */
559
+static unsigned int get_hook_jobs(struct repository *r,
560
+ struct run_hooks_opt *options,
561
+ struct string_list *hook_list)
562
+{
563
+ /*
564
+ * Hooks needing separate output streams must run sequentially.
565
+ * Next commit will allow parallelizing these as well.
566
+ */
567
+ if (!options->stdout_to_stderr)
568
+ return 1;
569
+
570
+ /*
571
+ * An explicit job count overrides everything else: this covers both
572
+ * FORCE_SERIAL callers (for hooks that must never run in parallel)
573
+ * and the -j flag from the CLI. The CLI override is intentional: users
574
+ * may want to serialize hooks declared parallel or to parallelize more
575
+ * aggressively than the default.
576
+ */
577
+ if (options->jobs)
578
+ return options->jobs;
579
+
580
+ /*
581
+ * Use hook.jobs from the already-parsed config cache (in-repo), or
582
+ * fallback to a direct config lookup (out-of-repo).
583
+ * Default to 1 (serial execution) on failure.
584
+ */
585
+ options->jobs = 1;
586
+ if (r) {
587
+ if (r->gitdir && r->hook_config_cache && r->hook_jobs)
588
+ options->jobs = r->hook_jobs;
589
+ else
590
+ repo_config_get_uint(r, "hook.jobs", &options->jobs);
591
+ }
592
+
593
+ /*
594
+ * Cap to serial any configured hook not marked as parallel = true.
595
+ * This enforces the parallel = false default, even for "traditional"
596
+ * hooks from the hookdir which cannot be marked parallel = true.
597
+ */
598
+ for (size_t i = 0; i < hook_list->nr; i++) {
599
+ struct hook *h = hook_list->items[i].util;
600
+ if (h->kind == HOOK_CONFIGURED && !h->parallel) {
601
+ options->jobs = 1;
602
+ break;
603
+ }
604
+ }
605
+
606
+ return options->jobs;
607
+}
608
+
609
int run_hooks_opt(struct repository *r, const char *hook_name,
610
struct run_hooks_opt *options)
611
{
612
+ struct string_list *hook_list = list_hooks(r, hook_name, options);
613
struct hook_cb_data cb_data = {
614
.rc = 0,
615
.hook_name = hook_name,
616
+ .hook_command_list = hook_list,
617
.options = options,
618
};
619
int ret = 0;
620
+ unsigned int jobs = get_hook_jobs(r, options, hook_list);
621
const struct run_process_parallel_opts opts = {
622
.tr2_category = "hook",
623
.tr2_label = hook_name,
624
554
- .processes = options->jobs,
555
- .ungroup = options->jobs == 1,
625
+ .processes = jobs,
626
+ .ungroup = jobs == 1,
627
628
.get_next_task = pick_next_hook,
629
.start_failure = notify_start_failure,
639
if (options->path_to_stdin && options->feed_pipe)
640
BUG("options path_to_stdin and feed_pipe are mutually exclusive");
641
571
- if (!options->jobs)
572
- BUG("run_hooks_opt must be called with options.jobs >= 1");
573
-
642
/*
643
* Ensure cb_data copy and free functions are either provided together,
644
* or neither one is provided.
649
if (options->invoked_hook)
650
*options->invoked_hook = 0;
651
584
- cb_data.hook_command_list = list_hooks(r, hook_name, options);
652
if (!cb_data.hook_command_list->nr) {
653
if (options->error_if_missing)
654
ret = error("cannot find a hook named %s", hook_name);