trailer: parse trailers from file or stdin
[git/git.git] / trailer.c
CommitLineData
9385b5d7 1#include "cache.h"
f0a90b4e 2#include "string-list.h"
9385b5d7
CC
3/*
4 * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>
5 */
6
7enum action_where { WHERE_END, WHERE_AFTER, WHERE_BEFORE, WHERE_START };
8enum action_if_exists { EXISTS_ADD_IF_DIFFERENT_NEIGHBOR, EXISTS_ADD_IF_DIFFERENT,
9 EXISTS_ADD, EXISTS_REPLACE, EXISTS_DO_NOTHING };
10enum action_if_missing { MISSING_ADD, MISSING_DO_NOTHING };
11
12struct conf_info {
13 char *name;
14 char *key;
15 char *command;
16 enum action_where where;
17 enum action_if_exists if_exists;
18 enum action_if_missing if_missing;
19};
20
21static struct conf_info default_conf_info;
22
23struct trailer_item {
24 struct trailer_item *previous;
25 struct trailer_item *next;
26 const char *token;
27 const char *value;
28 struct conf_info conf;
29};
30
31static struct trailer_item *first_conf_item;
32
33static char *separators = ":";
34
35static int after_or_end(enum action_where where)
36{
37 return (where == WHERE_AFTER) || (where == WHERE_END);
38}
39
40/*
41 * Return the length of the string not including any final
42 * punctuation. E.g., the input "Signed-off-by:" would return
43 * 13, stripping the trailing punctuation but retaining
44 * internal punctuation.
45 */
46static size_t token_len_without_separator(const char *token, size_t len)
47{
48 while (len > 0 && !isalnum(token[len - 1]))
49 len--;
50 return len;
51}
52
53static int same_token(struct trailer_item *a, struct trailer_item *b)
54{
55 size_t a_len = token_len_without_separator(a->token, strlen(a->token));
56 size_t b_len = token_len_without_separator(b->token, strlen(b->token));
57 size_t min_len = (a_len > b_len) ? b_len : a_len;
58
59 return !strncasecmp(a->token, b->token, min_len);
60}
61
62static int same_value(struct trailer_item *a, struct trailer_item *b)
63{
64 return !strcasecmp(a->value, b->value);
65}
66
67static int same_trailer(struct trailer_item *a, struct trailer_item *b)
68{
69 return same_token(a, b) && same_value(a, b);
70}
4103818d 71
2013d850
CC
72static inline int contains_only_spaces(const char *str)
73{
74 const char *s = str;
75 while (*s && isspace(*s))
76 s++;
77 return !*s;
78}
79
4103818d
CC
80static void free_trailer_item(struct trailer_item *item)
81{
82 free(item->conf.name);
83 free(item->conf.key);
84 free(item->conf.command);
85 free((char *)item->token);
86 free((char *)item->value);
87 free(item);
88}
89
90static void update_last(struct trailer_item **last)
91{
92 if (*last)
93 while ((*last)->next != NULL)
94 *last = (*last)->next;
95}
96
97static void update_first(struct trailer_item **first)
98{
99 if (*first)
100 while ((*first)->previous != NULL)
101 *first = (*first)->previous;
102}
103
104static void add_arg_to_input_list(struct trailer_item *on_tok,
105 struct trailer_item *arg_tok,
106 struct trailer_item **first,
107 struct trailer_item **last)
108{
109 if (after_or_end(arg_tok->conf.where)) {
110 arg_tok->next = on_tok->next;
111 on_tok->next = arg_tok;
112 arg_tok->previous = on_tok;
113 if (arg_tok->next)
114 arg_tok->next->previous = arg_tok;
115 update_last(last);
116 } else {
117 arg_tok->previous = on_tok->previous;
118 on_tok->previous = arg_tok;
119 arg_tok->next = on_tok;
120 if (arg_tok->previous)
121 arg_tok->previous->next = arg_tok;
122 update_first(first);
123 }
124}
125
126static int check_if_different(struct trailer_item *in_tok,
127 struct trailer_item *arg_tok,
128 int check_all)
129{
130 enum action_where where = arg_tok->conf.where;
131 do {
132 if (!in_tok)
133 return 1;
134 if (same_trailer(in_tok, arg_tok))
135 return 0;
136 /*
137 * if we want to add a trailer after another one,
138 * we have to check those before this one
139 */
140 in_tok = after_or_end(where) ? in_tok->previous : in_tok->next;
141 } while (check_all);
142 return 1;
143}
144
145static void remove_from_list(struct trailer_item *item,
146 struct trailer_item **first,
147 struct trailer_item **last)
148{
149 struct trailer_item *next = item->next;
150 struct trailer_item *previous = item->previous;
151
152 if (next) {
153 item->next->previous = previous;
154 item->next = NULL;
155 } else if (last)
156 *last = previous;
157
158 if (previous) {
159 item->previous->next = next;
160 item->previous = NULL;
161 } else if (first)
162 *first = next;
163}
164
165static struct trailer_item *remove_first(struct trailer_item **first)
166{
167 struct trailer_item *item = *first;
168 *first = item->next;
169 if (item->next) {
170 item->next->previous = NULL;
171 item->next = NULL;
172 }
173 return item;
174}
175
176static void apply_arg_if_exists(struct trailer_item *in_tok,
177 struct trailer_item *arg_tok,
178 struct trailer_item *on_tok,
179 struct trailer_item **in_tok_first,
180 struct trailer_item **in_tok_last)
181{
182 switch (arg_tok->conf.if_exists) {
183 case EXISTS_DO_NOTHING:
184 free_trailer_item(arg_tok);
185 break;
186 case EXISTS_REPLACE:
187 add_arg_to_input_list(on_tok, arg_tok,
188 in_tok_first, in_tok_last);
189 remove_from_list(in_tok, in_tok_first, in_tok_last);
190 free_trailer_item(in_tok);
191 break;
192 case EXISTS_ADD:
193 add_arg_to_input_list(on_tok, arg_tok,
194 in_tok_first, in_tok_last);
195 break;
196 case EXISTS_ADD_IF_DIFFERENT:
197 if (check_if_different(in_tok, arg_tok, 1))
198 add_arg_to_input_list(on_tok, arg_tok,
199 in_tok_first, in_tok_last);
200 else
201 free_trailer_item(arg_tok);
202 break;
203 case EXISTS_ADD_IF_DIFFERENT_NEIGHBOR:
204 if (check_if_different(on_tok, arg_tok, 0))
205 add_arg_to_input_list(on_tok, arg_tok,
206 in_tok_first, in_tok_last);
207 else
208 free_trailer_item(arg_tok);
209 break;
210 }
211}
212
213static void apply_arg_if_missing(struct trailer_item **in_tok_first,
214 struct trailer_item **in_tok_last,
215 struct trailer_item *arg_tok)
216{
217 struct trailer_item **in_tok;
218 enum action_where where;
219
220 switch (arg_tok->conf.if_missing) {
221 case MISSING_DO_NOTHING:
222 free_trailer_item(arg_tok);
223 break;
224 case MISSING_ADD:
225 where = arg_tok->conf.where;
226 in_tok = after_or_end(where) ? in_tok_last : in_tok_first;
227 if (*in_tok) {
228 add_arg_to_input_list(*in_tok, arg_tok,
229 in_tok_first, in_tok_last);
230 } else {
231 *in_tok_first = arg_tok;
232 *in_tok_last = arg_tok;
233 }
234 break;
235 }
236}
237
238static int find_same_and_apply_arg(struct trailer_item **in_tok_first,
239 struct trailer_item **in_tok_last,
240 struct trailer_item *arg_tok)
241{
242 struct trailer_item *in_tok;
243 struct trailer_item *on_tok;
244 struct trailer_item *following_tok;
245
246 enum action_where where = arg_tok->conf.where;
247 int middle = (where == WHERE_AFTER) || (where == WHERE_BEFORE);
248 int backwards = after_or_end(where);
249 struct trailer_item *start_tok = backwards ? *in_tok_last : *in_tok_first;
250
251 for (in_tok = start_tok; in_tok; in_tok = following_tok) {
252 following_tok = backwards ? in_tok->previous : in_tok->next;
253 if (!same_token(in_tok, arg_tok))
254 continue;
255 on_tok = middle ? in_tok : start_tok;
256 apply_arg_if_exists(in_tok, arg_tok, on_tok,
257 in_tok_first, in_tok_last);
258 return 1;
259 }
260 return 0;
261}
262
263static void process_trailers_lists(struct trailer_item **in_tok_first,
264 struct trailer_item **in_tok_last,
265 struct trailer_item **arg_tok_first)
266{
267 struct trailer_item *arg_tok;
268 struct trailer_item *next_arg;
269
270 if (!*arg_tok_first)
271 return;
272
273 for (arg_tok = *arg_tok_first; arg_tok; arg_tok = next_arg) {
274 int applied = 0;
275
276 next_arg = arg_tok->next;
277 remove_from_list(arg_tok, arg_tok_first, NULL);
278
279 applied = find_same_and_apply_arg(in_tok_first,
280 in_tok_last,
281 arg_tok);
282
283 if (!applied)
284 apply_arg_if_missing(in_tok_first,
285 in_tok_last,
286 arg_tok);
287 }
288}
46a0613f
CC
289
290static int set_where(struct conf_info *item, const char *value)
291{
292 if (!strcasecmp("after", value))
293 item->where = WHERE_AFTER;
294 else if (!strcasecmp("before", value))
295 item->where = WHERE_BEFORE;
296 else if (!strcasecmp("end", value))
297 item->where = WHERE_END;
298 else if (!strcasecmp("start", value))
299 item->where = WHERE_START;
300 else
301 return -1;
302 return 0;
303}
304
305static int set_if_exists(struct conf_info *item, const char *value)
306{
307 if (!strcasecmp("addIfDifferent", value))
308 item->if_exists = EXISTS_ADD_IF_DIFFERENT;
309 else if (!strcasecmp("addIfDifferentNeighbor", value))
310 item->if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR;
311 else if (!strcasecmp("add", value))
312 item->if_exists = EXISTS_ADD;
313 else if (!strcasecmp("replace", value))
314 item->if_exists = EXISTS_REPLACE;
315 else if (!strcasecmp("doNothing", value))
316 item->if_exists = EXISTS_DO_NOTHING;
317 else
318 return -1;
319 return 0;
320}
321
322static int set_if_missing(struct conf_info *item, const char *value)
323{
324 if (!strcasecmp("doNothing", value))
325 item->if_missing = MISSING_DO_NOTHING;
326 else if (!strcasecmp("add", value))
327 item->if_missing = MISSING_ADD;
328 else
329 return -1;
330 return 0;
331}
332
333static void duplicate_conf(struct conf_info *dst, struct conf_info *src)
334{
335 *dst = *src;
336 if (src->name)
337 dst->name = xstrdup(src->name);
338 if (src->key)
339 dst->key = xstrdup(src->key);
340 if (src->command)
341 dst->command = xstrdup(src->command);
342}
343
344static struct trailer_item *get_conf_item(const char *name)
345{
346 struct trailer_item *item;
347 struct trailer_item *previous;
348
349 /* Look up item with same name */
350 for (previous = NULL, item = first_conf_item;
351 item;
352 previous = item, item = item->next) {
353 if (!strcasecmp(item->conf.name, name))
354 return item;
355 }
356
357 /* Item does not already exists, create it */
358 item = xcalloc(sizeof(struct trailer_item), 1);
359 duplicate_conf(&item->conf, &default_conf_info);
360 item->conf.name = xstrdup(name);
361
362 if (!previous)
363 first_conf_item = item;
364 else {
365 previous->next = item;
366 item->previous = previous;
367 }
368
369 return item;
370}
371
372enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,
373 TRAILER_IF_EXISTS, TRAILER_IF_MISSING };
374
375static struct {
376 const char *name;
377 enum trailer_info_type type;
378} trailer_config_items[] = {
379 { "key", TRAILER_KEY },
380 { "command", TRAILER_COMMAND },
381 { "where", TRAILER_WHERE },
382 { "ifexists", TRAILER_IF_EXISTS },
383 { "ifmissing", TRAILER_IF_MISSING }
384};
385
386static int git_trailer_default_config(const char *conf_key, const char *value, void *cb)
387{
388 const char *trailer_item, *variable_name;
389
390 if (!skip_prefix(conf_key, "trailer.", &trailer_item))
391 return 0;
392
393 variable_name = strrchr(trailer_item, '.');
394 if (!variable_name) {
395 if (!strcmp(trailer_item, "where")) {
396 if (set_where(&default_conf_info, value) < 0)
397 warning(_("unknown value '%s' for key '%s'"),
398 value, conf_key);
399 } else if (!strcmp(trailer_item, "ifexists")) {
400 if (set_if_exists(&default_conf_info, value) < 0)
401 warning(_("unknown value '%s' for key '%s'"),
402 value, conf_key);
403 } else if (!strcmp(trailer_item, "ifmissing")) {
404 if (set_if_missing(&default_conf_info, value) < 0)
405 warning(_("unknown value '%s' for key '%s'"),
406 value, conf_key);
407 } else if (!strcmp(trailer_item, "separators")) {
408 separators = xstrdup(value);
409 }
410 }
411 return 0;
412}
413
414static int git_trailer_config(const char *conf_key, const char *value, void *cb)
415{
416 const char *trailer_item, *variable_name;
417 struct trailer_item *item;
418 struct conf_info *conf;
419 char *name = NULL;
420 enum trailer_info_type type;
421 int i;
422
423 if (!skip_prefix(conf_key, "trailer.", &trailer_item))
424 return 0;
425
426 variable_name = strrchr(trailer_item, '.');
427 if (!variable_name)
428 return 0;
429
430 variable_name++;
431 for (i = 0; i < ARRAY_SIZE(trailer_config_items); i++) {
432 if (strcmp(trailer_config_items[i].name, variable_name))
433 continue;
434 name = xstrndup(trailer_item, variable_name - trailer_item - 1);
435 type = trailer_config_items[i].type;
436 break;
437 }
438
439 if (!name)
440 return 0;
441
442 item = get_conf_item(name);
443 conf = &item->conf;
444 free(name);
445
446 switch (type) {
447 case TRAILER_KEY:
448 if (conf->key)
449 warning(_("more than one %s"), conf_key);
450 conf->key = xstrdup(value);
451 break;
452 case TRAILER_COMMAND:
453 if (conf->command)
454 warning(_("more than one %s"), conf_key);
455 conf->command = xstrdup(value);
456 break;
457 case TRAILER_WHERE:
458 if (set_where(conf, value))
459 warning(_("unknown value '%s' for key '%s'"), value, conf_key);
460 break;
461 case TRAILER_IF_EXISTS:
462 if (set_if_exists(conf, value))
463 warning(_("unknown value '%s' for key '%s'"), value, conf_key);
464 break;
465 case TRAILER_IF_MISSING:
466 if (set_if_missing(conf, value))
467 warning(_("unknown value '%s' for key '%s'"), value, conf_key);
468 break;
469 default:
470 die("internal bug in trailer.c");
471 }
472 return 0;
473}
f0a90b4e
CC
474
475static int parse_trailer(struct strbuf *tok, struct strbuf *val, const char *trailer)
476{
477 size_t len;
478 struct strbuf seps = STRBUF_INIT;
479 strbuf_addstr(&seps, separators);
480 strbuf_addch(&seps, '=');
481 len = strcspn(trailer, seps.buf);
482 strbuf_release(&seps);
483 if (len == 0)
484 return error(_("empty trailer token in trailer '%s'"), trailer);
485 if (len < strlen(trailer)) {
486 strbuf_add(tok, trailer, len);
487 strbuf_trim(tok);
488 strbuf_addstr(val, trailer + len + 1);
489 strbuf_trim(val);
490 } else {
491 strbuf_addstr(tok, trailer);
492 strbuf_trim(tok);
493 }
494 return 0;
495}
496
497static const char *token_from_item(struct trailer_item *item, char *tok)
498{
499 if (item->conf.key)
500 return item->conf.key;
501 if (tok)
502 return tok;
503 return item->conf.name;
504}
505
506static struct trailer_item *new_trailer_item(struct trailer_item *conf_item,
507 char *tok, char *val)
508{
509 struct trailer_item *new = xcalloc(sizeof(*new), 1);
510 new->value = val;
511
512 if (conf_item) {
513 duplicate_conf(&new->conf, &conf_item->conf);
514 new->token = xstrdup(token_from_item(conf_item, tok));
515 free(tok);
516 } else {
517 duplicate_conf(&new->conf, &default_conf_info);
518 new->token = tok;
519 }
520
521 return new;
522}
523
524static int token_matches_item(const char *tok, struct trailer_item *item, int tok_len)
525{
526 if (!strncasecmp(tok, item->conf.name, tok_len))
527 return 1;
528 return item->conf.key ? !strncasecmp(tok, item->conf.key, tok_len) : 0;
529}
530
531static struct trailer_item *create_trailer_item(const char *string)
532{
533 struct strbuf tok = STRBUF_INIT;
534 struct strbuf val = STRBUF_INIT;
535 struct trailer_item *item;
536 int tok_len;
537
538 if (parse_trailer(&tok, &val, string))
539 return NULL;
540
541 tok_len = token_len_without_separator(tok.buf, tok.len);
542
543 /* Lookup if the token matches something in the config */
544 for (item = first_conf_item; item; item = item->next) {
545 if (token_matches_item(tok.buf, item, tok_len))
546 return new_trailer_item(item,
547 strbuf_detach(&tok, NULL),
548 strbuf_detach(&val, NULL));
549 }
550
551 return new_trailer_item(NULL,
552 strbuf_detach(&tok, NULL),
553 strbuf_detach(&val, NULL));
554}
555
556static void add_trailer_item(struct trailer_item **first,
557 struct trailer_item **last,
558 struct trailer_item *new)
559{
560 if (!new)
561 return;
562 if (!*last) {
563 *first = new;
564 *last = new;
565 } else {
566 (*last)->next = new;
567 new->previous = *last;
568 *last = new;
569 }
570}
571
572static struct trailer_item *process_command_line_args(struct string_list *trailers)
573{
574 struct trailer_item *arg_tok_first = NULL;
575 struct trailer_item *arg_tok_last = NULL;
576 struct string_list_item *tr;
577
578 for_each_string_list_item(tr, trailers) {
579 struct trailer_item *new = create_trailer_item(tr->string);
580 add_trailer_item(&arg_tok_first, &arg_tok_last, new);
581 }
582
583 return arg_tok_first;
584}
2013d850
CC
585
586static struct strbuf **read_input_file(const char *file)
587{
588 struct strbuf **lines;
589 struct strbuf sb = STRBUF_INIT;
590
591 if (file) {
592 if (strbuf_read_file(&sb, file, 0) < 0)
593 die_errno(_("could not read input file '%s'"), file);
594 } else {
595 if (strbuf_read(&sb, fileno(stdin), 0) < 0)
596 die_errno(_("could not read from stdin"));
597 }
598
599 lines = strbuf_split(&sb, '\n');
600
601 strbuf_release(&sb);
602
603 return lines;
604}
605
606/*
607 * Return the (0 based) index of the start of the patch or the line
608 * count if there is no patch in the message.
609 */
610static int find_patch_start(struct strbuf **lines, int count)
611{
612 int i;
613
614 /* Get the start of the patch part if any */
615 for (i = 0; i < count; i++) {
616 if (starts_with(lines[i]->buf, "---"))
617 return i;
618 }
619
620 return count;
621}
622
623/*
624 * Return the (0 based) index of the first trailer line or count if
625 * there are no trailers. Trailers are searched only in the lines from
626 * index (count - 1) down to index 0.
627 */
628static int find_trailer_start(struct strbuf **lines, int count)
629{
630 int start, only_spaces = 1;
631
632 /*
633 * Get the start of the trailers by looking starting from the end
634 * for a line with only spaces before lines with one separator.
635 */
636 for (start = count - 1; start >= 0; start--) {
637 if (lines[start]->buf[0] == comment_line_char)
638 continue;
639 if (contains_only_spaces(lines[start]->buf)) {
640 if (only_spaces)
641 continue;
642 return start + 1;
643 }
644 if (strcspn(lines[start]->buf, separators) < lines[start]->len) {
645 if (only_spaces)
646 only_spaces = 0;
647 continue;
648 }
649 return count;
650 }
651
652 return only_spaces ? count : 0;
653}
654
655static int has_blank_line_before(struct strbuf **lines, int start)
656{
657 for (;start >= 0; start--) {
658 if (lines[start]->buf[0] == comment_line_char)
659 continue;
660 return contains_only_spaces(lines[start]->buf);
661 }
662 return 0;
663}
664
665static void print_lines(struct strbuf **lines, int start, int end)
666{
667 int i;
668 for (i = start; lines[i] && i < end; i++)
669 printf("%s", lines[i]->buf);
670}
671
672static int process_input_file(struct strbuf **lines,
673 struct trailer_item **in_tok_first,
674 struct trailer_item **in_tok_last)
675{
676 int count = 0;
677 int patch_start, trailer_start, i;
678
679 /* Get the line count */
680 while (lines[count])
681 count++;
682
683 patch_start = find_patch_start(lines, count);
684 trailer_start = find_trailer_start(lines, patch_start);
685
686 /* Print lines before the trailers as is */
687 print_lines(lines, 0, trailer_start);
688
689 if (!has_blank_line_before(lines, trailer_start - 1))
690 printf("\n");
691
692 /* Parse trailer lines */
693 for (i = trailer_start; i < patch_start; i++) {
694 struct trailer_item *new = create_trailer_item(lines[i]->buf);
695 add_trailer_item(in_tok_first, in_tok_last, new);
696 }
697
698 return patch_start;
699}