]>
Commit | Line | Data |
---|---|---|
9385b5d7 | 1 | #include "cache.h" |
f0a90b4e | 2 | #include "string-list.h" |
85039fb6 | 3 | #include "run-command.h" |
61cfef4c | 4 | #include "commit.h" |
e1f89863 | 5 | #include "tempfile.h" |
b1d78d77 | 6 | #include "trailer.h" |
8966a394 | 7 | #include "list.h" |
9385b5d7 CC |
8 | /* |
9 | * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org> | |
10 | */ | |
11 | ||
12 | enum action_where { WHERE_END, WHERE_AFTER, WHERE_BEFORE, WHERE_START }; | |
13 | enum action_if_exists { EXISTS_ADD_IF_DIFFERENT_NEIGHBOR, EXISTS_ADD_IF_DIFFERENT, | |
14 | EXISTS_ADD, EXISTS_REPLACE, EXISTS_DO_NOTHING }; | |
15 | enum action_if_missing { MISSING_ADD, MISSING_DO_NOTHING }; | |
16 | ||
17 | struct conf_info { | |
18 | char *name; | |
19 | char *key; | |
20 | char *command; | |
21 | enum action_where where; | |
22 | enum action_if_exists if_exists; | |
23 | enum action_if_missing if_missing; | |
24 | }; | |
25 | ||
26 | static struct conf_info default_conf_info; | |
27 | ||
28 | struct trailer_item { | |
8966a394 | 29 | struct list_head list; |
d65fd424 JT |
30 | char *token; |
31 | char *value; | |
cc71b0de JT |
32 | }; |
33 | ||
34 | struct arg_item { | |
35 | struct list_head list; | |
36 | char *token; | |
37 | char *value; | |
9385b5d7 CC |
38 | struct conf_info conf; |
39 | }; | |
40 | ||
8966a394 | 41 | static LIST_HEAD(conf_head); |
9385b5d7 CC |
42 | |
43 | static char *separators = ":"; | |
44 | ||
85039fb6 CC |
45 | #define TRAILER_ARG_STRING "$ARG" |
46 | ||
8966a394 JT |
47 | /* Iterate over the elements of the list. */ |
48 | #define list_for_each_dir(pos, head, is_reverse) \ | |
49 | for (pos = is_reverse ? (head)->prev : (head)->next; \ | |
50 | pos != (head); \ | |
51 | pos = is_reverse ? pos->prev : pos->next) | |
52 | ||
9385b5d7 CC |
53 | static int after_or_end(enum action_where where) |
54 | { | |
55 | return (where == WHERE_AFTER) || (where == WHERE_END); | |
56 | } | |
57 | ||
58 | /* | |
59 | * Return the length of the string not including any final | |
60 | * punctuation. E.g., the input "Signed-off-by:" would return | |
61 | * 13, stripping the trailing punctuation but retaining | |
62 | * internal punctuation. | |
63 | */ | |
64 | static size_t token_len_without_separator(const char *token, size_t len) | |
65 | { | |
66 | while (len > 0 && !isalnum(token[len - 1])) | |
67 | len--; | |
68 | return len; | |
69 | } | |
70 | ||
cc71b0de | 71 | static int same_token(struct trailer_item *a, struct arg_item *b) |
9385b5d7 CC |
72 | { |
73 | size_t a_len = token_len_without_separator(a->token, strlen(a->token)); | |
74 | size_t b_len = token_len_without_separator(b->token, strlen(b->token)); | |
75 | size_t min_len = (a_len > b_len) ? b_len : a_len; | |
76 | ||
77 | return !strncasecmp(a->token, b->token, min_len); | |
78 | } | |
79 | ||
cc71b0de | 80 | static int same_value(struct trailer_item *a, struct arg_item *b) |
9385b5d7 CC |
81 | { |
82 | return !strcasecmp(a->value, b->value); | |
83 | } | |
84 | ||
cc71b0de | 85 | static int same_trailer(struct trailer_item *a, struct arg_item *b) |
9385b5d7 CC |
86 | { |
87 | return same_token(a, b) && same_value(a, b); | |
88 | } | |
4103818d | 89 | |
2013d850 CC |
90 | static inline int contains_only_spaces(const char *str) |
91 | { | |
92 | const char *s = str; | |
93 | while (*s && isspace(*s)) | |
94 | s++; | |
95 | return !*s; | |
96 | } | |
97 | ||
85039fb6 CC |
98 | static inline void strbuf_replace(struct strbuf *sb, const char *a, const char *b) |
99 | { | |
100 | const char *ptr = strstr(sb->buf, a); | |
101 | if (ptr) | |
102 | strbuf_splice(sb, ptr - sb->buf, strlen(a), b, strlen(b)); | |
103 | } | |
104 | ||
4103818d | 105 | static void free_trailer_item(struct trailer_item *item) |
cc71b0de JT |
106 | { |
107 | free(item->token); | |
108 | free(item->value); | |
109 | free(item); | |
110 | } | |
111 | ||
112 | static void free_arg_item(struct arg_item *item) | |
4103818d CC |
113 | { |
114 | free(item->conf.name); | |
115 | free(item->conf.key); | |
116 | free(item->conf.command); | |
d65fd424 JT |
117 | free(item->token); |
118 | free(item->value); | |
4103818d CC |
119 | free(item); |
120 | } | |
121 | ||
b1d78d77 CC |
122 | static char last_non_space_char(const char *s) |
123 | { | |
124 | int i; | |
125 | for (i = strlen(s) - 1; i >= 0; i--) | |
126 | if (!isspace(s[i])) | |
127 | return s[i]; | |
128 | return '\0'; | |
129 | } | |
130 | ||
d0d2344a | 131 | static void print_tok_val(FILE *outfile, const char *tok, const char *val) |
b1d78d77 CC |
132 | { |
133 | char c = last_non_space_char(tok); | |
134 | if (!c) | |
135 | return; | |
136 | if (strchr(separators, c)) | |
d0d2344a | 137 | fprintf(outfile, "%s%s\n", tok, val); |
b1d78d77 | 138 | else |
d0d2344a | 139 | fprintf(outfile, "%s%c %s\n", tok, separators[0], val); |
b1d78d77 CC |
140 | } |
141 | ||
8966a394 | 142 | static void print_all(FILE *outfile, struct list_head *head, int trim_empty) |
b1d78d77 | 143 | { |
8966a394 | 144 | struct list_head *pos; |
b1d78d77 | 145 | struct trailer_item *item; |
8966a394 JT |
146 | list_for_each(pos, head) { |
147 | item = list_entry(pos, struct trailer_item, list); | |
b1d78d77 | 148 | if (!trim_empty || strlen(item->value) > 0) |
d0d2344a | 149 | print_tok_val(outfile, item->token, item->value); |
b1d78d77 CC |
150 | } |
151 | } | |
152 | ||
cc71b0de JT |
153 | static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok) |
154 | { | |
155 | struct trailer_item *new = xcalloc(sizeof(*new), 1); | |
156 | new->token = arg_tok->token; | |
157 | new->value = arg_tok->value; | |
158 | arg_tok->token = arg_tok->value = NULL; | |
159 | free_arg_item(arg_tok); | |
160 | return new; | |
161 | } | |
162 | ||
4103818d | 163 | static void add_arg_to_input_list(struct trailer_item *on_tok, |
cc71b0de | 164 | struct arg_item *arg_tok) |
8966a394 | 165 | { |
cc71b0de JT |
166 | int aoe = after_or_end(arg_tok->conf.where); |
167 | struct trailer_item *to_add = trailer_from_arg(arg_tok); | |
168 | if (aoe) | |
169 | list_add(&to_add->list, &on_tok->list); | |
8966a394 | 170 | else |
cc71b0de | 171 | list_add_tail(&to_add->list, &on_tok->list); |
4103818d CC |
172 | } |
173 | ||
174 | static int check_if_different(struct trailer_item *in_tok, | |
cc71b0de | 175 | struct arg_item *arg_tok, |
8966a394 JT |
176 | int check_all, |
177 | struct list_head *head) | |
4103818d CC |
178 | { |
179 | enum action_where where = arg_tok->conf.where; | |
8966a394 | 180 | struct list_head *next_head; |
4103818d | 181 | do { |
4103818d CC |
182 | if (same_trailer(in_tok, arg_tok)) |
183 | return 0; | |
184 | /* | |
185 | * if we want to add a trailer after another one, | |
186 | * we have to check those before this one | |
187 | */ | |
8966a394 JT |
188 | next_head = after_or_end(where) ? in_tok->list.prev |
189 | : in_tok->list.next; | |
190 | if (next_head == head) | |
191 | break; | |
192 | in_tok = list_entry(next_head, struct trailer_item, list); | |
4103818d CC |
193 | } while (check_all); |
194 | return 1; | |
195 | } | |
196 | ||
d65fd424 | 197 | static char *apply_command(const char *command, const char *arg) |
85039fb6 CC |
198 | { |
199 | struct strbuf cmd = STRBUF_INIT; | |
200 | struct strbuf buf = STRBUF_INIT; | |
b226293b | 201 | struct child_process cp = CHILD_PROCESS_INIT; |
85039fb6 | 202 | const char *argv[] = {NULL, NULL}; |
d65fd424 | 203 | char *result; |
85039fb6 CC |
204 | |
205 | strbuf_addstr(&cmd, command); | |
206 | if (arg) | |
207 | strbuf_replace(&cmd, TRAILER_ARG_STRING, arg); | |
208 | ||
209 | argv[0] = cmd.buf; | |
85039fb6 CC |
210 | cp.argv = argv; |
211 | cp.env = local_repo_env; | |
212 | cp.no_stdin = 1; | |
85039fb6 CC |
213 | cp.use_shell = 1; |
214 | ||
c5eadcaa | 215 | if (capture_command(&cp, &buf, 1024)) { |
13ad56f8 | 216 | error(_("running trailer command '%s' failed"), cmd.buf); |
85039fb6 CC |
217 | strbuf_release(&buf); |
218 | result = xstrdup(""); | |
c5eadcaa JK |
219 | } else { |
220 | strbuf_trim(&buf); | |
85039fb6 | 221 | result = strbuf_detach(&buf, NULL); |
c5eadcaa | 222 | } |
85039fb6 CC |
223 | |
224 | strbuf_release(&cmd); | |
225 | return result; | |
226 | } | |
227 | ||
cc71b0de | 228 | static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok) |
85039fb6 CC |
229 | { |
230 | if (arg_tok->conf.command) { | |
231 | const char *arg; | |
232 | if (arg_tok->value && arg_tok->value[0]) { | |
233 | arg = arg_tok->value; | |
234 | } else { | |
235 | if (in_tok && in_tok->value) | |
236 | arg = xstrdup(in_tok->value); | |
237 | else | |
238 | arg = xstrdup(""); | |
239 | } | |
240 | arg_tok->value = apply_command(arg_tok->conf.command, arg); | |
241 | free((char *)arg); | |
242 | } | |
243 | } | |
244 | ||
4103818d | 245 | static void apply_arg_if_exists(struct trailer_item *in_tok, |
cc71b0de | 246 | struct arg_item *arg_tok, |
4103818d | 247 | struct trailer_item *on_tok, |
8966a394 | 248 | struct list_head *head) |
4103818d CC |
249 | { |
250 | switch (arg_tok->conf.if_exists) { | |
251 | case EXISTS_DO_NOTHING: | |
cc71b0de | 252 | free_arg_item(arg_tok); |
4103818d CC |
253 | break; |
254 | case EXISTS_REPLACE: | |
85039fb6 | 255 | apply_item_command(in_tok, arg_tok); |
8966a394 JT |
256 | add_arg_to_input_list(on_tok, arg_tok); |
257 | list_del(&in_tok->list); | |
4103818d CC |
258 | free_trailer_item(in_tok); |
259 | break; | |
260 | case EXISTS_ADD: | |
85039fb6 | 261 | apply_item_command(in_tok, arg_tok); |
8966a394 | 262 | add_arg_to_input_list(on_tok, arg_tok); |
4103818d CC |
263 | break; |
264 | case EXISTS_ADD_IF_DIFFERENT: | |
85039fb6 | 265 | apply_item_command(in_tok, arg_tok); |
8966a394 JT |
266 | if (check_if_different(in_tok, arg_tok, 1, head)) |
267 | add_arg_to_input_list(on_tok, arg_tok); | |
4103818d | 268 | else |
cc71b0de | 269 | free_arg_item(arg_tok); |
4103818d CC |
270 | break; |
271 | case EXISTS_ADD_IF_DIFFERENT_NEIGHBOR: | |
85039fb6 | 272 | apply_item_command(in_tok, arg_tok); |
8966a394 JT |
273 | if (check_if_different(on_tok, arg_tok, 0, head)) |
274 | add_arg_to_input_list(on_tok, arg_tok); | |
4103818d | 275 | else |
cc71b0de | 276 | free_arg_item(arg_tok); |
4103818d CC |
277 | break; |
278 | } | |
279 | } | |
280 | ||
8966a394 | 281 | static void apply_arg_if_missing(struct list_head *head, |
cc71b0de | 282 | struct arg_item *arg_tok) |
4103818d | 283 | { |
4103818d | 284 | enum action_where where; |
cc71b0de | 285 | struct trailer_item *to_add; |
4103818d CC |
286 | |
287 | switch (arg_tok->conf.if_missing) { | |
288 | case MISSING_DO_NOTHING: | |
cc71b0de | 289 | free_arg_item(arg_tok); |
4103818d CC |
290 | break; |
291 | case MISSING_ADD: | |
292 | where = arg_tok->conf.where; | |
85039fb6 | 293 | apply_item_command(NULL, arg_tok); |
cc71b0de | 294 | to_add = trailer_from_arg(arg_tok); |
8966a394 | 295 | if (after_or_end(where)) |
cc71b0de | 296 | list_add_tail(&to_add->list, head); |
8966a394 | 297 | else |
cc71b0de | 298 | list_add(&to_add->list, head); |
4103818d CC |
299 | } |
300 | } | |
301 | ||
8966a394 | 302 | static int find_same_and_apply_arg(struct list_head *head, |
cc71b0de | 303 | struct arg_item *arg_tok) |
4103818d | 304 | { |
8966a394 | 305 | struct list_head *pos; |
4103818d CC |
306 | struct trailer_item *in_tok; |
307 | struct trailer_item *on_tok; | |
4103818d CC |
308 | |
309 | enum action_where where = arg_tok->conf.where; | |
310 | int middle = (where == WHERE_AFTER) || (where == WHERE_BEFORE); | |
311 | int backwards = after_or_end(where); | |
8966a394 | 312 | struct trailer_item *start_tok; |
4103818d | 313 | |
8966a394 JT |
314 | if (list_empty(head)) |
315 | return 0; | |
316 | ||
317 | start_tok = list_entry(backwards ? head->prev : head->next, | |
318 | struct trailer_item, | |
319 | list); | |
320 | ||
321 | list_for_each_dir(pos, head, backwards) { | |
322 | in_tok = list_entry(pos, struct trailer_item, list); | |
4103818d CC |
323 | if (!same_token(in_tok, arg_tok)) |
324 | continue; | |
325 | on_tok = middle ? in_tok : start_tok; | |
8966a394 | 326 | apply_arg_if_exists(in_tok, arg_tok, on_tok, head); |
4103818d CC |
327 | return 1; |
328 | } | |
329 | return 0; | |
330 | } | |
331 | ||
8966a394 JT |
332 | static void process_trailers_lists(struct list_head *head, |
333 | struct list_head *arg_head) | |
4103818d | 334 | { |
8966a394 | 335 | struct list_head *pos, *p; |
cc71b0de | 336 | struct arg_item *arg_tok; |
4103818d | 337 | |
8966a394 | 338 | list_for_each_safe(pos, p, arg_head) { |
4103818d | 339 | int applied = 0; |
cc71b0de | 340 | arg_tok = list_entry(pos, struct arg_item, list); |
4103818d | 341 | |
8966a394 | 342 | list_del(pos); |
4103818d | 343 | |
8966a394 | 344 | applied = find_same_and_apply_arg(head, arg_tok); |
4103818d CC |
345 | |
346 | if (!applied) | |
8966a394 | 347 | apply_arg_if_missing(head, arg_tok); |
4103818d CC |
348 | } |
349 | } | |
46a0613f CC |
350 | |
351 | static int set_where(struct conf_info *item, const char *value) | |
352 | { | |
353 | if (!strcasecmp("after", value)) | |
354 | item->where = WHERE_AFTER; | |
355 | else if (!strcasecmp("before", value)) | |
356 | item->where = WHERE_BEFORE; | |
357 | else if (!strcasecmp("end", value)) | |
358 | item->where = WHERE_END; | |
359 | else if (!strcasecmp("start", value)) | |
360 | item->where = WHERE_START; | |
361 | else | |
362 | return -1; | |
363 | return 0; | |
364 | } | |
365 | ||
366 | static int set_if_exists(struct conf_info *item, const char *value) | |
367 | { | |
368 | if (!strcasecmp("addIfDifferent", value)) | |
369 | item->if_exists = EXISTS_ADD_IF_DIFFERENT; | |
370 | else if (!strcasecmp("addIfDifferentNeighbor", value)) | |
371 | item->if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR; | |
372 | else if (!strcasecmp("add", value)) | |
373 | item->if_exists = EXISTS_ADD; | |
374 | else if (!strcasecmp("replace", value)) | |
375 | item->if_exists = EXISTS_REPLACE; | |
376 | else if (!strcasecmp("doNothing", value)) | |
377 | item->if_exists = EXISTS_DO_NOTHING; | |
378 | else | |
379 | return -1; | |
380 | return 0; | |
381 | } | |
382 | ||
383 | static int set_if_missing(struct conf_info *item, const char *value) | |
384 | { | |
385 | if (!strcasecmp("doNothing", value)) | |
386 | item->if_missing = MISSING_DO_NOTHING; | |
387 | else if (!strcasecmp("add", value)) | |
388 | item->if_missing = MISSING_ADD; | |
389 | else | |
390 | return -1; | |
391 | return 0; | |
392 | } | |
393 | ||
d65fd424 | 394 | static void duplicate_conf(struct conf_info *dst, const struct conf_info *src) |
46a0613f CC |
395 | { |
396 | *dst = *src; | |
397 | if (src->name) | |
398 | dst->name = xstrdup(src->name); | |
399 | if (src->key) | |
400 | dst->key = xstrdup(src->key); | |
401 | if (src->command) | |
402 | dst->command = xstrdup(src->command); | |
403 | } | |
404 | ||
cc71b0de | 405 | static struct arg_item *get_conf_item(const char *name) |
46a0613f | 406 | { |
8966a394 | 407 | struct list_head *pos; |
cc71b0de | 408 | struct arg_item *item; |
46a0613f CC |
409 | |
410 | /* Look up item with same name */ | |
8966a394 | 411 | list_for_each(pos, &conf_head) { |
cc71b0de | 412 | item = list_entry(pos, struct arg_item, list); |
46a0613f CC |
413 | if (!strcasecmp(item->conf.name, name)) |
414 | return item; | |
415 | } | |
416 | ||
417 | /* Item does not already exists, create it */ | |
cc71b0de | 418 | item = xcalloc(sizeof(*item), 1); |
46a0613f CC |
419 | duplicate_conf(&item->conf, &default_conf_info); |
420 | item->conf.name = xstrdup(name); | |
421 | ||
8966a394 | 422 | list_add_tail(&item->list, &conf_head); |
46a0613f CC |
423 | |
424 | return item; | |
425 | } | |
426 | ||
427 | enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE, | |
428 | TRAILER_IF_EXISTS, TRAILER_IF_MISSING }; | |
429 | ||
430 | static struct { | |
431 | const char *name; | |
432 | enum trailer_info_type type; | |
433 | } trailer_config_items[] = { | |
434 | { "key", TRAILER_KEY }, | |
435 | { "command", TRAILER_COMMAND }, | |
436 | { "where", TRAILER_WHERE }, | |
437 | { "ifexists", TRAILER_IF_EXISTS }, | |
438 | { "ifmissing", TRAILER_IF_MISSING } | |
439 | }; | |
440 | ||
441 | static int git_trailer_default_config(const char *conf_key, const char *value, void *cb) | |
442 | { | |
443 | const char *trailer_item, *variable_name; | |
444 | ||
445 | if (!skip_prefix(conf_key, "trailer.", &trailer_item)) | |
446 | return 0; | |
447 | ||
448 | variable_name = strrchr(trailer_item, '.'); | |
449 | if (!variable_name) { | |
450 | if (!strcmp(trailer_item, "where")) { | |
451 | if (set_where(&default_conf_info, value) < 0) | |
452 | warning(_("unknown value '%s' for key '%s'"), | |
453 | value, conf_key); | |
454 | } else if (!strcmp(trailer_item, "ifexists")) { | |
455 | if (set_if_exists(&default_conf_info, value) < 0) | |
456 | warning(_("unknown value '%s' for key '%s'"), | |
457 | value, conf_key); | |
458 | } else if (!strcmp(trailer_item, "ifmissing")) { | |
459 | if (set_if_missing(&default_conf_info, value) < 0) | |
460 | warning(_("unknown value '%s' for key '%s'"), | |
461 | value, conf_key); | |
462 | } else if (!strcmp(trailer_item, "separators")) { | |
463 | separators = xstrdup(value); | |
464 | } | |
465 | } | |
466 | return 0; | |
467 | } | |
468 | ||
469 | static int git_trailer_config(const char *conf_key, const char *value, void *cb) | |
470 | { | |
471 | const char *trailer_item, *variable_name; | |
cc71b0de | 472 | struct arg_item *item; |
46a0613f CC |
473 | struct conf_info *conf; |
474 | char *name = NULL; | |
475 | enum trailer_info_type type; | |
476 | int i; | |
477 | ||
478 | if (!skip_prefix(conf_key, "trailer.", &trailer_item)) | |
479 | return 0; | |
480 | ||
481 | variable_name = strrchr(trailer_item, '.'); | |
482 | if (!variable_name) | |
483 | return 0; | |
484 | ||
485 | variable_name++; | |
486 | for (i = 0; i < ARRAY_SIZE(trailer_config_items); i++) { | |
487 | if (strcmp(trailer_config_items[i].name, variable_name)) | |
488 | continue; | |
489 | name = xstrndup(trailer_item, variable_name - trailer_item - 1); | |
490 | type = trailer_config_items[i].type; | |
491 | break; | |
492 | } | |
493 | ||
494 | if (!name) | |
495 | return 0; | |
496 | ||
497 | item = get_conf_item(name); | |
498 | conf = &item->conf; | |
499 | free(name); | |
500 | ||
501 | switch (type) { | |
502 | case TRAILER_KEY: | |
503 | if (conf->key) | |
504 | warning(_("more than one %s"), conf_key); | |
505 | conf->key = xstrdup(value); | |
506 | break; | |
507 | case TRAILER_COMMAND: | |
508 | if (conf->command) | |
509 | warning(_("more than one %s"), conf_key); | |
510 | conf->command = xstrdup(value); | |
511 | break; | |
512 | case TRAILER_WHERE: | |
513 | if (set_where(conf, value)) | |
514 | warning(_("unknown value '%s' for key '%s'"), value, conf_key); | |
515 | break; | |
516 | case TRAILER_IF_EXISTS: | |
517 | if (set_if_exists(conf, value)) | |
518 | warning(_("unknown value '%s' for key '%s'"), value, conf_key); | |
519 | break; | |
520 | case TRAILER_IF_MISSING: | |
521 | if (set_if_missing(conf, value)) | |
522 | warning(_("unknown value '%s' for key '%s'"), value, conf_key); | |
523 | break; | |
524 | default: | |
ef1177d1 | 525 | die("BUG: trailer.c: unhandled type %d", type); |
46a0613f CC |
526 | } |
527 | return 0; | |
528 | } | |
f0a90b4e | 529 | |
cc71b0de | 530 | static const char *token_from_item(struct arg_item *item, char *tok) |
63ab3f34 JT |
531 | { |
532 | if (item->conf.key) | |
533 | return item->conf.key; | |
534 | if (tok) | |
535 | return tok; | |
536 | return item->conf.name; | |
537 | } | |
538 | ||
cc71b0de | 539 | static int token_matches_item(const char *tok, struct arg_item *item, int tok_len) |
63ab3f34 JT |
540 | { |
541 | if (!strncasecmp(tok, item->conf.name, tok_len)) | |
542 | return 1; | |
543 | return item->conf.key ? !strncasecmp(tok, item->conf.key, tok_len) : 0; | |
544 | } | |
545 | ||
546 | static int parse_trailer(struct strbuf *tok, struct strbuf *val, | |
547 | const struct conf_info **conf, const char *trailer) | |
f0a90b4e CC |
548 | { |
549 | size_t len; | |
550 | struct strbuf seps = STRBUF_INIT; | |
cc71b0de | 551 | struct arg_item *item; |
63ab3f34 JT |
552 | int tok_len; |
553 | struct list_head *pos; | |
554 | ||
f0a90b4e CC |
555 | strbuf_addstr(&seps, separators); |
556 | strbuf_addch(&seps, '='); | |
557 | len = strcspn(trailer, seps.buf); | |
558 | strbuf_release(&seps); | |
d52adf1f CC |
559 | if (len == 0) { |
560 | int l = strlen(trailer); | |
561 | while (l > 0 && isspace(trailer[l - 1])) | |
562 | l--; | |
563 | return error(_("empty trailer token in trailer '%.*s'"), l, trailer); | |
564 | } | |
f0a90b4e CC |
565 | if (len < strlen(trailer)) { |
566 | strbuf_add(tok, trailer, len); | |
567 | strbuf_trim(tok); | |
568 | strbuf_addstr(val, trailer + len + 1); | |
569 | strbuf_trim(val); | |
570 | } else { | |
571 | strbuf_addstr(tok, trailer); | |
572 | strbuf_trim(tok); | |
573 | } | |
f0a90b4e CC |
574 | |
575 | /* Lookup if the token matches something in the config */ | |
63ab3f34 | 576 | tok_len = token_len_without_separator(tok->buf, tok->len); |
cc71b0de JT |
577 | if (conf) |
578 | *conf = &default_conf_info; | |
8966a394 | 579 | list_for_each(pos, &conf_head) { |
cc71b0de | 580 | item = list_entry(pos, struct arg_item, list); |
63ab3f34 JT |
581 | if (token_matches_item(tok->buf, item, tok_len)) { |
582 | char *tok_buf = strbuf_detach(tok, NULL); | |
cc71b0de JT |
583 | if (conf) |
584 | *conf = &item->conf; | |
63ab3f34 JT |
585 | strbuf_addstr(tok, token_from_item(item, tok_buf)); |
586 | free(tok_buf); | |
587 | break; | |
588 | } | |
f0a90b4e CC |
589 | } |
590 | ||
63ab3f34 | 591 | return 0; |
f0a90b4e CC |
592 | } |
593 | ||
cc71b0de | 594 | static void add_trailer_item(struct list_head *head, char *tok, char *val) |
f0a90b4e | 595 | { |
63ab3f34 JT |
596 | struct trailer_item *new = xcalloc(sizeof(*new), 1); |
597 | new->token = tok; | |
598 | new->value = val; | |
8966a394 | 599 | list_add_tail(&new->list, head); |
f0a90b4e CC |
600 | } |
601 | ||
cc71b0de JT |
602 | static void add_arg_item(struct list_head *arg_head, char *tok, char *val, |
603 | const struct conf_info *conf) | |
604 | { | |
605 | struct arg_item *new = xcalloc(sizeof(*new), 1); | |
606 | new->token = tok; | |
607 | new->value = val; | |
608 | duplicate_conf(&new->conf, conf); | |
609 | list_add_tail(&new->list, arg_head); | |
610 | } | |
611 | ||
8966a394 JT |
612 | static void process_command_line_args(struct list_head *arg_head, |
613 | struct string_list *trailers) | |
f0a90b4e | 614 | { |
f0a90b4e | 615 | struct string_list_item *tr; |
cc71b0de | 616 | struct arg_item *item; |
63ab3f34 JT |
617 | struct strbuf tok = STRBUF_INIT; |
618 | struct strbuf val = STRBUF_INIT; | |
619 | const struct conf_info *conf; | |
8966a394 | 620 | struct list_head *pos; |
85039fb6 | 621 | |
cc71b0de | 622 | /* Add an arg item for each configured trailer with a command */ |
8966a394 | 623 | list_for_each(pos, &conf_head) { |
cc71b0de | 624 | item = list_entry(pos, struct arg_item, list); |
63ab3f34 | 625 | if (item->conf.command) |
cc71b0de JT |
626 | add_arg_item(arg_head, |
627 | xstrdup(token_from_item(item, NULL)), | |
628 | xstrdup(""), | |
629 | &item->conf); | |
85039fb6 | 630 | } |
f0a90b4e | 631 | |
cc71b0de | 632 | /* Add an arg item for each trailer on the command line */ |
f0a90b4e | 633 | for_each_string_list_item(tr, trailers) { |
63ab3f34 | 634 | if (!parse_trailer(&tok, &val, &conf, tr->string)) |
cc71b0de JT |
635 | add_arg_item(arg_head, |
636 | strbuf_detach(&tok, NULL), | |
637 | strbuf_detach(&val, NULL), | |
638 | conf); | |
f0a90b4e | 639 | } |
f0a90b4e | 640 | } |
2013d850 CC |
641 | |
642 | static struct strbuf **read_input_file(const char *file) | |
643 | { | |
644 | struct strbuf **lines; | |
645 | struct strbuf sb = STRBUF_INIT; | |
646 | ||
647 | if (file) { | |
648 | if (strbuf_read_file(&sb, file, 0) < 0) | |
649 | die_errno(_("could not read input file '%s'"), file); | |
650 | } else { | |
651 | if (strbuf_read(&sb, fileno(stdin), 0) < 0) | |
652 | die_errno(_("could not read from stdin")); | |
653 | } | |
654 | ||
655 | lines = strbuf_split(&sb, '\n'); | |
656 | ||
657 | strbuf_release(&sb); | |
658 | ||
659 | return lines; | |
660 | } | |
661 | ||
662 | /* | |
663 | * Return the (0 based) index of the start of the patch or the line | |
664 | * count if there is no patch in the message. | |
665 | */ | |
666 | static int find_patch_start(struct strbuf **lines, int count) | |
667 | { | |
668 | int i; | |
669 | ||
670 | /* Get the start of the patch part if any */ | |
671 | for (i = 0; i < count; i++) { | |
672 | if (starts_with(lines[i]->buf, "---")) | |
673 | return i; | |
674 | } | |
675 | ||
676 | return count; | |
677 | } | |
678 | ||
679 | /* | |
680 | * Return the (0 based) index of the first trailer line or count if | |
681 | * there are no trailers. Trailers are searched only in the lines from | |
682 | * index (count - 1) down to index 0. | |
683 | */ | |
684 | static int find_trailer_start(struct strbuf **lines, int count) | |
685 | { | |
5c99995d CC |
686 | int start, end_of_title, only_spaces = 1; |
687 | ||
688 | /* The first paragraph is the title and cannot be trailers */ | |
689 | for (start = 0; start < count; start++) { | |
690 | if (lines[start]->buf[0] == comment_line_char) | |
691 | continue; | |
692 | if (contains_only_spaces(lines[start]->buf)) | |
693 | break; | |
694 | } | |
695 | end_of_title = start; | |
2013d850 CC |
696 | |
697 | /* | |
698 | * Get the start of the trailers by looking starting from the end | |
699 | * for a line with only spaces before lines with one separator. | |
700 | */ | |
5c99995d | 701 | for (start = count - 1; start >= end_of_title; start--) { |
2013d850 CC |
702 | if (lines[start]->buf[0] == comment_line_char) |
703 | continue; | |
704 | if (contains_only_spaces(lines[start]->buf)) { | |
705 | if (only_spaces) | |
706 | continue; | |
707 | return start + 1; | |
708 | } | |
709 | if (strcspn(lines[start]->buf, separators) < lines[start]->len) { | |
710 | if (only_spaces) | |
711 | only_spaces = 0; | |
712 | continue; | |
713 | } | |
714 | return count; | |
715 | } | |
716 | ||
717 | return only_spaces ? count : 0; | |
718 | } | |
719 | ||
61cfef4c CC |
720 | /* Get the index of the end of the trailers */ |
721 | static int find_trailer_end(struct strbuf **lines, int patch_start) | |
722 | { | |
723 | struct strbuf sb = STRBUF_INIT; | |
724 | int i, ignore_bytes; | |
725 | ||
726 | for (i = 0; i < patch_start; i++) | |
727 | strbuf_addbuf(&sb, lines[i]); | |
728 | ignore_bytes = ignore_non_trailer(&sb); | |
729 | strbuf_release(&sb); | |
730 | for (i = patch_start - 1; i >= 0 && ignore_bytes > 0; i--) | |
731 | ignore_bytes -= lines[i]->len; | |
732 | ||
733 | return i + 1; | |
734 | } | |
735 | ||
2013d850 CC |
736 | static int has_blank_line_before(struct strbuf **lines, int start) |
737 | { | |
738 | for (;start >= 0; start--) { | |
739 | if (lines[start]->buf[0] == comment_line_char) | |
740 | continue; | |
741 | return contains_only_spaces(lines[start]->buf); | |
742 | } | |
743 | return 0; | |
744 | } | |
745 | ||
d0d2344a | 746 | static void print_lines(FILE *outfile, struct strbuf **lines, int start, int end) |
2013d850 CC |
747 | { |
748 | int i; | |
749 | for (i = start; lines[i] && i < end; i++) | |
d0d2344a | 750 | fprintf(outfile, "%s", lines[i]->buf); |
2013d850 CC |
751 | } |
752 | ||
d0d2344a TK |
753 | static int process_input_file(FILE *outfile, |
754 | struct strbuf **lines, | |
8966a394 | 755 | struct list_head *head) |
2013d850 CC |
756 | { |
757 | int count = 0; | |
61cfef4c | 758 | int patch_start, trailer_start, trailer_end, i; |
63ab3f34 JT |
759 | struct strbuf tok = STRBUF_INIT; |
760 | struct strbuf val = STRBUF_INIT; | |
2013d850 CC |
761 | |
762 | /* Get the line count */ | |
763 | while (lines[count]) | |
764 | count++; | |
765 | ||
766 | patch_start = find_patch_start(lines, count); | |
61cfef4c CC |
767 | trailer_end = find_trailer_end(lines, patch_start); |
768 | trailer_start = find_trailer_start(lines, trailer_end); | |
2013d850 CC |
769 | |
770 | /* Print lines before the trailers as is */ | |
d0d2344a | 771 | print_lines(outfile, lines, 0, trailer_start); |
2013d850 CC |
772 | |
773 | if (!has_blank_line_before(lines, trailer_start - 1)) | |
d0d2344a | 774 | fprintf(outfile, "\n"); |
2013d850 CC |
775 | |
776 | /* Parse trailer lines */ | |
61cfef4c | 777 | for (i = trailer_start; i < trailer_end; i++) { |
63ab3f34 | 778 | if (lines[i]->buf[0] != comment_line_char && |
cc71b0de | 779 | !parse_trailer(&tok, &val, NULL, lines[i]->buf)) |
63ab3f34 JT |
780 | add_trailer_item(head, |
781 | strbuf_detach(&tok, NULL), | |
cc71b0de | 782 | strbuf_detach(&val, NULL)); |
2013d850 CC |
783 | } |
784 | ||
61cfef4c | 785 | return trailer_end; |
2013d850 | 786 | } |
b1d78d77 | 787 | |
8966a394 | 788 | static void free_all(struct list_head *head) |
b1d78d77 | 789 | { |
8966a394 JT |
790 | struct list_head *pos, *p; |
791 | list_for_each_safe(pos, p, head) { | |
792 | list_del(pos); | |
793 | free_trailer_item(list_entry(pos, struct trailer_item, list)); | |
b1d78d77 CC |
794 | } |
795 | } | |
796 | ||
e1f89863 TK |
797 | static struct tempfile trailers_tempfile; |
798 | ||
799 | static FILE *create_in_place_tempfile(const char *file) | |
800 | { | |
801 | struct stat st; | |
802 | struct strbuf template = STRBUF_INIT; | |
803 | const char *tail; | |
804 | FILE *outfile; | |
805 | ||
806 | if (stat(file, &st)) | |
807 | die_errno(_("could not stat %s"), file); | |
808 | if (!S_ISREG(st.st_mode)) | |
809 | die(_("file %s is not a regular file"), file); | |
810 | if (!(st.st_mode & S_IWUSR)) | |
811 | die(_("file %s is not writable by user"), file); | |
812 | ||
813 | /* Create temporary file in the same directory as the original */ | |
814 | tail = strrchr(file, '/'); | |
815 | if (tail != NULL) | |
816 | strbuf_add(&template, file, tail - file + 1); | |
817 | strbuf_addstr(&template, "git-interpret-trailers-XXXXXX"); | |
818 | ||
819 | xmks_tempfile_m(&trailers_tempfile, template.buf, st.st_mode); | |
820 | strbuf_release(&template); | |
821 | outfile = fdopen_tempfile(&trailers_tempfile, "w"); | |
822 | if (!outfile) | |
823 | die_errno(_("could not open temporary file")); | |
824 | ||
825 | return outfile; | |
826 | } | |
827 | ||
828 | void process_trailers(const char *file, int in_place, int trim_empty, struct string_list *trailers) | |
b1d78d77 | 829 | { |
8966a394 JT |
830 | LIST_HEAD(head); |
831 | LIST_HEAD(arg_head); | |
b1d78d77 | 832 | struct strbuf **lines; |
61cfef4c | 833 | int trailer_end; |
d0d2344a | 834 | FILE *outfile = stdout; |
b1d78d77 CC |
835 | |
836 | /* Default config must be setup first */ | |
837 | git_config(git_trailer_default_config, NULL); | |
838 | git_config(git_trailer_config, NULL); | |
839 | ||
840 | lines = read_input_file(file); | |
841 | ||
e1f89863 TK |
842 | if (in_place) |
843 | outfile = create_in_place_tempfile(file); | |
844 | ||
b1d78d77 | 845 | /* Print the lines before the trailers */ |
8966a394 | 846 | trailer_end = process_input_file(outfile, lines, &head); |
b1d78d77 | 847 | |
8966a394 | 848 | process_command_line_args(&arg_head, trailers); |
b1d78d77 | 849 | |
8966a394 | 850 | process_trailers_lists(&head, &arg_head); |
b1d78d77 | 851 | |
8966a394 | 852 | print_all(outfile, &head, trim_empty); |
b1d78d77 | 853 | |
8966a394 | 854 | free_all(&head); |
b1d78d77 CC |
855 | |
856 | /* Print the lines after the trailers as is */ | |
d0d2344a | 857 | print_lines(outfile, lines, trailer_end, INT_MAX); |
b1d78d77 | 858 | |
e1f89863 TK |
859 | if (in_place) |
860 | if (rename_tempfile(&trailers_tempfile, file)) | |
861 | die_errno(_("could not rename temporary file to %s"), file); | |
862 | ||
b1d78d77 CC |
863 | strbuf_list_free(lines); |
864 | } |