Merge branch 'rs/list-optim' into jch
[git/jrn.git] / trailer.c
blob987fa29c194572f1bcb622845c409e23c44f445a
1 #include "cache.h"
2 #include "string-list.h"
3 #include "run-command.h"
4 #include "string-list.h"
5 #include "trailer.h"
6 /*
7 * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>
8 */
10 enum action_where { WHERE_AFTER, WHERE_BEFORE };
11 enum action_if_exists { EXISTS_ADD_IF_DIFFERENT, EXISTS_ADD_IF_DIFFERENT_NEIGHBOR,
12 EXISTS_ADD, EXISTS_OVERWRITE, EXISTS_DO_NOTHING };
13 enum action_if_missing { MISSING_ADD, MISSING_DO_NOTHING };
15 struct conf_info {
16 char *name;
17 char *key;
18 char *command;
19 unsigned command_uses_arg : 1;
20 enum action_where where;
21 enum action_if_exists if_exists;
22 enum action_if_missing if_missing;
25 #define TRAILER_ARG_STRING "$ARG"
27 struct trailer_item {
28 struct trailer_item *previous;
29 struct trailer_item *next;
30 const char *token;
31 const char *value;
32 struct conf_info conf;
35 static struct trailer_item *first_conf_item;
37 static int same_token(struct trailer_item *a, struct trailer_item *b, int alnum_len)
39 return !strncasecmp(a->token, b->token, alnum_len);
42 static int same_value(struct trailer_item *a, struct trailer_item *b)
44 return !strcasecmp(a->value, b->value);
47 static int same_trailer(struct trailer_item *a, struct trailer_item *b, int alnum_len)
49 return same_token(a, b, alnum_len) && same_value(a, b);
52 /* Get the length of buf from its beginning until its last alphanumeric character */
53 static size_t alnum_len(const char *buf, size_t len)
55 while (len > 0 && !isalnum(buf[len - 1]))
56 len--;
57 return len;
60 static inline int contains_only_spaces(const char *str)
62 const char *s = str;
63 while (*s && isspace(*s))
64 s++;
65 return !*s;
68 static inline void strbuf_replace(struct strbuf *sb, const char *a, const char *b)
70 const char *ptr = strstr(sb->buf, a);
71 if (ptr)
72 strbuf_splice(sb, ptr - sb->buf, strlen(a), b, strlen(b));
75 static void free_trailer_item(struct trailer_item *item)
77 free(item->conf.name);
78 free(item->conf.key);
79 free(item->conf.command);
80 free((char *)item->token);
81 free((char *)item->value);
82 free(item);
85 static void print_tok_val(const char *tok, const char *val)
87 char c = tok[strlen(tok) - 1];
88 if (isalnum(c))
89 printf("%s: %s\n", tok, val);
90 else
91 printf("%s%s\n", tok, val);
94 static void print_all(struct trailer_item *first, int trim_empty)
96 struct trailer_item *item;
97 for (item = first; item; item = item->next) {
98 if (!trim_empty || strlen(item->value) > 0)
99 print_tok_val(item->token, item->value);
103 static void add_arg_to_input_list(struct trailer_item *in_tok,
104 struct trailer_item *arg_tok)
106 if (arg_tok->conf.where == WHERE_AFTER) {
107 arg_tok->next = in_tok->next;
108 in_tok->next = arg_tok;
109 arg_tok->previous = in_tok;
110 if (arg_tok->next)
111 arg_tok->next->previous = arg_tok;
112 } else {
113 arg_tok->previous = in_tok->previous;
114 in_tok->previous = arg_tok;
115 arg_tok->next = in_tok;
116 if (arg_tok->previous)
117 arg_tok->previous->next = arg_tok;
121 static int check_if_different(struct trailer_item *in_tok,
122 struct trailer_item *arg_tok,
123 int alnum_len, int check_all)
125 enum action_where where = arg_tok->conf.where;
126 do {
127 if (!in_tok)
128 return 1;
129 if (same_trailer(in_tok, arg_tok, alnum_len))
130 return 0;
132 * if we want to add a trailer after another one,
133 * we have to check those before this one
135 in_tok = (where == WHERE_AFTER) ? in_tok->previous : in_tok->next;
136 } while (check_all);
137 return 1;
140 static void apply_arg_if_exists(struct trailer_item *in_tok,
141 struct trailer_item *arg_tok,
142 int alnum_len)
144 switch (arg_tok->conf.if_exists) {
145 case EXISTS_DO_NOTHING:
146 free_trailer_item(arg_tok);
147 break;
148 case EXISTS_OVERWRITE:
149 free((char *)in_tok->value);
150 in_tok->value = xstrdup(arg_tok->value);
151 free_trailer_item(arg_tok);
152 break;
153 case EXISTS_ADD:
154 add_arg_to_input_list(in_tok, arg_tok);
155 break;
156 case EXISTS_ADD_IF_DIFFERENT:
157 if (check_if_different(in_tok, arg_tok, alnum_len, 1))
158 add_arg_to_input_list(in_tok, arg_tok);
159 else
160 free_trailer_item(arg_tok);
161 break;
162 case EXISTS_ADD_IF_DIFFERENT_NEIGHBOR:
163 if (check_if_different(in_tok, arg_tok, alnum_len, 0))
164 add_arg_to_input_list(in_tok, arg_tok);
165 else
166 free_trailer_item(arg_tok);
167 break;
171 static void remove_from_list(struct trailer_item *item,
172 struct trailer_item **first)
174 if (item->next)
175 item->next->previous = item->previous;
176 if (item->previous)
177 item->previous->next = item->next;
178 else
179 *first = item->next;
182 static struct trailer_item *remove_first(struct trailer_item **first)
184 struct trailer_item *item = *first;
185 *first = item->next;
186 if (item->next) {
187 item->next->previous = NULL;
188 item->next = NULL;
190 return item;
193 static void process_input_token(struct trailer_item *in_tok,
194 struct trailer_item **arg_tok_first,
195 enum action_where where)
197 struct trailer_item *arg_tok;
198 struct trailer_item *next_arg;
200 int after = where == WHERE_AFTER;
201 int tok_alnum_len = alnum_len(in_tok->token, strlen(in_tok->token));
203 for (arg_tok = *arg_tok_first; arg_tok; arg_tok = next_arg) {
204 next_arg = arg_tok->next;
205 if (!same_token(in_tok, arg_tok, tok_alnum_len))
206 continue;
207 if (arg_tok->conf.where != where)
208 continue;
209 remove_from_list(arg_tok, arg_tok_first);
210 apply_arg_if_exists(in_tok, arg_tok, tok_alnum_len);
212 * If arg has been added to input,
213 * then we need to process it too now.
215 if ((after ? in_tok->next : in_tok->previous) == arg_tok)
216 in_tok = arg_tok;
220 static void update_last(struct trailer_item **last)
222 if (*last)
223 while ((*last)->next != NULL)
224 *last = (*last)->next;
227 static void update_first(struct trailer_item **first)
229 if (*first)
230 while ((*first)->previous != NULL)
231 *first = (*first)->previous;
234 static void apply_arg_if_missing(struct trailer_item **in_tok_first,
235 struct trailer_item **in_tok_last,
236 struct trailer_item *arg_tok)
238 struct trailer_item **in_tok;
239 enum action_where where;
241 switch (arg_tok->conf.if_missing) {
242 case MISSING_DO_NOTHING:
243 free_trailer_item(arg_tok);
244 break;
245 case MISSING_ADD:
246 where = arg_tok->conf.where;
247 in_tok = (where == WHERE_AFTER) ? in_tok_last : in_tok_first;
248 if (*in_tok) {
249 add_arg_to_input_list(*in_tok, arg_tok);
250 *in_tok = arg_tok;
251 } else {
252 *in_tok_first = arg_tok;
253 *in_tok_last = arg_tok;
255 break;
259 static void process_trailers_lists(struct trailer_item **in_tok_first,
260 struct trailer_item **in_tok_last,
261 struct trailer_item **arg_tok_first)
263 struct trailer_item *in_tok;
264 struct trailer_item *arg_tok;
266 if (!*arg_tok_first)
267 return;
269 /* Process input from end to start */
270 for (in_tok = *in_tok_last; in_tok; in_tok = in_tok->previous)
271 process_input_token(in_tok, arg_tok_first, WHERE_AFTER);
273 update_last(in_tok_last);
275 if (!*arg_tok_first)
276 return;
278 /* Process input from start to end */
279 for (in_tok = *in_tok_first; in_tok; in_tok = in_tok->next)
280 process_input_token(in_tok, arg_tok_first, WHERE_BEFORE);
282 update_first(in_tok_first);
284 /* Process args left */
285 while (*arg_tok_first) {
286 arg_tok = remove_first(arg_tok_first);
287 apply_arg_if_missing(in_tok_first, in_tok_last, arg_tok);
291 static int set_where(struct conf_info *item, const char *value)
293 if (!strcasecmp("after", value))
294 item->where = WHERE_AFTER;
295 else if (!strcasecmp("before", value))
296 item->where = WHERE_BEFORE;
297 else
298 return -1;
299 return 0;
302 static int set_if_exists(struct conf_info *item, const char *value)
304 if (!strcasecmp("addIfDifferent", value))
305 item->if_exists = EXISTS_ADD_IF_DIFFERENT;
306 else if (!strcasecmp("addIfDifferentNeighbor", value))
307 item->if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR;
308 else if (!strcasecmp("add", value))
309 item->if_exists = EXISTS_ADD;
310 else if (!strcasecmp("overwrite", value))
311 item->if_exists = EXISTS_OVERWRITE;
312 else if (!strcasecmp("doNothing", value))
313 item->if_exists = EXISTS_DO_NOTHING;
314 else
315 return -1;
316 return 0;
319 static int set_if_missing(struct conf_info *item, const char *value)
321 if (!strcasecmp("doNothing", value))
322 item->if_missing = MISSING_DO_NOTHING;
323 else if (!strcasecmp("add", value))
324 item->if_missing = MISSING_ADD;
325 else
326 return -1;
327 return 0;
330 static struct trailer_item *get_conf_item(const char *name)
332 struct trailer_item *item;
333 struct trailer_item *previous;
335 /* Look up item with same name */
336 for (previous = NULL, item = first_conf_item;
337 item;
338 previous = item, item = item->next) {
339 if (!strcasecmp(item->conf.name, name))
340 return item;
343 /* Item does not already exists, create it */
344 item = xcalloc(sizeof(struct trailer_item), 1);
345 item->conf.name = xstrdup(name);
347 if (!previous)
348 first_conf_item = item;
349 else {
350 previous->next = item;
351 item->previous = previous;
354 return item;
357 enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,
358 TRAILER_IF_EXISTS, TRAILER_IF_MISSING };
360 static struct {
361 const char *name;
362 enum trailer_info_type type;
363 } trailer_config_items[] = {
364 { "key", TRAILER_KEY },
365 { "command", TRAILER_COMMAND },
366 { "where", TRAILER_WHERE },
367 { "ifexists", TRAILER_IF_EXISTS },
368 { "ifmissing", TRAILER_IF_MISSING }
371 static int git_trailer_config(const char *conf_key, const char *value, void *cb)
373 const char *trailer_item, *variable_name;
374 struct trailer_item *item;
375 struct conf_info *conf;
376 char *name = NULL;
377 enum trailer_info_type type;
378 int i;
380 if (!skip_prefix(conf_key, "trailer.", &trailer_item))
381 return 0;
383 variable_name = strrchr(trailer_item, '.');
384 if (!variable_name) {
385 warning(_("two level trailer config variable %s"), conf_key);
386 return 0;
389 variable_name++;
390 for (i = 0; i < ARRAY_SIZE(trailer_config_items); i++) {
391 if (strcmp(trailer_config_items[i].name, variable_name))
392 continue;
393 name = xstrndup(trailer_item, variable_name - trailer_item - 1);
394 type = trailer_config_items[i].type;
395 break;
398 if (!name)
399 return 0;
401 item = get_conf_item(name);
402 conf = &item->conf;
403 free(name);
405 switch (type) {
406 case TRAILER_KEY:
407 if (conf->key)
408 warning(_("more than one %s"), conf_key);
409 conf->key = xstrdup(value);
410 break;
411 case TRAILER_COMMAND:
412 if (conf->command)
413 warning(_("more than one %s"), conf_key);
414 conf->command = xstrdup(value);
415 conf->command_uses_arg = !!strstr(conf->command, TRAILER_ARG_STRING);
416 break;
417 case TRAILER_WHERE:
418 if (set_where(conf, value))
419 warning(_("unknown value '%s' for key '%s'"), value, conf_key);
420 break;
421 case TRAILER_IF_EXISTS:
422 if (set_if_exists(conf, value))
423 warning(_("unknown value '%s' for key '%s'"), value, conf_key);
424 break;
425 case TRAILER_IF_MISSING:
426 if (set_if_missing(conf, value))
427 warning(_("unknown value '%s' for key '%s'"), value, conf_key);
428 break;
429 default:
430 die("internal bug in trailer.c");
432 return 0;
435 static int parse_trailer(struct strbuf *tok, struct strbuf *val, const char *trailer)
437 size_t len = strcspn(trailer, "=:");
438 if (len == 0)
439 return error(_("empty trailer token in trailer '%s'"), trailer);
440 if (len < strlen(trailer)) {
441 strbuf_add(tok, trailer, len);
442 strbuf_trim(tok);
443 strbuf_addstr(val, trailer + len + 1);
444 strbuf_trim(val);
445 } else {
446 strbuf_addstr(tok, trailer);
447 strbuf_trim(tok);
449 return 0;
452 static int read_from_command(struct child_process *cp, struct strbuf *buf)
454 if (run_command(cp))
455 return error("running trailer command '%s' failed", cp->argv[0]);
456 if (strbuf_read(buf, cp->out, 1024) < 1)
457 return error("reading from trailer command '%s' failed", cp->argv[0]);
458 strbuf_trim(buf);
459 return 0;
462 static const char *apply_command(const char *command, const char *arg)
464 struct strbuf cmd = STRBUF_INIT;
465 struct strbuf buf = STRBUF_INIT;
466 struct child_process cp;
467 const char *argv[] = {NULL, NULL};
468 const char *result;
470 strbuf_addstr(&cmd, command);
471 if (arg)
472 strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);
474 argv[0] = cmd.buf;
475 memset(&cp, 0, sizeof(cp));
476 cp.argv = argv;
477 cp.env = local_repo_env;
478 cp.no_stdin = 1;
479 cp.out = -1;
480 cp.use_shell = 1;
482 if (read_from_command(&cp, &buf)) {
483 strbuf_release(&buf);
484 result = xstrdup("");
485 } else
486 result = strbuf_detach(&buf, NULL);
488 strbuf_release(&cmd);
489 return result;
492 static void duplicate_conf(struct conf_info *dst, struct conf_info *src)
494 *dst = *src;
495 if (src->name)
496 dst->name = xstrdup(src->name);
497 if (src->key)
498 dst->key = xstrdup(src->key);
499 if (src->command)
500 dst->command = xstrdup(src->command);
503 static const char *token_from_item(struct trailer_item *item)
505 if (item->conf.key)
506 return item->conf.key;
508 return item->conf.name;
511 static struct trailer_item *new_trailer_item(struct trailer_item *conf_item,
512 char *tok, char *val)
514 struct trailer_item *new = xcalloc(sizeof(*new), 1);
515 new->value = val;
517 if (conf_item) {
518 duplicate_conf(&new->conf, &conf_item->conf);
519 new->token = xstrdup(token_from_item(conf_item));
520 free(tok);
521 if (conf_item->conf.command_uses_arg || !val) {
522 new->value = apply_command(conf_item->conf.command, val);
523 free(val);
525 } else
526 new->token = tok;
528 return new;
531 static int token_matches_item(const char *tok, struct trailer_item *item, int alnum_len)
533 if (!strncasecmp(tok, item->conf.name, alnum_len))
534 return 1;
535 return item->conf.key ? !strncasecmp(tok, item->conf.key, alnum_len) : 0;
538 static struct trailer_item *create_trailer_item(const char *string)
540 struct strbuf tok = STRBUF_INIT;
541 struct strbuf val = STRBUF_INIT;
542 struct trailer_item *item;
543 int tok_alnum_len;
545 if (parse_trailer(&tok, &val, string))
546 return NULL;
548 tok_alnum_len = alnum_len(tok.buf, tok.len);
550 /* Lookup if the token matches something in the config */
551 for (item = first_conf_item; item; item = item->next) {
552 if (token_matches_item(tok.buf, item, tok_alnum_len)) {
553 strbuf_release(&tok);
554 return new_trailer_item(item,
555 NULL,
556 strbuf_detach(&val, NULL));
560 return new_trailer_item(NULL,
561 strbuf_detach(&tok, NULL),
562 strbuf_detach(&val, NULL));
565 static void add_trailer_item(struct trailer_item **first,
566 struct trailer_item **last,
567 struct trailer_item *new)
569 if (!new)
570 return;
571 if (!*last) {
572 *first = new;
573 *last = new;
574 } else {
575 (*last)->next = new;
576 new->previous = *last;
577 *last = new;
581 static struct trailer_item *process_command_line_args(struct string_list *trailers)
583 struct trailer_item *arg_tok_first = NULL;
584 struct trailer_item *arg_tok_last = NULL;
585 struct string_list_item *tr;
586 struct trailer_item *item;
588 for_each_string_list_item(tr, trailers) {
589 struct trailer_item *new = create_trailer_item(tr->string);
590 add_trailer_item(&arg_tok_first, &arg_tok_last, new);
593 /* Add conf commands that don't use $ARG */
594 for (item = first_conf_item; item; item = item->next) {
595 if (item->conf.command && !item->conf.command_uses_arg) {
596 struct trailer_item *new = new_trailer_item(item, NULL, NULL);
597 add_trailer_item(&arg_tok_first, &arg_tok_last, new);
601 return arg_tok_first;
604 static struct strbuf **read_input_file(const char *file)
606 struct strbuf **lines;
607 struct strbuf sb = STRBUF_INIT;
609 if (file) {
610 if (strbuf_read_file(&sb, file, 0) < 0)
611 die_errno(_("could not read input file '%s'"), file);
612 } else {
613 if (strbuf_read(&sb, fileno(stdin), 0) < 0)
614 die_errno(_("could not read from stdin"));
617 lines = strbuf_split(&sb, '\n');
619 strbuf_release(&sb);
621 return lines;
625 * Return the (0 based) index of the start of the patch or the line
626 * count if there is no patch in the message.
628 static int find_patch_start(struct strbuf **lines, int count)
630 int i;
632 /* Get the start of the patch part if any */
633 for (i = 0; i < count; i++) {
634 if (starts_with(lines[i]->buf, "---"))
635 return i;
638 return count;
642 * Return the (0 based) index of the first trailer line or count if
643 * there are no trailers. Trailers are searched only in the lines from
644 * index (count - 1) down to index 0.
646 static int find_trailer_start(struct strbuf **lines, int count)
648 int start, only_spaces = 1;
651 * Get the start of the trailers by looking starting from the end
652 * for a line with only spaces before lines with one ':'.
654 for (start = count - 1; start >= 0; start--) {
655 if (lines[start]->buf[0] == comment_line_char)
656 continue;
657 if (contains_only_spaces(lines[start]->buf)) {
658 if (only_spaces)
659 continue;
660 return start + 1;
662 if (strchr(lines[start]->buf, ':')) {
663 if (only_spaces)
664 only_spaces = 0;
665 continue;
667 return count;
670 return only_spaces ? count : 0;
673 static int has_blank_line_before(struct strbuf **lines, int start)
675 for (;start >= 0; start--) {
676 if (lines[start]->buf[0] == comment_line_char)
677 continue;
678 return contains_only_spaces(lines[start]->buf);
680 return 0;
683 static void print_lines(struct strbuf **lines, int start, int end)
685 int i;
686 for (i = start; lines[i] && i < end; i++)
687 printf("%s", lines[i]->buf);
690 static int process_input_file(struct strbuf **lines,
691 struct trailer_item **in_tok_first,
692 struct trailer_item **in_tok_last)
694 int count = 0;
695 int patch_start, trailer_start, i;
697 /* Get the line count */
698 while (lines[count])
699 count++;
701 patch_start = find_patch_start(lines, count);
702 trailer_start = find_trailer_start(lines, patch_start);
704 /* Print lines before the trailers as is */
705 print_lines(lines, 0, trailer_start);
707 if (!has_blank_line_before(lines, trailer_start - 1))
708 printf("\n");
710 /* Parse trailer lines */
711 for (i = trailer_start; i < patch_start; i++) {
712 struct trailer_item *new = create_trailer_item(lines[i]->buf);
713 add_trailer_item(in_tok_first, in_tok_last, new);
716 return patch_start;
719 static void free_all(struct trailer_item **first)
721 while (*first) {
722 struct trailer_item *item = remove_first(first);
723 free_trailer_item(item);
727 void process_trailers(const char *file, int trim_empty, struct string_list *trailers)
729 struct trailer_item *in_tok_first = NULL;
730 struct trailer_item *in_tok_last = NULL;
731 struct trailer_item *arg_tok_first;
732 struct strbuf **lines;
733 int patch_start;
735 git_config(git_trailer_config, NULL);
737 lines = read_input_file(file);
739 /* Print the lines before the trailers */
740 patch_start = process_input_file(lines, &in_tok_first, &in_tok_last);
742 arg_tok_first = process_command_line_args(trailers);
744 process_trailers_lists(&in_tok_first, &in_tok_last, &arg_tok_first);
746 print_all(in_tok_first, trim_empty);
748 free_all(&in_tok_first);
750 /* Print the lines after the trailers as is */
751 print_lines(lines, patch_start, INT_MAX);
753 strbuf_list_free(lines);