]>
Commit | Line | Data |
---|---|---|
9385b5d7 CC |
1 | #include "cache.h" |
2 | /* | |
3 | * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org> | |
4 | */ | |
5 | ||
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 }; | |
10 | ||
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; | |
18 | }; | |
19 | ||
20 | static struct conf_info default_conf_info; | |
21 | ||
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; | |
28 | }; | |
29 | ||
30 | static struct trailer_item *first_conf_item; | |
31 | ||
32 | static char *separators = ":"; | |
33 | ||
34 | static 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 | */ | |
45 | static 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 | ||
52 | static 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 | ||
61 | static int same_value(struct trailer_item *a, struct trailer_item *b) | |
62 | { | |
63 | return !strcasecmp(a->value, b->value); | |
64 | } | |
65 | ||
66 | static 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 | |
71 | static 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 | ||
81 | static void update_last(struct trailer_item **last) | |
82 | { | |
83 | if (*last) | |
84 | while ((*last)->next != NULL) | |
85 | *last = (*last)->next; | |
86 | } | |
87 | ||
88 | static void update_first(struct trailer_item **first) | |
89 | { | |
90 | if (*first) | |
91 | while ((*first)->previous != NULL) | |
92 | *first = (*first)->previous; | |
93 | } | |
94 | ||
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) | |
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 | ||
117 | static 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 | ||
136 | static 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 | ||
156 | static 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 | ||
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) | |
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 | ||
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) | |
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 | ||
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) | |
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 | ||
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) | |
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 | |
281 | static 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 | ||
296 | static 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 | ||
313 | static 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 | ||
324 | static 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 | ||
335 | static 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 | ||
363 | enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE, | |
364 | TRAILER_IF_EXISTS, TRAILER_IF_MISSING }; | |
365 | ||
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 } | |
375 | }; | |
376 | ||
377 | static 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 | ||
405 | static 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 | } |