trailer: read and process config information
[git/git.git] / trailer.c
CommitLineData
9385b5d7
CC
1#include "cache.h"
2/*
3 * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>
4 */
5
6enum action_where { WHERE_END, WHERE_AFTER, WHERE_BEFORE, WHERE_START };
7enum action_if_exists { EXISTS_ADD_IF_DIFFERENT_NEIGHBOR, EXISTS_ADD_IF_DIFFERENT,
8 EXISTS_ADD, EXISTS_REPLACE, EXISTS_DO_NOTHING };
9enum action_if_missing { MISSING_ADD, MISSING_DO_NOTHING };
10
11struct 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;
18};
19
20static struct conf_info default_conf_info;
21
22struct 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;
28};
29
30static struct trailer_item *first_conf_item;
31
32static char *separators = ":";
33
34static int after_or_end(enum action_where where)
35{
36 return (where == WHERE_AFTER) || (where == WHERE_END);
37}
38
39/*
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.
44 */
45static size_t token_len_without_separator(const char *token, size_t len)
46{
47 while (len > 0 && !isalnum(token[len - 1]))
48 len--;
49 return len;
50}
51
52static int same_token(struct trailer_item *a, struct trailer_item *b)
53{
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;
57
58 return !strncasecmp(a->token, b->token, min_len);
59}
60
61static int same_value(struct trailer_item *a, struct trailer_item *b)
62{
63 return !strcasecmp(a->value, b->value);
64}
65
66static int same_trailer(struct trailer_item *a, struct trailer_item *b)
67{
68 return same_token(a, b) && same_value(a, b);
69}
4103818d
CC
70
71static void free_trailer_item(struct trailer_item *item)
72{
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);
79}
80
81static void update_last(struct trailer_item **last)
82{
83 if (*last)
84 while ((*last)->next != NULL)
85 *last = (*last)->next;
86}
87
88static void update_first(struct trailer_item **first)
89{
90 if (*first)
91 while ((*first)->previous != NULL)
92 *first = (*first)->previous;
93}
94
95static 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)
99{
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);
114 }
115}
116
117static int check_if_different(struct trailer_item *in_tok,
118 struct trailer_item *arg_tok,
119 int check_all)
120{
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;
127 /*
128 * if we want to add a trailer after another one,
129 * we have to check those before this one
130 */
131 in_tok = after_or_end(where) ? in_tok->previous : in_tok->next;
132 } while (check_all);
133 return 1;
134}
135
136static void remove_from_list(struct trailer_item *item,
137 struct trailer_item **first,
138 struct trailer_item **last)
139{
140 struct trailer_item *next = item->next;
141 struct trailer_item *previous = item->previous;
142
143 if (next) {
144 item->next->previous = previous;
145 item->next = NULL;
146 } else if (last)
147 *last = previous;
148
149 if (previous) {
150 item->previous->next = next;
151 item->previous = NULL;
152 } else if (first)
153 *first = next;
154}
155
156static struct trailer_item *remove_first(struct trailer_item **first)
157{
158 struct trailer_item *item = *first;
159 *first = item->next;
160 if (item->next) {
161 item->next->previous = NULL;
162 item->next = NULL;
163 }
164 return item;
165}
166
167static 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)
172{
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;
201 }
202}
203
204static void apply_arg_if_missing(struct trailer_item **in_tok_first,
205 struct trailer_item **in_tok_last,
206 struct trailer_item *arg_tok)
207{
208 struct trailer_item **in_tok;
209 enum action_where where;
210
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;
224 }
225 break;
226 }
227}
228
229static 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)
232{
233 struct trailer_item *in_tok;
234 struct trailer_item *on_tok;
235 struct trailer_item *following_tok;
236
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;
241
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;
250 }
251 return 0;
252}
253
254static void process_trailers_lists(struct trailer_item **in_tok_first,
255 struct trailer_item **in_tok_last,
256 struct trailer_item **arg_tok_first)
257{
258 struct trailer_item *arg_tok;
259 struct trailer_item *next_arg;
260
261 if (!*arg_tok_first)
262 return;
263
264 for (arg_tok = *arg_tok_first; arg_tok; arg_tok = next_arg) {
265 int applied = 0;
266
267 next_arg = arg_tok->next;
268 remove_from_list(arg_tok, arg_tok_first, NULL);
269
270 applied = find_same_and_apply_arg(in_tok_first,
271 in_tok_last,
272 arg_tok);
273
274 if (!applied)
275 apply_arg_if_missing(in_tok_first,
276 in_tok_last,
277 arg_tok);
278 }
279}
46a0613f
CC
280
281static int set_where(struct conf_info *item, const char *value)
282{
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;
294}
295
296static int set_if_exists(struct conf_info *item, const char *value)
297{
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;
311}
312
313static int set_if_missing(struct conf_info *item, const char *value)
314{
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;
322}
323
324static void duplicate_conf(struct conf_info *dst, struct conf_info *src)
325{
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);
333}
334
335static struct trailer_item *get_conf_item(const char *name)
336{
337 struct trailer_item *item;
338 struct trailer_item *previous;
339
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;
346 }
347
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);
352
353 if (!previous)
354 first_conf_item = item;
355 else {
356 previous->next = item;
357 item->previous = previous;
358 }
359
360 return item;
361}
362
363enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,
364 TRAILER_IF_EXISTS, TRAILER_IF_MISSING };
365
366static 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 }
375};
376
377static int git_trailer_default_config(const char *conf_key, const char *value, void *cb)
378{
379 const char *trailer_item, *variable_name;
380
381 if (!skip_prefix(conf_key, "trailer.", &trailer_item))
382 return 0;
383
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);
400 }
401 }
402 return 0;
403}
404
405static int git_trailer_config(const char *conf_key, const char *value, void *cb)
406{
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;
413
414 if (!skip_prefix(conf_key, "trailer.", &trailer_item))
415 return 0;
416
417 variable_name = strrchr(trailer_item, '.');
418 if (!variable_name)
419 return 0;
420
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;
428 }
429
430 if (!name)
431 return 0;
432
433 item = get_conf_item(name);
434 conf = &item->conf;
435 free(name);
436
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");
462 }
463 return 0;
464}