4
#include "gettext.h"
5
#include "hook.h"
6
#include "path.h"
7
+#include "parse.h"
8
#include "run-command.h"
9
#include "config.h"
10
#include "strbuf.h"
11
+#include "strmap.h"
12
#include "environment.h"
13
#include "setup.h"
14
56
57
if (h->kind == HOOK_TRADITIONAL)
58
free((void *)h->u.traditional.path);
59
+ else if (h->kind == HOOK_CONFIGURED) {
60
+ free((void *)h->u.configured.friendly_name);
61
+ free((void *)h->u.configured.command);
62
+ }
63
64
if (cb_data_free)
65
cb_data_free(h->feed_pipe_cb_data);
107
string_list_append(hook_list, hook_path)->util = h;
108
}
109
110
+static void unsorted_string_list_remove(struct string_list *list,
111
+ const char *str)
112
+{
113
+ struct string_list_item *item = unsorted_string_list_lookup(list, str);
114
+ if (item)
115
+ unsorted_string_list_delete_item(list, item - list->items, 0);
116
+}
117
+
118
+/*
119
+ * Callback struct to collect all hook.* keys in a single config pass.
120
+ * commands: friendly-name to command map.
121
+ * event_hooks: event-name to list of friendly-names map.
122
+ * disabled_hooks: set of friendly-names with hook.name.enabled = false.
123
+ */
124
+struct hook_all_config_cb {
125
+ struct strmap commands;
126
+ struct strmap event_hooks;
127
+ struct string_list disabled_hooks;
128
+};
129
+
130
+/* repo_config() callback that collects all hook.* configuration in one pass. */
131
+static int hook_config_lookup_all(const char *key, const char *value,
132
+ const struct config_context *ctx UNUSED,
133
+ void *cb_data)
134
+{
135
+ struct hook_all_config_cb *data = cb_data;
136
+ const char *name, *subkey;
137
+ char *hook_name;
138
+ size_t name_len = 0;
139
+
140
+ if (parse_config_key(key, "hook", &name, &name_len, &subkey))
141
+ return 0;
142
+
143
+ if (!value)
144
+ return config_error_nonbool(key);
145
+
146
+ /* Extract name, ensuring it is null-terminated. */
147
+ hook_name = xmemdupz(name, name_len);
148
+
149
+ if (!strcmp(subkey, "event")) {
150
+ struct string_list *hooks =
151
+ strmap_get(&data->event_hooks, value);
152
+
153
+ if (!hooks) {
154
+ hooks = xcalloc(1, sizeof(*hooks));
155
+ string_list_init_dup(hooks);
156
+ strmap_put(&data->event_hooks, value, hooks);
157
+ }
158
+
159
+ /* Re-insert if necessary to preserve last-seen order. */
160
+ unsorted_string_list_remove(hooks, hook_name);
161
+ string_list_append(hooks, hook_name);
162
+ } else if (!strcmp(subkey, "command")) {
163
+ /* Store command overwriting the old value */
164
+ char *old = strmap_put(&data->commands, hook_name,
165
+ xstrdup(value));
166
+ free(old);
167
+ }
168
+
169
+ free(hook_name);
170
+ return 0;
171
+}
172
+
173
+/*
174
+ * The hook config cache maps each hook event name to a string_list where
175
+ * every item's string is the hook's friendly-name and its util pointer is
176
+ * the corresponding command string. Both strings are owned by the map.
177
+ *
178
+ * Disabled hooks and hooks missing a command are already filtered out at
179
+ * parse time, so callers can iterate the list directly.
180
+ */
181
+void hook_cache_clear(struct strmap *cache)
182
+{
183
+ struct hashmap_iter iter;
184
+ struct strmap_entry *e;
185
+
186
+ strmap_for_each_entry(cache, &iter, e) {
187
+ struct string_list *hooks = e->value;
188
+ string_list_clear(hooks, 1); /* free util (command) pointers */
189
+ free(hooks);
190
+ }
191
+ strmap_clear(cache, 0);
192
+}
193
+
194
+/* Populate `cache` with the complete hook configuration */
195
+static void build_hook_config_map(struct repository *r, struct strmap *cache)
196
+{
197
+ struct hook_all_config_cb cb_data;
198
+ struct hashmap_iter iter;
199
+ struct strmap_entry *e;
200
+
201
+ strmap_init(&cb_data.commands);
202
+ strmap_init(&cb_data.event_hooks);
203
+ string_list_init_dup(&cb_data.disabled_hooks);
204
+
205
+ /* Parse all configs in one run. */
206
+ repo_config(r, hook_config_lookup_all, &cb_data);
207
+
208
+ /* Construct the cache from parsed configs. */
209
+ strmap_for_each_entry(&cb_data.event_hooks, &iter, e) {
210
+ struct string_list *hook_names = e->value;
211
+ struct string_list *hooks = xcalloc(1, sizeof(*hooks));
212
+
213
+ string_list_init_dup(hooks);
214
+
215
+ for (size_t i = 0; i < hook_names->nr; i++) {
216
+ const char *hname = hook_names->items[i].string;
217
+ char *command;
218
+
219
+ command = strmap_get(&cb_data.commands, hname);
220
+ if (!command)
221
+ die(_("'hook.%s.command' must be configured or "
222
+ "'hook.%s.event' must be removed;"
223
+ " aborting."), hname, hname);
224
+
225
+ /* util stores the command; owned by the cache. */
226
+ string_list_append(hooks, hname)->util =
227
+ xstrdup(command);
228
+ }
229
+
230
+ strmap_put(cache, e->key, hooks);
231
+ }
232
+
233
+ strmap_clear(&cb_data.commands, 1);
234
+ string_list_clear(&cb_data.disabled_hooks, 0);
235
+ strmap_for_each_entry(&cb_data.event_hooks, &iter, e) {
236
+ string_list_clear(e->value, 0);
237
+ free(e->value);
238
+ }
239
+ strmap_clear(&cb_data.event_hooks, 0);
240
+}
241
+
242
+/* Return the hook config map for `r`, populating it first if needed. */
243
+static struct strmap *get_hook_config_cache(struct repository *r)
244
+{
245
+ struct strmap *cache = NULL;
246
+
247
+ if (r) {
248
+ /*
249
+ * For in-repo calls, the map is stored in r->hook_config_cache,
250
+ * so repeated invocations don't parse the configs, so allocate
251
+ * it just once on the first call.
252
+ */
253
+ if (!r->hook_config_cache) {
254
+ r->hook_config_cache = xcalloc(1, sizeof(*cache));
255
+ strmap_init(r->hook_config_cache);
256
+ build_hook_config_map(r, r->hook_config_cache);
257
+ }
258
+ cache = r->hook_config_cache;
259
+ }
260
+
261
+ return cache;
262
+}
263
+
264
+static void list_hooks_add_configured(struct repository *r,
265
+ const char *hookname,
266
+ struct string_list *list,
267
+ struct run_hooks_opt *options)
268
+{
269
+ struct strmap *cache = get_hook_config_cache(r);
270
+ struct string_list *configured_hooks = strmap_get(cache, hookname);
271
+
272
+ /* Iterate through configured hooks and initialize internal states */
273
+ for (size_t i = 0; configured_hooks && i < configured_hooks->nr; i++) {
274
+ const char *friendly_name = configured_hooks->items[i].string;
275
+ const char *command = configured_hooks->items[i].util;
276
+ struct hook *hook = xcalloc(1, sizeof(struct hook));
277
+
278
+ if (options && options->feed_pipe_cb_data_alloc)
279
+ hook->feed_pipe_cb_data =
280
+ options->feed_pipe_cb_data_alloc(
281
+ options->feed_pipe_ctx);
282
+
283
+ hook->kind = HOOK_CONFIGURED;
284
+ hook->u.configured.friendly_name = xstrdup(friendly_name);
285
+ hook->u.configured.command = xstrdup(command);
286
+
287
+ string_list_append(list, friendly_name)->util = hook;
288
+ }
289
+}
290
+
291
struct string_list *list_hooks(struct repository *r, const char *hookname,
292
struct run_hooks_opt *options)
293
{
299
hook_head = xmalloc(sizeof(struct string_list));
300
string_list_init_dup(hook_head);
301
302
+ /* Add hooks from the config, e.g. hook.myhook.event = pre-commit */
303
+ list_hooks_add_configured(r, hookname, hook_head, options);
304
+
305
/* Add the default "traditional" hooks from hookdir. */
306
list_hooks_add_default(r, hookname, hook_head, options);
307
354
cp->dir = hook_cb->options->dir;
355
356
/* Add hook exec paths or commands */
167
- if (h->kind == HOOK_TRADITIONAL)
357
+ if (h->kind == HOOK_TRADITIONAL) {
358
strvec_push(&cp->args, h->u.traditional.path);
359
+ } else if (h->kind == HOOK_CONFIGURED) {
360
+ /* to enable oneliners, let config-specified hooks run in shell. */
361
+ cp->use_shell = true;
362
+ strvec_push(&cp->args, h->u.configured.command);
363
+ }
364
365
if (!cp->args.nr)
366
BUG("hook must have at least one command or exec path");