@@ -120,24 +120,26 @@ static void unsorted_string_list_remove(struct string_list *list,
120120
121121/*
122122 * Cache entry stored as the .util pointer of string_list items inside the
123- * hook config cache. For now carries only the command for the hook. Next
124- * commits will add more data.
123+ * hook config cache. Carries both the resolved command and the parallel flag.
125124 */
126125struct hook_config_cache_entry {
127126 char * command ;
127+ unsigned int parallel :1 ;
128128};
129129
130130/*
131131 * Callback struct to collect all hook.* keys in a single config pass.
132132 * commands: friendly-name to command map.
133133 * event_hooks: event-name to list of friendly-names map.
134134 * disabled_hooks: set of friendly-names with hook.name.enabled = false.
135+ * parallel_hooks: friendly-name to parallel flag.
135136 * jobs: value of the global hook.jobs key. Defaults to 0 if unset.
136137 */
137138struct hook_all_config_cb {
138139 struct strmap commands ;
139140 struct strmap event_hooks ;
140141 struct string_list disabled_hooks ;
142+ struct strmap parallel_hooks ;
141143 unsigned int jobs ;
142144};
143145
@@ -216,6 +218,10 @@ static int hook_config_lookup_all(const char *key, const char *value,
216218 default :
217219 break ; /* ignore unrecognised values */
218220 }
221+ } else if (!strcmp (subkey , "parallel" )) {
222+ int v = git_parse_maybe_bool (value );
223+ if (v >= 0 )
224+ strmap_put (& data -> parallel_hooks , hook_name , (void * )(uintptr_t )v );
219225 }
220226
221227 free (hook_name );
@@ -259,6 +265,7 @@ static void build_hook_config_map(struct repository *r,
259265 strmap_init (& cb_data .commands );
260266 strmap_init (& cb_data .event_hooks );
261267 string_list_init_dup (& cb_data .disabled_hooks );
268+ strmap_init (& cb_data .parallel_hooks );
262269
263270 /* Parse all configs in one run, capturing hook.* including hook.jobs. */
264271 repo_config (r , hook_config_lookup_all , & cb_data );
@@ -273,6 +280,7 @@ static void build_hook_config_map(struct repository *r,
273280 for (size_t i = 0 ; i < hook_names -> nr ; i ++ ) {
274281 const char * hname = hook_names -> items [i ].string ;
275282 struct hook_config_cache_entry * entry ;
283+ void * par = strmap_get (& cb_data .parallel_hooks , hname );
276284 char * command ;
277285
278286 /* filter out disabled hooks */
@@ -289,6 +297,7 @@ static void build_hook_config_map(struct repository *r,
289297 /* util stores a cache entry; owned by the cache. */
290298 CALLOC_ARRAY (entry , 1 );
291299 entry -> command = xstrdup (command );
300+ entry -> parallel = par ? (int )(uintptr_t )par : 0 ;
292301 string_list_append (hooks , hname )-> util = entry ;
293302 }
294303
@@ -298,6 +307,7 @@ static void build_hook_config_map(struct repository *r,
298307 cache -> jobs = cb_data .jobs ;
299308
300309 strmap_clear (& cb_data .commands , 1 );
310+ strmap_clear (& cb_data .parallel_hooks , 0 ); /* values are uintptr_t, not heap ptrs */
301311 string_list_clear (& cb_data .disabled_hooks , 0 );
302312 strmap_for_each_entry (& cb_data .event_hooks , & iter , e ) {
303313 string_list_clear (e -> value , 0 );
@@ -364,6 +374,7 @@ static void list_hooks_add_configured(struct repository *r,
364374 hook -> kind = HOOK_CONFIGURED ;
365375 hook -> u .configured .friendly_name = xstrdup (friendly_name );
366376 hook -> u .configured .command = xstrdup (entry -> command );
377+ hook -> parallel = entry -> parallel ;
367378
368379 string_list_append (list , friendly_name )-> util = hook ;
369380 }
@@ -499,21 +510,67 @@ static void run_hooks_opt_clear(struct run_hooks_opt *options)
499510 strvec_clear (& options -> args );
500511}
501512
513+ /* Determine how many jobs to use for hook execution. */
514+ static unsigned int get_hook_jobs (struct repository * r ,
515+ struct run_hooks_opt * options ,
516+ struct string_list * hook_list )
517+ {
518+ unsigned int jobs ;
519+
520+ /*
521+ * Hooks needing separate output streams must run sequentially. Next
522+ * commits will add an extension to allow parallelizing these as well.
523+ */
524+ if (!options -> stdout_to_stderr )
525+ return 1 ;
526+
527+ /* An explicit job count (FORCE_SERIAL jobs=1, or -j from CLI). */
528+ if (options -> jobs )
529+ return options -> jobs ;
530+
531+ /*
532+ * Use hook.jobs from the already-parsed config cache (in-repo), or
533+ * fall back to a direct config lookup (out-of-repo). Default to 1.
534+ */
535+ if (r && r -> gitdir && r -> hook_config_cache )
536+ /* Use the already-parsed cache (in-repo) */
537+ jobs = r -> hook_config_cache -> jobs ? r -> hook_config_cache -> jobs : 1 ;
538+ else
539+ /* No cache present (out-of-repo call), use direct cfg lookup */
540+ jobs = repo_config_get_uint (r , "hook.jobs" , & jobs ) ? 1 : jobs ;
541+
542+ /*
543+ * Cap to serial any configured hook not marked as parallel = true.
544+ * This enforces the parallel = false default, even for "traditional"
545+ * hooks from the hookdir which cannot be marked parallel = true.
546+ */
547+ for (size_t i = 0 ; jobs > 1 && i < hook_list -> nr ; i ++ ) {
548+ struct hook * h = hook_list -> items [i ].util ;
549+ if (h -> kind == HOOK_CONFIGURED && !h -> parallel )
550+ jobs = 1 ;
551+ }
552+
553+ return jobs ;
554+ }
555+
502556int run_hooks_opt (struct repository * r , const char * hook_name ,
503557 struct run_hooks_opt * options )
504558{
559+ struct string_list * hook_list = list_hooks (r , hook_name , options );
505560 struct hook_cb_data cb_data = {
506561 .rc = 0 ,
507562 .hook_name = hook_name ,
563+ .hook_command_list = hook_list ,
508564 .options = options ,
509565 };
510566 int ret = 0 ;
567+ unsigned int jobs = get_hook_jobs (r , options , hook_list );
511568 const struct run_process_parallel_opts opts = {
512569 .tr2_category = "hook" ,
513570 .tr2_label = hook_name ,
514571
515- .processes = options -> jobs ,
516- .ungroup = options -> jobs == 1 ,
572+ .processes = jobs ,
573+ .ungroup = jobs == 1 ,
517574
518575 .get_next_task = pick_next_hook ,
519576 .start_failure = notify_start_failure ,
@@ -529,9 +586,6 @@ int run_hooks_opt(struct repository *r, const char *hook_name,
529586 if (options -> path_to_stdin && options -> feed_pipe )
530587 BUG ("options path_to_stdin and feed_pipe are mutually exclusive" );
531588
532- if (!options -> jobs )
533- BUG ("run_hooks_opt must be called with options.jobs >= 1" );
534-
535589 /*
536590 * Ensure cb_data copy and free functions are either provided together,
537591 * or neither one is provided.
@@ -543,7 +597,6 @@ int run_hooks_opt(struct repository *r, const char *hook_name,
543597 if (options -> invoked_hook )
544598 * options -> invoked_hook = 0 ;
545599
546- cb_data .hook_command_list = list_hooks (r , hook_name , options );
547600 if (!cb_data .hook_command_list -> nr ) {
548601 if (options -> error_if_missing )
549602 ret = error ("cannot find a hook named %s" , hook_name );
0 commit comments