trailer: parse trailers from file or stdin
[git/debian.git] / trailer.c
blob4f0de3b677f74f58aa63f124628175d8b19b4390
1 #include "cache.h"
2 #include "string-list.h"
3 /*
4 * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>
5 */
7 enum action_where { WHERE_END, WHERE_AFTER, WHERE_BEFORE, WHERE_START };
8 enum action_if_exists { EXISTS_ADD_IF_DIFFERENT_NEIGHBOR, EXISTS_ADD_IF_DIFFERENT,
9 EXISTS_ADD, EXISTS_REPLACE, EXISTS_DO_NOTHING };
10 enum action_if_missing { MISSING_ADD, MISSING_DO_NOTHING };
12 struct 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;
21 static struct conf_info default_conf_info;
23 struct 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;
31 static struct trailer_item *first_conf_item;
33 static char *separators = ":";
35 static int after_or_end(enum action_where where)
37 return (where == WHERE_AFTER) || (where == WHERE_END);
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.
46 static size_t token_len_without_separator(const char *token, size_t len)
48 while (len > 0 && !isalnum(token[len - 1]))
49 len--;
50 return len;
53 static int same_token(struct trailer_item *a, struct trailer_item *b)
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;
59 return !strncasecmp(a->token, b->token, min_len);
62 static int same_value(struct trailer_item *a, struct trailer_item *b)
64 return !strcasecmp(a->value, b->value);
67 static int same_trailer(struct trailer_item *a, struct trailer_item *b)
69 return same_token(a, b) && same_value(a, b);
72 static inline int contains_only_spaces(const char *str)
74 const char *s = str;
75 while (*s && isspace(*s))
76 s++;
77 return !*s;
80 static void free_trailer_item(struct trailer_item *item)
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);
90 static void update_last(struct trailer_item **last)
92 if (*last)
93 while ((*last)->next != NULL)
94 *last = (*last)->next;
97 static void update_first(struct trailer_item **first)
99 if (*first)
100 while ((*first)->previous != NULL)
101 *first = (*first)->previous;
104 static 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)
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);
126 static int check_if_different(struct trailer_item *in_tok,
127 struct trailer_item *arg_tok,
128 int check_all)
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;
137 * if we want to add a trailer after another one,
138 * we have to check those before this one
140 in_tok = after_or_end(where) ? in_tok->previous : in_tok->next;
141 } while (check_all);
142 return 1;
145 static void remove_from_list(struct trailer_item *item,
146 struct trailer_item **first,
147 struct trailer_item **last)
149 struct trailer_item *next = item->next;
150 struct trailer_item *previous = item->previous;
152 if (next) {
153 item->next->previous = previous;
154 item->next = NULL;
155 } else if (last)
156 *last = previous;
158 if (previous) {
159 item->previous->next = next;
160 item->previous = NULL;
161 } else if (first)
162 *first = next;
165 static struct trailer_item *remove_first(struct trailer_item **first)
167 struct trailer_item *item = *first;
168 *first = item->next;
169 if (item->next) {
170 item->next->previous = NULL;
171 item->next = NULL;
173 return item;
176 static 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)
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;
213 static void apply_arg_if_missing(struct trailer_item **in_tok_first,
214 struct trailer_item **in_tok_last,
215 struct trailer_item *arg_tok)
217 struct trailer_item **in_tok;
218 enum action_where where;
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;
234 break;
238 static 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)
242 struct trailer_item *in_tok;
243 struct trailer_item *on_tok;
244 struct trailer_item *following_tok;
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;
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;
260 return 0;
263 static void process_trailers_lists(struct trailer_item **in_tok_first,
264 struct trailer_item **in_tok_last,
265 struct trailer_item **arg_tok_first)
267 struct trailer_item *arg_tok;
268 struct trailer_item *next_arg;
270 if (!*arg_tok_first)
271 return;
273 for (arg_tok = *arg_tok_first; arg_tok; arg_tok = next_arg) {
274 int applied = 0;
276 next_arg = arg_tok->next;
277 remove_from_list(arg_tok, arg_tok_first, NULL);
279 applied = find_same_and_apply_arg(in_tok_first,
280 in_tok_last,
281 arg_tok);
283 if (!applied)
284 apply_arg_if_missing(in_tok_first,
285 in_tok_last,
286 arg_tok);
290 static int set_where(struct conf_info *item, const char *value)
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;
305 static int set_if_exists(struct conf_info *item, const char *value)
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;
322 static int set_if_missing(struct conf_info *item, const char *value)
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;
333 static void duplicate_conf(struct conf_info *dst, struct conf_info *src)
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);
344 static struct trailer_item *get_conf_item(const char *name)
346 struct trailer_item *item;
347 struct trailer_item *previous;
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;
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);
362 if (!previous)
363 first_conf_item = item;
364 else {
365 previous->next = item;
366 item->previous = previous;
369 return item;
372 enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,
373 TRAILER_IF_EXISTS, TRAILER_IF_MISSING };
375 static 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 }
386 static int git_trailer_default_config(const char *conf_key, const char *value, void *cb)
388 const char *trailer_item, *variable_name;
390 if (!skip_prefix(conf_key, "trailer.", &trailer_item))
391 return 0;
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);
411 return 0;
414 static int git_trailer_config(const char *conf_key, const char *value, void *cb)
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;
423 if (!skip_prefix(conf_key, "trailer.", &trailer_item))
424 return 0;
426 variable_name = strrchr(trailer_item, '.');
427 if (!variable_name)
428 return 0;
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;
439 if (!name)
440 return 0;
442 item = get_conf_item(name);
443 conf = &item->conf;
444 free(name);
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");
472 return 0;
475 static int parse_trailer(struct strbuf *tok, struct strbuf *val, const char *trailer)
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);
494 return 0;
497 static const char *token_from_item(struct trailer_item *item, char *tok)
499 if (item->conf.key)
500 return item->conf.key;
501 if (tok)
502 return tok;
503 return item->conf.name;
506 static struct trailer_item *new_trailer_item(struct trailer_item *conf_item,
507 char *tok, char *val)
509 struct trailer_item *new = xcalloc(sizeof(*new), 1);
510 new->value = val;
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;
521 return new;
524 static int token_matches_item(const char *tok, struct trailer_item *item, int tok_len)
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;
531 static struct trailer_item *create_trailer_item(const char *string)
533 struct strbuf tok = STRBUF_INIT;
534 struct strbuf val = STRBUF_INIT;
535 struct trailer_item *item;
536 int tok_len;
538 if (parse_trailer(&tok, &val, string))
539 return NULL;
541 tok_len = token_len_without_separator(tok.buf, tok.len);
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));
551 return new_trailer_item(NULL,
552 strbuf_detach(&tok, NULL),
553 strbuf_detach(&val, NULL));
556 static void add_trailer_item(struct trailer_item **first,
557 struct trailer_item **last,
558 struct trailer_item *new)
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;
572 static struct trailer_item *process_command_line_args(struct string_list *trailers)
574 struct trailer_item *arg_tok_first = NULL;
575 struct trailer_item *arg_tok_last = NULL;
576 struct string_list_item *tr;
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);
583 return arg_tok_first;
586 static struct strbuf **read_input_file(const char *file)
588 struct strbuf **lines;
589 struct strbuf sb = STRBUF_INIT;
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"));
599 lines = strbuf_split(&sb, '\n');
601 strbuf_release(&sb);
603 return lines;
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.
610 static int find_patch_start(struct strbuf **lines, int count)
612 int i;
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;
620 return count;
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.
628 static int find_trailer_start(struct strbuf **lines, int count)
630 int start, only_spaces = 1;
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.
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;
644 if (strcspn(lines[start]->buf, separators) < lines[start]->len) {
645 if (only_spaces)
646 only_spaces = 0;
647 continue;
649 return count;
652 return only_spaces ? count : 0;
655 static int has_blank_line_before(struct strbuf **lines, int start)
657 for (;start >= 0; start--) {
658 if (lines[start]->buf[0] == comment_line_char)
659 continue;
660 return contains_only_spaces(lines[start]->buf);
662 return 0;
665 static void print_lines(struct strbuf **lines, int start, int end)
667 int i;
668 for (i = start; lines[i] && i < end; i++)
669 printf("%s", lines[i]->buf);
672 static int process_input_file(struct strbuf **lines,
673 struct trailer_item **in_tok_first,
674 struct trailer_item **in_tok_last)
676 int count = 0;
677 int patch_start, trailer_start, i;
679 /* Get the line count */
680 while (lines[count])
681 count++;
683 patch_start = find_patch_start(lines, count);
684 trailer_start = find_trailer_start(lines, patch_start);
686 /* Print lines before the trailers as is */
687 print_lines(lines, 0, trailer_start);
689 if (!has_blank_line_before(lines, trailer_start - 1))
690 printf("\n");
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);
698 return patch_start;