2 #include "string-list.h"
4 * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>
7 enum action_where
{ WHERE_END
, WHERE_AFTER
, WHERE_BEFORE
, WHERE_START
};
8 enum action_if_exists
{ EXISTS_ADD_IF_DIFFERENT_NEIGHBOR
, EXISTS_ADD_IF_DIFFERENT
,
9 EXISTS_ADD
, EXISTS_REPLACE
, EXISTS_DO_NOTHING
};
10 enum action_if_missing
{ MISSING_ADD
, MISSING_DO_NOTHING
};
16 enum action_where where
;
17 enum action_if_exists if_exists
;
18 enum action_if_missing if_missing
;
21 static struct conf_info default_conf_info
;
24 struct trailer_item
*previous
;
25 struct trailer_item
*next
;
28 struct conf_info conf
;
31 static struct trailer_item
*first_conf_item
;
33 static char *separators
= ":";
35 static int after_or_end(enum action_where where
)
37 return (where
== WHERE_AFTER
) || (where
== WHERE_END
);
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.
46 static size_t token_len_without_separator(const char *token
, size_t len
)
48 while (len
> 0 && !isalnum(token
[len
- 1]))
53 static int same_token(struct trailer_item
*a
, struct trailer_item
*b
)
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
;
59 return !strncasecmp(a
->token
, b
->token
, min_len
);
62 static int same_value(struct trailer_item
*a
, struct trailer_item
*b
)
64 return !strcasecmp(a
->value
, b
->value
);
67 static int same_trailer(struct trailer_item
*a
, struct trailer_item
*b
)
69 return same_token(a
, b
) && same_value(a
, b
);
72 static void free_trailer_item(struct trailer_item
*item
)
74 free(item
->conf
.name
);
76 free(item
->conf
.command
);
77 free((char *)item
->token
);
78 free((char *)item
->value
);
82 static void update_last(struct trailer_item
**last
)
85 while ((*last
)->next
!= NULL
)
86 *last
= (*last
)->next
;
89 static void update_first(struct trailer_item
**first
)
92 while ((*first
)->previous
!= NULL
)
93 *first
= (*first
)->previous
;
96 static 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
)
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
;
106 arg_tok
->next
->previous
= arg_tok
;
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
;
118 static int check_if_different(struct trailer_item
*in_tok
,
119 struct trailer_item
*arg_tok
,
122 enum action_where where
= arg_tok
->conf
.where
;
126 if (same_trailer(in_tok
, arg_tok
))
129 * if we want to add a trailer after another one,
130 * we have to check those before this one
132 in_tok
= after_or_end(where
) ? in_tok
->previous
: in_tok
->next
;
137 static void remove_from_list(struct trailer_item
*item
,
138 struct trailer_item
**first
,
139 struct trailer_item
**last
)
141 struct trailer_item
*next
= item
->next
;
142 struct trailer_item
*previous
= item
->previous
;
145 item
->next
->previous
= previous
;
151 item
->previous
->next
= next
;
152 item
->previous
= NULL
;
157 static struct trailer_item
*remove_first(struct trailer_item
**first
)
159 struct trailer_item
*item
= *first
;
162 item
->next
->previous
= NULL
;
168 static 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
)
174 switch (arg_tok
->conf
.if_exists
) {
175 case EXISTS_DO_NOTHING
:
176 free_trailer_item(arg_tok
);
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
);
185 add_arg_to_input_list(on_tok
, arg_tok
,
186 in_tok_first
, in_tok_last
);
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
);
193 free_trailer_item(arg_tok
);
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
);
200 free_trailer_item(arg_tok
);
205 static void apply_arg_if_missing(struct trailer_item
**in_tok_first
,
206 struct trailer_item
**in_tok_last
,
207 struct trailer_item
*arg_tok
)
209 struct trailer_item
**in_tok
;
210 enum action_where where
;
212 switch (arg_tok
->conf
.if_missing
) {
213 case MISSING_DO_NOTHING
:
214 free_trailer_item(arg_tok
);
217 where
= arg_tok
->conf
.where
;
218 in_tok
= after_or_end(where
) ? in_tok_last
: in_tok_first
;
220 add_arg_to_input_list(*in_tok
, arg_tok
,
221 in_tok_first
, in_tok_last
);
223 *in_tok_first
= arg_tok
;
224 *in_tok_last
= arg_tok
;
230 static 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
)
234 struct trailer_item
*in_tok
;
235 struct trailer_item
*on_tok
;
236 struct trailer_item
*following_tok
;
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
;
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
))
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
);
255 static void process_trailers_lists(struct trailer_item
**in_tok_first
,
256 struct trailer_item
**in_tok_last
,
257 struct trailer_item
**arg_tok_first
)
259 struct trailer_item
*arg_tok
;
260 struct trailer_item
*next_arg
;
265 for (arg_tok
= *arg_tok_first
; arg_tok
; arg_tok
= next_arg
) {
268 next_arg
= arg_tok
->next
;
269 remove_from_list(arg_tok
, arg_tok_first
, NULL
);
271 applied
= find_same_and_apply_arg(in_tok_first
,
276 apply_arg_if_missing(in_tok_first
,
282 static int set_where(struct conf_info
*item
, const char *value
)
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
;
297 static int set_if_exists(struct conf_info
*item
, const char *value
)
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
;
314 static int set_if_missing(struct conf_info
*item
, const char *value
)
316 if (!strcasecmp("doNothing", value
))
317 item
->if_missing
= MISSING_DO_NOTHING
;
318 else if (!strcasecmp("add", value
))
319 item
->if_missing
= MISSING_ADD
;
325 static void duplicate_conf(struct conf_info
*dst
, struct conf_info
*src
)
329 dst
->name
= xstrdup(src
->name
);
331 dst
->key
= xstrdup(src
->key
);
333 dst
->command
= xstrdup(src
->command
);
336 static struct trailer_item
*get_conf_item(const char *name
)
338 struct trailer_item
*item
;
339 struct trailer_item
*previous
;
341 /* Look up item with same name */
342 for (previous
= NULL
, item
= first_conf_item
;
344 previous
= item
, item
= item
->next
) {
345 if (!strcasecmp(item
->conf
.name
, name
))
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
);
355 first_conf_item
= item
;
357 previous
->next
= item
;
358 item
->previous
= previous
;
364 enum trailer_info_type
{ TRAILER_KEY
, TRAILER_COMMAND
, TRAILER_WHERE
,
365 TRAILER_IF_EXISTS
, TRAILER_IF_MISSING
};
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
}
378 static int git_trailer_default_config(const char *conf_key
, const char *value
, void *cb
)
380 const char *trailer_item
, *variable_name
;
382 if (!skip_prefix(conf_key
, "trailer.", &trailer_item
))
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'"),
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'"),
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'"),
399 } else if (!strcmp(trailer_item
, "separators")) {
400 separators
= xstrdup(value
);
406 static int git_trailer_config(const char *conf_key
, const char *value
, void *cb
)
408 const char *trailer_item
, *variable_name
;
409 struct trailer_item
*item
;
410 struct conf_info
*conf
;
412 enum trailer_info_type type
;
415 if (!skip_prefix(conf_key
, "trailer.", &trailer_item
))
418 variable_name
= strrchr(trailer_item
, '.');
423 for (i
= 0; i
< ARRAY_SIZE(trailer_config_items
); i
++) {
424 if (strcmp(trailer_config_items
[i
].name
, variable_name
))
426 name
= xstrndup(trailer_item
, variable_name
- trailer_item
- 1);
427 type
= trailer_config_items
[i
].type
;
434 item
= get_conf_item(name
);
441 warning(_("more than one %s"), conf_key
);
442 conf
->key
= xstrdup(value
);
444 case TRAILER_COMMAND
:
446 warning(_("more than one %s"), conf_key
);
447 conf
->command
= xstrdup(value
);
450 if (set_where(conf
, value
))
451 warning(_("unknown value '%s' for key '%s'"), value
, conf_key
);
453 case TRAILER_IF_EXISTS
:
454 if (set_if_exists(conf
, value
))
455 warning(_("unknown value '%s' for key '%s'"), value
, conf_key
);
457 case TRAILER_IF_MISSING
:
458 if (set_if_missing(conf
, value
))
459 warning(_("unknown value '%s' for key '%s'"), value
, conf_key
);
462 die("internal bug in trailer.c");
467 static int parse_trailer(struct strbuf
*tok
, struct strbuf
*val
, const char *trailer
)
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
);
476 return error(_("empty trailer token in trailer '%s'"), trailer
);
477 if (len
< strlen(trailer
)) {
478 strbuf_add(tok
, trailer
, len
);
480 strbuf_addstr(val
, trailer
+ len
+ 1);
483 strbuf_addstr(tok
, trailer
);
489 static const char *token_from_item(struct trailer_item
*item
, char *tok
)
492 return item
->conf
.key
;
495 return item
->conf
.name
;
498 static struct trailer_item
*new_trailer_item(struct trailer_item
*conf_item
,
499 char *tok
, char *val
)
501 struct trailer_item
*new = xcalloc(sizeof(*new), 1);
505 duplicate_conf(&new->conf
, &conf_item
->conf
);
506 new->token
= xstrdup(token_from_item(conf_item
, tok
));
509 duplicate_conf(&new->conf
, &default_conf_info
);
516 static int token_matches_item(const char *tok
, struct trailer_item
*item
, int tok_len
)
518 if (!strncasecmp(tok
, item
->conf
.name
, tok_len
))
520 return item
->conf
.key
? !strncasecmp(tok
, item
->conf
.key
, tok_len
) : 0;
523 static struct trailer_item
*create_trailer_item(const char *string
)
525 struct strbuf tok
= STRBUF_INIT
;
526 struct strbuf val
= STRBUF_INIT
;
527 struct trailer_item
*item
;
530 if (parse_trailer(&tok
, &val
, string
))
533 tok_len
= token_len_without_separator(tok
.buf
, tok
.len
);
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
));
543 return new_trailer_item(NULL
,
544 strbuf_detach(&tok
, NULL
),
545 strbuf_detach(&val
, NULL
));
548 static void add_trailer_item(struct trailer_item
**first
,
549 struct trailer_item
**last
,
550 struct trailer_item
*new)
559 new->previous
= *last
;
564 static struct trailer_item
*process_command_line_args(struct string_list
*trailers
)
566 struct trailer_item
*arg_tok_first
= NULL
;
567 struct trailer_item
*arg_tok_last
= NULL
;
568 struct string_list_item
*tr
;
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);
575 return arg_tok_first
;