trailer: process command line trailer arguments
[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
CC
71
72static void free_trailer_item(struct trailer_item *item)
73{
74 free(item->conf.name);
75 free(item->conf.key);
76 free(item->conf.command);
77 free((char *)item->token);
78 free((char *)item->value);
79 free(item);
80}
81
82static void update_last(struct trailer_item **last)
83{
84 if (*last)
85 while ((*last)->next != NULL)
86 *last = (*last)->next;
87}
88
89static void update_first(struct trailer_item **first)
90{
91 if (*first)
92 while ((*first)->previous != NULL)
93 *first = (*first)->previous;
94}
95
96static void add_arg_to_input_list(struct trailer_item *on_tok,
97 struct trailer_item *arg_tok,
98 struct trailer_item **first,
99 struct trailer_item **last)
100{
101 if (after_or_end(arg_tok->conf.where)) {
102 arg_tok->next = on_tok->next;
103 on_tok->next = arg_tok;
104 arg_tok->previous = on_tok;
105 if (arg_tok->next)
106 arg_tok->next->previous = arg_tok;
107 update_last(last);
108 } else {
109 arg_tok->previous = on_tok->previous;
110 on_tok->previous = arg_tok;
111 arg_tok->next = on_tok;
112 if (arg_tok->previous)
113 arg_tok->previous->next = arg_tok;
114 update_first(first);
115 }
116}
117
118static int check_if_different(struct trailer_item *in_tok,
119 struct trailer_item *arg_tok,
120 int check_all)
121{
122 enum action_where where = arg_tok->conf.where;
123 do {
124 if (!in_tok)
125 return 1;
126 if (same_trailer(in_tok, arg_tok))
127 return 0;
128 /*
129 * if we want to add a trailer after another one,
130 * we have to check those before this one
131 */
132 in_tok = after_or_end(where) ? in_tok->previous : in_tok->next;
133 } while (check_all);
134 return 1;
135}
136
137static void remove_from_list(struct trailer_item *item,
138 struct trailer_item **first,
139 struct trailer_item **last)
140{
141 struct trailer_item *next = item->next;
142 struct trailer_item *previous = item->previous;
143
144 if (next) {
145 item->next->previous = previous;
146 item->next = NULL;
147 } else if (last)
148 *last = previous;
149
150 if (previous) {
151 item->previous->next = next;
152 item->previous = NULL;
153 } else if (first)
154 *first = next;
155}
156
157static struct trailer_item *remove_first(struct trailer_item **first)
158{
159 struct trailer_item *item = *first;
160 *first = item->next;
161 if (item->next) {
162 item->next->previous = NULL;
163 item->next = NULL;
164 }
165 return item;
166}
167
168static void apply_arg_if_exists(struct trailer_item *in_tok,
169 struct trailer_item *arg_tok,
170 struct trailer_item *on_tok,
171 struct trailer_item **in_tok_first,
172 struct trailer_item **in_tok_last)
173{
174 switch (arg_tok->conf.if_exists) {
175 case EXISTS_DO_NOTHING:
176 free_trailer_item(arg_tok);
177 break;
178 case EXISTS_REPLACE:
179 add_arg_to_input_list(on_tok, arg_tok,
180 in_tok_first, in_tok_last);
181 remove_from_list(in_tok, in_tok_first, in_tok_last);
182 free_trailer_item(in_tok);
183 break;
184 case EXISTS_ADD:
185 add_arg_to_input_list(on_tok, arg_tok,
186 in_tok_first, in_tok_last);
187 break;
188 case EXISTS_ADD_IF_DIFFERENT:
189 if (check_if_different(in_tok, arg_tok, 1))
190 add_arg_to_input_list(on_tok, arg_tok,
191 in_tok_first, in_tok_last);
192 else
193 free_trailer_item(arg_tok);
194 break;
195 case EXISTS_ADD_IF_DIFFERENT_NEIGHBOR:
196 if (check_if_different(on_tok, arg_tok, 0))
197 add_arg_to_input_list(on_tok, arg_tok,
198 in_tok_first, in_tok_last);
199 else
200 free_trailer_item(arg_tok);
201 break;
202 }
203}
204
205static void apply_arg_if_missing(struct trailer_item **in_tok_first,
206 struct trailer_item **in_tok_last,
207 struct trailer_item *arg_tok)
208{
209 struct trailer_item **in_tok;
210 enum action_where where;
211
212 switch (arg_tok->conf.if_missing) {
213 case MISSING_DO_NOTHING:
214 free_trailer_item(arg_tok);
215 break;
216 case MISSING_ADD:
217 where = arg_tok->conf.where;
218 in_tok = after_or_end(where) ? in_tok_last : in_tok_first;
219 if (*in_tok) {
220 add_arg_to_input_list(*in_tok, arg_tok,
221 in_tok_first, in_tok_last);
222 } else {
223 *in_tok_first = arg_tok;
224 *in_tok_last = arg_tok;
225 }
226 break;
227 }
228}
229
230static int find_same_and_apply_arg(struct trailer_item **in_tok_first,
231 struct trailer_item **in_tok_last,
232 struct trailer_item *arg_tok)
233{
234 struct trailer_item *in_tok;
235 struct trailer_item *on_tok;
236 struct trailer_item *following_tok;
237
238 enum action_where where = arg_tok->conf.where;
239 int middle = (where == WHERE_AFTER) || (where == WHERE_BEFORE);
240 int backwards = after_or_end(where);
241 struct trailer_item *start_tok = backwards ? *in_tok_last : *in_tok_first;
242
243 for (in_tok = start_tok; in_tok; in_tok = following_tok) {
244 following_tok = backwards ? in_tok->previous : in_tok->next;
245 if (!same_token(in_tok, arg_tok))
246 continue;
247 on_tok = middle ? in_tok : start_tok;
248 apply_arg_if_exists(in_tok, arg_tok, on_tok,
249 in_tok_first, in_tok_last);
250 return 1;
251 }
252 return 0;
253}
254
255static void process_trailers_lists(struct trailer_item **in_tok_first,
256 struct trailer_item **in_tok_last,
257 struct trailer_item **arg_tok_first)
258{
259 struct trailer_item *arg_tok;
260 struct trailer_item *next_arg;
261
262 if (!*arg_tok_first)
263 return;
264
265 for (arg_tok = *arg_tok_first; arg_tok; arg_tok = next_arg) {
266 int applied = 0;
267
268 next_arg = arg_tok->next;
269 remove_from_list(arg_tok, arg_tok_first, NULL);
270
271 applied = find_same_and_apply_arg(in_tok_first,
272 in_tok_last,
273 arg_tok);
274
275 if (!applied)
276 apply_arg_if_missing(in_tok_first,
277 in_tok_last,
278 arg_tok);
279 }
280}
46a0613f
CC
281
282static int set_where(struct conf_info *item, const char *value)
283{
284 if (!strcasecmp("after", value))
285 item->where = WHERE_AFTER;
286 else if (!strcasecmp("before", value))
287 item->where = WHERE_BEFORE;
288 else if (!strcasecmp("end", value))
289 item->where = WHERE_END;
290 else if (!strcasecmp("start", value))
291 item->where = WHERE_START;
292 else
293 return -1;
294 return 0;
295}
296
297static int set_if_exists(struct conf_info *item, const char *value)
298{
299 if (!strcasecmp("addIfDifferent", value))
300 item->if_exists = EXISTS_ADD_IF_DIFFERENT;
301 else if (!strcasecmp("addIfDifferentNeighbor", value))
302 item->if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR;
303 else if (!strcasecmp("add", value))
304 item->if_exists = EXISTS_ADD;
305 else if (!strcasecmp("replace", value))
306 item->if_exists = EXISTS_REPLACE;
307 else if (!strcasecmp("doNothing", value))
308 item->if_exists = EXISTS_DO_NOTHING;
309 else
310 return -1;
311 return 0;
312}
313
314static int set_if_missing(struct conf_info *item, const char *value)
315{
316 if (!strcasecmp("doNothing", value))
317 item->if_missing = MISSING_DO_NOTHING;
318 else if (!strcasecmp("add", value))
319 item->if_missing = MISSING_ADD;
320 else
321 return -1;
322 return 0;
323}
324
325static void duplicate_conf(struct conf_info *dst, struct conf_info *src)
326{
327 *dst = *src;
328 if (src->name)
329 dst->name = xstrdup(src->name);
330 if (src->key)
331 dst->key = xstrdup(src->key);
332 if (src->command)
333 dst->command = xstrdup(src->command);
334}
335
336static struct trailer_item *get_conf_item(const char *name)
337{
338 struct trailer_item *item;
339 struct trailer_item *previous;
340
341 /* Look up item with same name */
342 for (previous = NULL, item = first_conf_item;
343 item;
344 previous = item, item = item->next) {
345 if (!strcasecmp(item->conf.name, name))
346 return item;
347 }
348
349 /* Item does not already exists, create it */
350 item = xcalloc(sizeof(struct trailer_item), 1);
351 duplicate_conf(&item->conf, &default_conf_info);
352 item->conf.name = xstrdup(name);
353
354 if (!previous)
355 first_conf_item = item;
356 else {
357 previous->next = item;
358 item->previous = previous;
359 }
360
361 return item;
362}
363
364enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,
365 TRAILER_IF_EXISTS, TRAILER_IF_MISSING };
366
367static struct {
368 const char *name;
369 enum trailer_info_type type;
370} trailer_config_items[] = {
371 { "key", TRAILER_KEY },
372 { "command", TRAILER_COMMAND },
373 { "where", TRAILER_WHERE },
374 { "ifexists", TRAILER_IF_EXISTS },
375 { "ifmissing", TRAILER_IF_MISSING }
376};
377
378static int git_trailer_default_config(const char *conf_key, const char *value, void *cb)
379{
380 const char *trailer_item, *variable_name;
381
382 if (!skip_prefix(conf_key, "trailer.", &trailer_item))
383 return 0;
384
385 variable_name = strrchr(trailer_item, '.');
386 if (!variable_name) {
387 if (!strcmp(trailer_item, "where")) {
388 if (set_where(&default_conf_info, value) < 0)
389 warning(_("unknown value '%s' for key '%s'"),
390 value, conf_key);
391 } else if (!strcmp(trailer_item, "ifexists")) {
392 if (set_if_exists(&default_conf_info, value) < 0)
393 warning(_("unknown value '%s' for key '%s'"),
394 value, conf_key);
395 } else if (!strcmp(trailer_item, "ifmissing")) {
396 if (set_if_missing(&default_conf_info, value) < 0)
397 warning(_("unknown value '%s' for key '%s'"),
398 value, conf_key);
399 } else if (!strcmp(trailer_item, "separators")) {
400 separators = xstrdup(value);
401 }
402 }
403 return 0;
404}
405
406static int git_trailer_config(const char *conf_key, const char *value, void *cb)
407{
408 const char *trailer_item, *variable_name;
409 struct trailer_item *item;
410 struct conf_info *conf;
411 char *name = NULL;
412 enum trailer_info_type type;
413 int i;
414
415 if (!skip_prefix(conf_key, "trailer.", &trailer_item))
416 return 0;
417
418 variable_name = strrchr(trailer_item, '.');
419 if (!variable_name)
420 return 0;
421
422 variable_name++;
423 for (i = 0; i < ARRAY_SIZE(trailer_config_items); i++) {
424 if (strcmp(trailer_config_items[i].name, variable_name))
425 continue;
426 name = xstrndup(trailer_item, variable_name - trailer_item - 1);
427 type = trailer_config_items[i].type;
428 break;
429 }
430
431 if (!name)
432 return 0;
433
434 item = get_conf_item(name);
435 conf = &item->conf;
436 free(name);
437
438 switch (type) {
439 case TRAILER_KEY:
440 if (conf->key)
441 warning(_("more than one %s"), conf_key);
442 conf->key = xstrdup(value);
443 break;
444 case TRAILER_COMMAND:
445 if (conf->command)
446 warning(_("more than one %s"), conf_key);
447 conf->command = xstrdup(value);
448 break;
449 case TRAILER_WHERE:
450 if (set_where(conf, value))
451 warning(_("unknown value '%s' for key '%s'"), value, conf_key);
452 break;
453 case TRAILER_IF_EXISTS:
454 if (set_if_exists(conf, value))
455 warning(_("unknown value '%s' for key '%s'"), value, conf_key);
456 break;
457 case TRAILER_IF_MISSING:
458 if (set_if_missing(conf, value))
459 warning(_("unknown value '%s' for key '%s'"), value, conf_key);
460 break;
461 default:
462 die("internal bug in trailer.c");
463 }
464 return 0;
465}
f0a90b4e
CC
466
467static int parse_trailer(struct strbuf *tok, struct strbuf *val, const char *trailer)
468{
469 size_t len;
470 struct strbuf seps = STRBUF_INIT;
471 strbuf_addstr(&seps, separators);
472 strbuf_addch(&seps, '=');
473 len = strcspn(trailer, seps.buf);
474 strbuf_release(&seps);
475 if (len == 0)
476 return error(_("empty trailer token in trailer '%s'"), trailer);
477 if (len < strlen(trailer)) {
478 strbuf_add(tok, trailer, len);
479 strbuf_trim(tok);
480 strbuf_addstr(val, trailer + len + 1);
481 strbuf_trim(val);
482 } else {
483 strbuf_addstr(tok, trailer);
484 strbuf_trim(tok);
485 }
486 return 0;
487}
488
489static const char *token_from_item(struct trailer_item *item, char *tok)
490{
491 if (item->conf.key)
492 return item->conf.key;
493 if (tok)
494 return tok;
495 return item->conf.name;
496}
497
498static struct trailer_item *new_trailer_item(struct trailer_item *conf_item,
499 char *tok, char *val)
500{
501 struct trailer_item *new = xcalloc(sizeof(*new), 1);
502 new->value = val;
503
504 if (conf_item) {
505 duplicate_conf(&new->conf, &conf_item->conf);
506 new->token = xstrdup(token_from_item(conf_item, tok));
507 free(tok);
508 } else {
509 duplicate_conf(&new->conf, &default_conf_info);
510 new->token = tok;
511 }
512
513 return new;
514}
515
516static int token_matches_item(const char *tok, struct trailer_item *item, int tok_len)
517{
518 if (!strncasecmp(tok, item->conf.name, tok_len))
519 return 1;
520 return item->conf.key ? !strncasecmp(tok, item->conf.key, tok_len) : 0;
521}
522
523static struct trailer_item *create_trailer_item(const char *string)
524{
525 struct strbuf tok = STRBUF_INIT;
526 struct strbuf val = STRBUF_INIT;
527 struct trailer_item *item;
528 int tok_len;
529
530 if (parse_trailer(&tok, &val, string))
531 return NULL;
532
533 tok_len = token_len_without_separator(tok.buf, tok.len);
534
535 /* Lookup if the token matches something in the config */
536 for (item = first_conf_item; item; item = item->next) {
537 if (token_matches_item(tok.buf, item, tok_len))
538 return new_trailer_item(item,
539 strbuf_detach(&tok, NULL),
540 strbuf_detach(&val, NULL));
541 }
542
543 return new_trailer_item(NULL,
544 strbuf_detach(&tok, NULL),
545 strbuf_detach(&val, NULL));
546}
547
548static void add_trailer_item(struct trailer_item **first,
549 struct trailer_item **last,
550 struct trailer_item *new)
551{
552 if (!new)
553 return;
554 if (!*last) {
555 *first = new;
556 *last = new;
557 } else {
558 (*last)->next = new;
559 new->previous = *last;
560 *last = new;
561 }
562}
563
564static struct trailer_item *process_command_line_args(struct string_list *trailers)
565{
566 struct trailer_item *arg_tok_first = NULL;
567 struct trailer_item *arg_tok_last = NULL;
568 struct string_list_item *tr;
569
570 for_each_string_list_item(tr, trailers) {
571 struct trailer_item *new = create_trailer_item(tr->string);
572 add_trailer_item(&arg_tok_first, &arg_tok_last, new);
573 }
574
575 return arg_tok_first;
576}