trailer: process trailers from input message and arguments
[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}