trailer: read and process config information
[alt-git.git] / trailer.c
blob668dc3373053b9940df038c107e9b9bfd6561c7d
1 #include "cache.h"
2 /*
3 * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>
4 */
6 enum action_where { WHERE_END, WHERE_AFTER, WHERE_BEFORE, WHERE_START };
7 enum action_if_exists { EXISTS_ADD_IF_DIFFERENT_NEIGHBOR, EXISTS_ADD_IF_DIFFERENT,
8 EXISTS_ADD, EXISTS_REPLACE, EXISTS_DO_NOTHING };
9 enum action_if_missing { MISSING_ADD, MISSING_DO_NOTHING };
11 struct conf_info {
12 char *name;
13 char *key;
14 char *command;
15 enum action_where where;
16 enum action_if_exists if_exists;
17 enum action_if_missing if_missing;
20 static struct conf_info default_conf_info;
22 struct trailer_item {
23 struct trailer_item *previous;
24 struct trailer_item *next;
25 const char *token;
26 const char *value;
27 struct conf_info conf;
30 static struct trailer_item *first_conf_item;
32 static char *separators = ":";
34 static int after_or_end(enum action_where where)
36 return (where == WHERE_AFTER) || (where == WHERE_END);
40 * Return the length of the string not including any final
41 * punctuation. E.g., the input "Signed-off-by:" would return
42 * 13, stripping the trailing punctuation but retaining
43 * internal punctuation.
45 static size_t token_len_without_separator(const char *token, size_t len)
47 while (len > 0 && !isalnum(token[len - 1]))
48 len--;
49 return len;
52 static int same_token(struct trailer_item *a, struct trailer_item *b)
54 size_t a_len = token_len_without_separator(a->token, strlen(a->token));
55 size_t b_len = token_len_without_separator(b->token, strlen(b->token));
56 size_t min_len = (a_len > b_len) ? b_len : a_len;
58 return !strncasecmp(a->token, b->token, min_len);
61 static int same_value(struct trailer_item *a, struct trailer_item *b)
63 return !strcasecmp(a->value, b->value);
66 static int same_trailer(struct trailer_item *a, struct trailer_item *b)
68 return same_token(a, b) && same_value(a, b);
71 static void free_trailer_item(struct trailer_item *item)
73 free(item->conf.name);
74 free(item->conf.key);
75 free(item->conf.command);
76 free((char *)item->token);
77 free((char *)item->value);
78 free(item);
81 static void update_last(struct trailer_item **last)
83 if (*last)
84 while ((*last)->next != NULL)
85 *last = (*last)->next;
88 static void update_first(struct trailer_item **first)
90 if (*first)
91 while ((*first)->previous != NULL)
92 *first = (*first)->previous;
95 static void add_arg_to_input_list(struct trailer_item *on_tok,
96 struct trailer_item *arg_tok,
97 struct trailer_item **first,
98 struct trailer_item **last)
100 if (after_or_end(arg_tok->conf.where)) {
101 arg_tok->next = on_tok->next;
102 on_tok->next = arg_tok;
103 arg_tok->previous = on_tok;
104 if (arg_tok->next)
105 arg_tok->next->previous = arg_tok;
106 update_last(last);
107 } else {
108 arg_tok->previous = on_tok->previous;
109 on_tok->previous = arg_tok;
110 arg_tok->next = on_tok;
111 if (arg_tok->previous)
112 arg_tok->previous->next = arg_tok;
113 update_first(first);
117 static int check_if_different(struct trailer_item *in_tok,
118 struct trailer_item *arg_tok,
119 int check_all)
121 enum action_where where = arg_tok->conf.where;
122 do {
123 if (!in_tok)
124 return 1;
125 if (same_trailer(in_tok, arg_tok))
126 return 0;
128 * if we want to add a trailer after another one,
129 * we have to check those before this one
131 in_tok = after_or_end(where) ? in_tok->previous : in_tok->next;
132 } while (check_all);
133 return 1;
136 static void remove_from_list(struct trailer_item *item,
137 struct trailer_item **first,
138 struct trailer_item **last)
140 struct trailer_item *next = item->next;
141 struct trailer_item *previous = item->previous;
143 if (next) {
144 item->next->previous = previous;
145 item->next = NULL;
146 } else if (last)
147 *last = previous;
149 if (previous) {
150 item->previous->next = next;
151 item->previous = NULL;
152 } else if (first)
153 *first = next;
156 static struct trailer_item *remove_first(struct trailer_item **first)
158 struct trailer_item *item = *first;
159 *first = item->next;
160 if (item->next) {
161 item->next->previous = NULL;
162 item->next = NULL;
164 return item;
167 static void apply_arg_if_exists(struct trailer_item *in_tok,
168 struct trailer_item *arg_tok,
169 struct trailer_item *on_tok,
170 struct trailer_item **in_tok_first,
171 struct trailer_item **in_tok_last)
173 switch (arg_tok->conf.if_exists) {
174 case EXISTS_DO_NOTHING:
175 free_trailer_item(arg_tok);
176 break;
177 case EXISTS_REPLACE:
178 add_arg_to_input_list(on_tok, arg_tok,
179 in_tok_first, in_tok_last);
180 remove_from_list(in_tok, in_tok_first, in_tok_last);
181 free_trailer_item(in_tok);
182 break;
183 case EXISTS_ADD:
184 add_arg_to_input_list(on_tok, arg_tok,
185 in_tok_first, in_tok_last);
186 break;
187 case EXISTS_ADD_IF_DIFFERENT:
188 if (check_if_different(in_tok, arg_tok, 1))
189 add_arg_to_input_list(on_tok, arg_tok,
190 in_tok_first, in_tok_last);
191 else
192 free_trailer_item(arg_tok);
193 break;
194 case EXISTS_ADD_IF_DIFFERENT_NEIGHBOR:
195 if (check_if_different(on_tok, arg_tok, 0))
196 add_arg_to_input_list(on_tok, arg_tok,
197 in_tok_first, in_tok_last);
198 else
199 free_trailer_item(arg_tok);
200 break;
204 static void apply_arg_if_missing(struct trailer_item **in_tok_first,
205 struct trailer_item **in_tok_last,
206 struct trailer_item *arg_tok)
208 struct trailer_item **in_tok;
209 enum action_where where;
211 switch (arg_tok->conf.if_missing) {
212 case MISSING_DO_NOTHING:
213 free_trailer_item(arg_tok);
214 break;
215 case MISSING_ADD:
216 where = arg_tok->conf.where;
217 in_tok = after_or_end(where) ? in_tok_last : in_tok_first;
218 if (*in_tok) {
219 add_arg_to_input_list(*in_tok, arg_tok,
220 in_tok_first, in_tok_last);
221 } else {
222 *in_tok_first = arg_tok;
223 *in_tok_last = arg_tok;
225 break;
229 static int find_same_and_apply_arg(struct trailer_item **in_tok_first,
230 struct trailer_item **in_tok_last,
231 struct trailer_item *arg_tok)
233 struct trailer_item *in_tok;
234 struct trailer_item *on_tok;
235 struct trailer_item *following_tok;
237 enum action_where where = arg_tok->conf.where;
238 int middle = (where == WHERE_AFTER) || (where == WHERE_BEFORE);
239 int backwards = after_or_end(where);
240 struct trailer_item *start_tok = backwards ? *in_tok_last : *in_tok_first;
242 for (in_tok = start_tok; in_tok; in_tok = following_tok) {
243 following_tok = backwards ? in_tok->previous : in_tok->next;
244 if (!same_token(in_tok, arg_tok))
245 continue;
246 on_tok = middle ? in_tok : start_tok;
247 apply_arg_if_exists(in_tok, arg_tok, on_tok,
248 in_tok_first, in_tok_last);
249 return 1;
251 return 0;
254 static void process_trailers_lists(struct trailer_item **in_tok_first,
255 struct trailer_item **in_tok_last,
256 struct trailer_item **arg_tok_first)
258 struct trailer_item *arg_tok;
259 struct trailer_item *next_arg;
261 if (!*arg_tok_first)
262 return;
264 for (arg_tok = *arg_tok_first; arg_tok; arg_tok = next_arg) {
265 int applied = 0;
267 next_arg = arg_tok->next;
268 remove_from_list(arg_tok, arg_tok_first, NULL);
270 applied = find_same_and_apply_arg(in_tok_first,
271 in_tok_last,
272 arg_tok);
274 if (!applied)
275 apply_arg_if_missing(in_tok_first,
276 in_tok_last,
277 arg_tok);
281 static int set_where(struct conf_info *item, const char *value)
283 if (!strcasecmp("after", value))
284 item->where = WHERE_AFTER;
285 else if (!strcasecmp("before", value))
286 item->where = WHERE_BEFORE;
287 else if (!strcasecmp("end", value))
288 item->where = WHERE_END;
289 else if (!strcasecmp("start", value))
290 item->where = WHERE_START;
291 else
292 return -1;
293 return 0;
296 static int set_if_exists(struct conf_info *item, const char *value)
298 if (!strcasecmp("addIfDifferent", value))
299 item->if_exists = EXISTS_ADD_IF_DIFFERENT;
300 else if (!strcasecmp("addIfDifferentNeighbor", value))
301 item->if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR;
302 else if (!strcasecmp("add", value))
303 item->if_exists = EXISTS_ADD;
304 else if (!strcasecmp("replace", value))
305 item->if_exists = EXISTS_REPLACE;
306 else if (!strcasecmp("doNothing", value))
307 item->if_exists = EXISTS_DO_NOTHING;
308 else
309 return -1;
310 return 0;
313 static int set_if_missing(struct conf_info *item, const char *value)
315 if (!strcasecmp("doNothing", value))
316 item->if_missing = MISSING_DO_NOTHING;
317 else if (!strcasecmp("add", value))
318 item->if_missing = MISSING_ADD;
319 else
320 return -1;
321 return 0;
324 static void duplicate_conf(struct conf_info *dst, struct conf_info *src)
326 *dst = *src;
327 if (src->name)
328 dst->name = xstrdup(src->name);
329 if (src->key)
330 dst->key = xstrdup(src->key);
331 if (src->command)
332 dst->command = xstrdup(src->command);
335 static struct trailer_item *get_conf_item(const char *name)
337 struct trailer_item *item;
338 struct trailer_item *previous;
340 /* Look up item with same name */
341 for (previous = NULL, item = first_conf_item;
342 item;
343 previous = item, item = item->next) {
344 if (!strcasecmp(item->conf.name, name))
345 return item;
348 /* Item does not already exists, create it */
349 item = xcalloc(sizeof(struct trailer_item), 1);
350 duplicate_conf(&item->conf, &default_conf_info);
351 item->conf.name = xstrdup(name);
353 if (!previous)
354 first_conf_item = item;
355 else {
356 previous->next = item;
357 item->previous = previous;
360 return item;
363 enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,
364 TRAILER_IF_EXISTS, TRAILER_IF_MISSING };
366 static struct {
367 const char *name;
368 enum trailer_info_type type;
369 } trailer_config_items[] = {
370 { "key", TRAILER_KEY },
371 { "command", TRAILER_COMMAND },
372 { "where", TRAILER_WHERE },
373 { "ifexists", TRAILER_IF_EXISTS },
374 { "ifmissing", TRAILER_IF_MISSING }
377 static int git_trailer_default_config(const char *conf_key, const char *value, void *cb)
379 const char *trailer_item, *variable_name;
381 if (!skip_prefix(conf_key, "trailer.", &trailer_item))
382 return 0;
384 variable_name = strrchr(trailer_item, '.');
385 if (!variable_name) {
386 if (!strcmp(trailer_item, "where")) {
387 if (set_where(&default_conf_info, value) < 0)
388 warning(_("unknown value '%s' for key '%s'"),
389 value, conf_key);
390 } else if (!strcmp(trailer_item, "ifexists")) {
391 if (set_if_exists(&default_conf_info, value) < 0)
392 warning(_("unknown value '%s' for key '%s'"),
393 value, conf_key);
394 } else if (!strcmp(trailer_item, "ifmissing")) {
395 if (set_if_missing(&default_conf_info, value) < 0)
396 warning(_("unknown value '%s' for key '%s'"),
397 value, conf_key);
398 } else if (!strcmp(trailer_item, "separators")) {
399 separators = xstrdup(value);
402 return 0;
405 static int git_trailer_config(const char *conf_key, const char *value, void *cb)
407 const char *trailer_item, *variable_name;
408 struct trailer_item *item;
409 struct conf_info *conf;
410 char *name = NULL;
411 enum trailer_info_type type;
412 int i;
414 if (!skip_prefix(conf_key, "trailer.", &trailer_item))
415 return 0;
417 variable_name = strrchr(trailer_item, '.');
418 if (!variable_name)
419 return 0;
421 variable_name++;
422 for (i = 0; i < ARRAY_SIZE(trailer_config_items); i++) {
423 if (strcmp(trailer_config_items[i].name, variable_name))
424 continue;
425 name = xstrndup(trailer_item, variable_name - trailer_item - 1);
426 type = trailer_config_items[i].type;
427 break;
430 if (!name)
431 return 0;
433 item = get_conf_item(name);
434 conf = &item->conf;
435 free(name);
437 switch (type) {
438 case TRAILER_KEY:
439 if (conf->key)
440 warning(_("more than one %s"), conf_key);
441 conf->key = xstrdup(value);
442 break;
443 case TRAILER_COMMAND:
444 if (conf->command)
445 warning(_("more than one %s"), conf_key);
446 conf->command = xstrdup(value);
447 break;
448 case TRAILER_WHERE:
449 if (set_where(conf, value))
450 warning(_("unknown value '%s' for key '%s'"), value, conf_key);
451 break;
452 case TRAILER_IF_EXISTS:
453 if (set_if_exists(conf, value))
454 warning(_("unknown value '%s' for key '%s'"), value, conf_key);
455 break;
456 case TRAILER_IF_MISSING:
457 if (set_if_missing(conf, value))
458 warning(_("unknown value '%s' for key '%s'"), value, conf_key);
459 break;
460 default:
461 die("internal bug in trailer.c");
463 return 0;