File Coverage

deps/libgit2/src/libgit2/mailmap.c
Criterion Covered Total %
statement 29 254 11.4
branch 9 174 5.1
condition n/a
subroutine n/a
pod n/a
total 38 428 8.8


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