Line Flow Count Block(s) Source
1 - /*
2 - * Copyright (C) the libgit2 contributors. All rights reserved.
3 - *
4 - * This file is part of libgit2, distributed under the GNU GPL v2 with
5 - * a Linking Exception. For full terms see the included COPYING file.
6 - */
7 -
8 - #include "mailmap.h"
9 -
10 - #include "common.h"
11 - #include "path.h"
12 - #include "repository.h"
13 - #include "signature.h"
14 - #include "git2/config.h"
15 - #include "git2/revparse.h"
16 - #include "blob.h"
17 - #include "parse.h"
18 -
19 - #define MM_FILE ".mailmap"
20 - #define MM_FILE_CONFIG "mailmap.file"
21 - #define MM_BLOB_CONFIG "mailmap.blob"
22 - #define MM_BLOB_DEFAULT "HEAD:" MM_FILE
23 -
24 81 2 static void mailmap_entry_free(git_mailmap_entry *entry)
25 - {
26 81 2 if (!entry)
27 81 3,9 return;
28 -
29 81 4 git__free(entry->real_name);
30 81 5 git__free(entry->real_email);
31 81 6 git__free(entry->replace_name);
32 81 7 git__free(entry->replace_email);
33 81 8 git__free(entry);
34 - }
35 -
36 - /*
37 - * First we sort by replace_email, then replace_name (if present).
38 - * Entries with names are greater than entries without.
39 - */
40 500 2 static int mailmap_entry_cmp(const void *a_raw, const void *b_raw)
41 - {
42 500 2 const git_mailmap_entry *a = (const git_mailmap_entry *)a_raw;
43 500 2 const git_mailmap_entry *b = (const git_mailmap_entry *)b_raw;
44 - int cmp;
45 -
46 500 2-6 assert(a && b && a->replace_email && b->replace_email);
47 -
48 500 7 cmp = git__strcmp(a->replace_email, b->replace_email);
49 500 7 if (cmp)
50 299 8 return cmp;
51 -
52 - /* NULL replace_names are less than not-NULL ones */
53 201 9,10 if (a->replace_name == NULL || b->replace_name == NULL)
54 180 11 return (int)(a->replace_name != NULL) - (int)(b->replace_name != NULL);
55 -
56 21 12 return git__strcmp(a->replace_name, b->replace_name);
57 - }
58 -
59 - /* Replace the old entry with the new on duplicate. */
60 2 2 static int mailmap_entry_replace(void **old_raw, void *new_raw)
61 - {
62 2 2 mailmap_entry_free((git_mailmap_entry *)*old_raw);
63 2 3 *old_raw = new_raw;
64 2 3 return GIT_EEXISTS;
65 - }
66 -
67 - /* Check if we're at the end of line, w/ comments */
68 334 2 static bool is_eol(git_parse_ctx *ctx)
69 - {
70 - char c;
71 334 2 return git_parse_peek(&c, ctx, GIT_PARSE_PEEK_SKIP_WHITESPACE) < 0 || c == '#';
72 - }
73 -
74 291 2 static int advance_until(
75 - const char **start, size_t *len, git_parse_ctx *ctx, char needle)
76 - {
77 291 2 *start = ctx->line;
78 3423 2,4-6 while (ctx->line_len > 0 && *ctx->line != '#' && *ctx->line != needle)
79 3132 3 git_parse_advance_chars(ctx, 1);
80 -
81 291 7,8 if (ctx->line_len == 0 || *ctx->line == '#')
82 5 9 return -1; /* end of line */
83 -
84 286 10 *len = ctx->line - *start;
85 286 10 git_parse_advance_chars(ctx, 1); /* advance past needle */
86 286 11 return 0;
87 - }
88 -
89 - /*
90 - * Parse a single entry from a mailmap file.
91 - *
92 - * The output git_bufs will be non-owning, and should be copied before being
93 - * persisted.
94 - */
95 172 2 static int parse_mailmap_entry(
96 - git_buf *real_name, git_buf *real_email,
97 - git_buf *replace_name, git_buf *replace_email,
98 - git_parse_ctx *ctx)
99 - {
100 - const char *start;
101 - size_t len;
102 -
103 172 2 git_buf_clear(real_name);
104 172 3 git_buf_clear(real_email);
105 172 4 git_buf_clear(replace_name);
106 172 5 git_buf_clear(replace_email);
107 -
108 172 6 git_parse_advance_ws(ctx);
109 172 7,8 if (is_eol(ctx))
110 86 9 return -1; /* blank line */
111 -
112 - /* Parse the real name */
113 86 10,11 if (advance_until(&start, &len, ctx, '<') < 0)
114 5 12 return -1;
115 -
116 81 13 git_buf_attach_notowned(real_name, start, len);
117 81 14 git_buf_rtrim(real_name);
118 -
119 - /*
120 - * If this is the last email in the line, this is the email to replace,
121 - * otherwise, it's the real email.
122 - */
123 81 15,16 if (advance_until(&start, &len, ctx, '>') < 0)
124 ##### 17 return -1;
125 -
126 - /* If we aren't at the end of the line, parse a second name and email */
127 81 18,19 if (!is_eol(ctx)) {
128 62 20 git_buf_attach_notowned(real_email, start, len);
129 -
130 62 21 git_parse_advance_ws(ctx);
131 62 22,23 if (advance_until(&start, &len, ctx, '<') < 0)
132 ##### 24 return -1;
133 62 25 git_buf_attach_notowned(replace_name, start, len);
134 62 26 git_buf_rtrim(replace_name);
135 -
136 62 27,28 if (advance_until(&start, &len, ctx, '>') < 0)
137 ##### 29 return -1;
138 - }
139 -
140 81 30 git_buf_attach_notowned(replace_email, start, len);
141 -
142 81 31,32 if (!is_eol(ctx))
143 ##### 33 return -1;
144 -
145 81 34 return 0;
146 - }
147 -
148 13 2 int git_mailmap_new(git_mailmap **out)
149 - {
150 - int error;
151 13 2 git_mailmap *mm = git__calloc(1, sizeof(git_mailmap));
152 13 3,4 GIT_ERROR_CHECK_ALLOC(mm);
153 -
154 13 5 error = git_vector_init(&mm->entries, 0, mailmap_entry_cmp);
155 13 6 if (error < 0) {
156 ##### 7 git__free(mm);
157 ##### 8 return error;
158 - }
159 13 9 *out = mm;
160 13 9 return 0;
161 - }
162 -
163 43 2 void git_mailmap_free(git_mailmap *mm)
164 - {
165 - size_t idx;
166 - git_mailmap_entry *entry;
167 43 2 if (!mm)
168 43 3,11 return;
169 -
170 92 4,6-8 git_vector_foreach(&mm->entries, idx, entry)
171 79 5 mailmap_entry_free(entry);
172 -
173 13 9 git_vector_free(&mm->entries);
174 13 10 git__free(mm);
175 - }
176 -
177 81 2 static int mailmap_add_entry_unterminated(
178 - git_mailmap *mm,
179 - const char *real_name, size_t real_name_size,
180 - const char *real_email, size_t real_email_size,
181 - const char *replace_name, size_t replace_name_size,
182 - const char *replace_email, size_t replace_email_size)
183 - {
184 - int error;
185 81 2 git_mailmap_entry *entry = git__calloc(1, sizeof(git_mailmap_entry));
186 81 3,4 GIT_ERROR_CHECK_ALLOC(entry);
187 -
188 81 5-8 assert(mm && replace_email && *replace_email);
189 -
190 81 9 if (real_name_size > 0) {
191 57 10 entry->real_name = git__substrdup(real_name, real_name_size);
192 57 11,12 GIT_ERROR_CHECK_ALLOC(entry->real_name);
193 - }
194 81 13 if (real_email_size > 0) {
195 62 14 entry->real_email = git__substrdup(real_email, real_email_size);
196 62 15,16 GIT_ERROR_CHECK_ALLOC(entry->real_email);
197 - }
198 81 17 if (replace_name_size > 0) {
199 26 18 entry->replace_name = git__substrdup(replace_name, replace_name_size);
200 26 19,20 GIT_ERROR_CHECK_ALLOC(entry->replace_name);
201 - }
202 81 21 entry->replace_email = git__substrdup(replace_email, replace_email_size);
203 81 22,23 GIT_ERROR_CHECK_ALLOC(entry->replace_email);
204 -
205 81 24 error = git_vector_insert_sorted(&mm->entries, entry, mailmap_entry_replace);
206 81 25 if (error == GIT_EEXISTS)
207 2 26 error = GIT_OK;
208 79 27 else if (error < 0)
209 ##### 28 mailmap_entry_free(entry);
210 -
211 81 29 return error;
212 - }
213 -
214 ##### 2 int git_mailmap_add_entry(
215 - git_mailmap *mm, const char *real_name, const char *real_email,
216 - const char *replace_name, const char *replace_email)
217 - {
218 ##### 2 return mailmap_add_entry_unterminated(
219 - mm,
220 - real_name, real_name ? strlen(real_name) : 0,
221 - real_email, real_email ? strlen(real_email) : 0,
222 - replace_name, replace_name ? strlen(replace_name) : 0,
223 - replace_email, strlen(replace_email));
224 - }
225 -
226 15 2 static int mailmap_add_buffer(git_mailmap *mm, const char *buf, size_t len)
227 - {
228 15 2 int error = 0;
229 - git_parse_ctx ctx;
230 -
231 - /* Scratch buffers containing the real parsed names & emails */
232 15 2 git_buf real_name = GIT_BUF_INIT;
233 15 2 git_buf real_email = GIT_BUF_INIT;
234 15 2 git_buf replace_name = GIT_BUF_INIT;
235 15 2 git_buf replace_email = GIT_BUF_INIT;
236 -
237 - /* Buffers may not contain '\0's. */
238 15 2 if (memchr(buf, '\0', len) != NULL)
239 ##### 3 return -1;
240 -
241 15 4 git_parse_ctx_init(&ctx, buf, len);
242 -
243 - /* Run the parser */
244 187 5,14 while (ctx.remain_len > 0) {
245 172 6 error = parse_mailmap_entry(
246 - &real_name, &real_email, &replace_name, &replace_email, &ctx);
247 172 7 if (error < 0) {
248 91 8 error = 0; /* Skip lines which don't contain a valid entry */
249 91 8 git_parse_advance_line(&ctx);
250 91 9 continue; /* TODO: warn */
251 - }
252 -
253 - /* NOTE: Can't use add_entry(...) as our buffers aren't terminated */
254 81 10,10,10,10,10 error = mailmap_add_entry_unterminated(
255 81 10,10 mm, real_name.ptr, real_name.size, real_email.ptr, real_email.size,
256 81 10,10 replace_name.ptr, replace_name.size, replace_email.ptr, replace_email.size);
257 81 11 if (error < 0)
258 ##### 12 goto cleanup;
259 -
260 81 13 error = 0;
261 - }
262 -
263 - cleanup:
264 15 15 git_buf_dispose(&real_name);
265 15 16 git_buf_dispose(&real_email);
266 15 17 git_buf_dispose(&replace_name);
267 15 18 git_buf_dispose(&replace_email);
268 15 19 return error;
269 - }
270 -
271 7 2 int git_mailmap_from_buffer(git_mailmap **out, const char *data, size_t len)
272 - {
273 7 2 int error = git_mailmap_new(out);
274 7 3 if (error < 0)
275 ##### 4 return error;
276 -
277 7 5 error = mailmap_add_buffer(*out, data, len);
278 7 6 if (error < 0) {
279 ##### 7 git_mailmap_free(*out);
280 ##### 8 *out = NULL;
281 - }
282 7 9 return error;
283 - }
284 -
285 3 2 static int mailmap_add_blob(
286 - git_mailmap *mm, git_repository *repo, const char *rev)
287 - {
288 3 2 git_object *object = NULL;
289 3 2 git_blob *blob = NULL;
290 3 2 git_buf content = GIT_BUF_INIT;
291 - int error;
292 -
293 3 2-4 assert(mm && repo);
294 -
295 3 5 error = git_revparse_single(&object, repo, rev);
296 3 6 if (error < 0)
297 ##### 7 goto cleanup;
298 -
299 3 8 error = git_object_peel((git_object **)&blob, object, GIT_OBJECT_BLOB);
300 3 9 if (error < 0)
301 ##### 10 goto cleanup;
302 -
303 3 11 error = git_blob__getbuf(&content, blob);
304 3 12 if (error < 0)
305 ##### 13 goto cleanup;
306 -
307 3 14 error = mailmap_add_buffer(mm, content.ptr, content.size);
308 3 15 if (error < 0)
309 ##### 16 goto cleanup;
310 -
311 - cleanup:
312 3 17 git_buf_dispose(&content);
313 3 18 git_blob_free(blob);
314 3 19 git_object_free(object);
315 3 20 return error;
316 - }
317 -
318 5 2 static int mailmap_add_file_ondisk(
319 - git_mailmap *mm, const char *path, git_repository *repo)
320 - {
321 5 2-4 const char *base = repo ? git_repository_workdir(repo) : NULL;
322 5 5 git_buf fullpath = GIT_BUF_INIT;
323 5 5 git_buf content = GIT_BUF_INIT;
324 - int error;
325 -
326 5 5 error = git_path_join_unrooted(&fullpath, path, base, NULL);
327 5 6 if (error < 0)
328 ##### 7 goto cleanup;
329 -
330 5 8 error = git_futils_readbuffer(&content, fullpath.ptr);
331 5 9 if (error < 0)
332 ##### 10 goto cleanup;
333 -
334 5 11 error = mailmap_add_buffer(mm, content.ptr, content.size);
335 5 12 if (error < 0)
336 ##### 13 goto cleanup;
337 -
338 - cleanup:
339 5 14 git_buf_dispose(&fullpath);
340 5 15 git_buf_dispose(&content);
341 5 16 return error;
342 - }
343 -
344 - /* NOTE: Only expose with an error return, currently never errors */
345 6 2 static void mailmap_add_from_repository(git_mailmap *mm, git_repository *repo)
346 - {
347 6 2 git_config *config = NULL;
348 6 2 git_buf rev_buf = GIT_BUF_INIT;
349 6 2 git_buf path_buf = GIT_BUF_INIT;
350 6 2 const char *rev = NULL;
351 6 2 const char *path = NULL;
352 -
353 6 2-4 assert(mm && repo);
354 -
355 - /* If we're in a bare repo, default blob to 'HEAD:.mailmap' */
356 6 5 if (repo->is_bare)
357 2 6 rev = MM_BLOB_DEFAULT;
358 -
359 - /* Try to load 'mailmap.file' and 'mailmap.blob' cfgs from the repo */
360 6 7,8 if (git_repository_config(&config, repo) == 0) {
361 6 9,10 if (git_config_get_string_buf(&rev_buf, config, MM_BLOB_CONFIG) == 0)
362 2 11 rev = rev_buf.ptr;
363 6 12,13 if (git_config_get_path(&path_buf, config, MM_FILE_CONFIG) == 0)
364 1 14 path = path_buf.ptr;
365 - }
366 -
367 - /*
368 - * Load mailmap files in order, overriding previous entries with new ones.
369 - * 1. The '.mailmap' file in the repository's workdir root,
370 - * 2. The blob described by the 'mailmap.blob' config (default HEAD:.mailmap),
371 - * 3. The file described by the 'mailmap.file' config.
372 - *
373 - * We ignore errors from these loads, as these files may not exist, or may
374 - * contain invalid information, and we don't want to report that error.
375 - *
376 - * XXX: Warn?
377 - */
378 6 15 if (!repo->is_bare)
379 4 16 mailmap_add_file_ondisk(mm, MM_FILE, repo);
380 6 17 if (rev != NULL)
381 3 18 mailmap_add_blob(mm, repo, rev);
382 6 19 if (path != NULL)
383 1 20 mailmap_add_file_ondisk(mm, path, repo);
384 -
385 6 21 git_buf_dispose(&rev_buf);
386 6 22 git_buf_dispose(&path_buf);
387 6 23 git_config_free(config);
388 6 24 }
389 -
390 6 2 int git_mailmap_from_repository(git_mailmap **out, git_repository *repo)
391 - {
392 6 2 int error = git_mailmap_new(out);
393 6 3 if (error < 0)
394 ##### 4 return error;
395 6 5 mailmap_add_from_repository(*out, repo);
396 6 6 return 0;
397 - }
398 -
399 290 2 const git_mailmap_entry *git_mailmap_entry_lookup(
400 - const git_mailmap *mm, const char *name, const char *email)
401 - {
402 - int error;
403 290 2 ssize_t fallback = -1;
404 - size_t idx;
405 - git_mailmap_entry *entry;
406 -
407 - /* The lookup needle we want to use only sets the replace_email. */
408 290 2 git_mailmap_entry needle = { NULL };
409 290 2 needle.replace_email = (char *)email;
410 -
411 290 2,3 assert(email);
412 -
413 290 4 if (!mm)
414 163 5 return NULL;
415 -
416 - /*
417 - * We want to find the place to start looking. so we do a binary search for
418 - * the "fallback" nameless entry. If we find it, we advance past it and record
419 - * the index.
420 - */
421 127 6 error = git_vector_bsearch(&idx, (git_vector *)&mm->entries, &needle);
422 127 7 if (error >= 0)
423 66 8 fallback = idx++;
424 61 9 else if (error != GIT_ENOTFOUND)
425 ##### 10 return NULL;
426 -
427 - /* do a linear search for an exact match */
428 182 11,20-22 for (; idx < git_vector_length(&mm->entries); ++idx) {
429 171 12 entry = git_vector_get(&mm->entries, idx);
430 -
431 171 13 if (git__strcmp(entry->replace_email, email))
432 81 14 break; /* it's a different email, so we're done looking */
433 -
434 90 15,16 assert(entry->replace_name); /* should be specific */
435 90 17,18 if (!name || !git__strcmp(entry->replace_name, name))
436 35 19 return entry;
437 - }
438 -
439 92 23 if (fallback < 0)
440 26 24 return NULL; /* no fallback */
441 66 25 return git_vector_get(&mm->entries, fallback);
442 - }
443 -
444 253 2 int git_mailmap_resolve(
445 - const char **real_name, const char **real_email,
446 - const git_mailmap *mailmap,
447 - const char *name, const char *email)
448 - {
449 253 2 const git_mailmap_entry *entry = NULL;
450 253 2-4 assert(name && email);
451 -
452 253 5 *real_name = name;
453 253 5 *real_email = email;
454 -
455 253 5,6 if ((entry = git_mailmap_entry_lookup(mailmap, name, email))) {
456 65 7 if (entry->real_name)
457 42 8 *real_name = entry->real_name;
458 65 9 if (entry->real_email)
459 51 10 *real_email = entry->real_email;
460 - }
461 253 11 return 0;
462 - }
463 -
464 173 2 int git_mailmap_resolve_signature(
465 - git_signature **out, const git_mailmap *mailmap, const git_signature *sig)
466 - {
467 173 2 const char *name = NULL;
468 173 2 const char *email = NULL;
469 - int error;
470 -
471 173 2 if (!sig)
472 ##### 3 return 0;
473 -
474 173 4 error = git_mailmap_resolve(&name, &email, mailmap, sig->name, sig->email);
475 173 5 if (error < 0)
476 ##### 6 return error;
477 -
478 173 7 error = git_signature_new(out, name, email, sig->when.time, sig->when.offset);
479 173 8 if (error < 0)
480 ##### 9 return error;
481 -
482 - /* Copy over the sign, as git_signature_new doesn't let you pass it. */
483 173 10 (*out)->when.sign = sig->when.sign;
484 173 10 return 0;
485 - }