File Coverage

deps/libgit2/src/mailmap.c
Criterion Covered Total %
statement 28 247 11.3
branch 9 172 5.2
condition n/a
subroutine n/a
pod n/a
total 37 419 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 "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 0           static void mailmap_entry_free(git_mailmap_entry *entry)
25             {
26 0 0         if (!entry)
27 0           return;
28              
29 0           git__free(entry->real_name);
30 0           git__free(entry->real_email);
31 0           git__free(entry->replace_name);
32 0           git__free(entry->replace_email);
33 0           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 0           static int mailmap_entry_cmp(const void *a_raw, const void *b_raw)
41             {
42 0           const git_mailmap_entry *a = (const git_mailmap_entry *)a_raw;
43 0           const git_mailmap_entry *b = (const git_mailmap_entry *)b_raw;
44             int cmp;
45              
46 0 0         assert(a && b && a->replace_email && b->replace_email);
    0          
    0          
    0          
47              
48 0           cmp = git__strcmp(a->replace_email, b->replace_email);
49 0 0         if (cmp)
50 0           return cmp;
51              
52             /* NULL replace_names are less than not-NULL ones */
53 0 0         if (a->replace_name == NULL || b->replace_name == NULL)
    0          
54 0           return (int)(a->replace_name != NULL) - (int)(b->replace_name != NULL);
55              
56 0           return git__strcmp(a->replace_name, b->replace_name);
57             }
58              
59             /* Replace the old entry with the new on duplicate. */
60 0           static int mailmap_entry_replace(void **old_raw, void *new_raw)
61             {
62 0           mailmap_entry_free((git_mailmap_entry *)*old_raw);
63 0           *old_raw = new_raw;
64 0           return GIT_EEXISTS;
65             }
66              
67             /* Check if we're at the end of line, w/ comments */
68 0           static bool is_eol(git_parse_ctx *ctx)
69             {
70             char c;
71 0 0         return git_parse_peek(&c, ctx, GIT_PARSE_PEEK_SKIP_WHITESPACE) < 0 || c == '#';
    0          
72             }
73              
74 0           static int advance_until(
75             const char **start, size_t *len, git_parse_ctx *ctx, char needle)
76             {
77 0           *start = ctx->line;
78 0 0         while (ctx->line_len > 0 && *ctx->line != '#' && *ctx->line != needle)
    0          
    0          
79 0           git_parse_advance_chars(ctx, 1);
80              
81 0 0         if (ctx->line_len == 0 || *ctx->line == '#')
    0          
82 0           return -1; /* end of line */
83              
84 0           *len = ctx->line - *start;
85 0           git_parse_advance_chars(ctx, 1); /* advance past needle */
86 0           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 0           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 0           git_buf_clear(real_name);
104 0           git_buf_clear(real_email);
105 0           git_buf_clear(replace_name);
106 0           git_buf_clear(replace_email);
107              
108 0           git_parse_advance_ws(ctx);
109 0 0         if (is_eol(ctx))
110 0           return -1; /* blank line */
111              
112             /* Parse the real name */
113 0 0         if (advance_until(&start, &len, ctx, '<') < 0)
114 0           return -1;
115              
116 0           git_buf_attach_notowned(real_name, start, len);
117 0           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 0 0         if (advance_until(&start, &len, ctx, '>') < 0)
124 0           return -1;
125              
126             /* If we aren't at the end of the line, parse a second name and email */
127 0 0         if (!is_eol(ctx)) {
128 0           git_buf_attach_notowned(real_email, start, len);
129              
130 0           git_parse_advance_ws(ctx);
131 0 0         if (advance_until(&start, &len, ctx, '<') < 0)
132 0           return -1;
133 0           git_buf_attach_notowned(replace_name, start, len);
134 0           git_buf_rtrim(replace_name);
135              
136 0 0         if (advance_until(&start, &len, ctx, '>') < 0)
137 0           return -1;
138             }
139              
140 0           git_buf_attach_notowned(replace_email, start, len);
141              
142 0 0         if (!is_eol(ctx))
143 0           return -1;
144              
145 0           return 0;
146             }
147              
148 0           int git_mailmap_new(git_mailmap **out)
149             {
150             int error;
151 0           git_mailmap *mm = git__calloc(1, sizeof(git_mailmap));
152 0 0         GIT_ERROR_CHECK_ALLOC(mm);
153              
154 0           error = git_vector_init(&mm->entries, 0, mailmap_entry_cmp);
155 0 0         if (error < 0) {
156 0           git__free(mm);
157 0           return error;
158             }
159 0           *out = mm;
160 0           return 0;
161             }
162              
163 2           void git_mailmap_free(git_mailmap *mm)
164             {
165             size_t idx;
166             git_mailmap_entry *entry;
167 2 50         if (!mm)
168 2           return;
169              
170 0 0         git_vector_foreach(&mm->entries, idx, entry)
171 0           mailmap_entry_free(entry);
172              
173 0           git_vector_free(&mm->entries);
174 0           git__free(mm);
175             }
176              
177 0           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 0           git_mailmap_entry *entry = git__calloc(1, sizeof(git_mailmap_entry));
186 0 0         GIT_ERROR_CHECK_ALLOC(entry);
187              
188 0 0         assert(mm && replace_email && *replace_email);
    0          
    0          
189              
190 0 0         if (real_name_size > 0) {
191 0           entry->real_name = git__substrdup(real_name, real_name_size);
192 0 0         GIT_ERROR_CHECK_ALLOC(entry->real_name);
193             }
194 0 0         if (real_email_size > 0) {
195 0           entry->real_email = git__substrdup(real_email, real_email_size);
196 0 0         GIT_ERROR_CHECK_ALLOC(entry->real_email);
197             }
198 0 0         if (replace_name_size > 0) {
199 0           entry->replace_name = git__substrdup(replace_name, replace_name_size);
200 0 0         GIT_ERROR_CHECK_ALLOC(entry->replace_name);
201             }
202 0           entry->replace_email = git__substrdup(replace_email, replace_email_size);
203 0 0         GIT_ERROR_CHECK_ALLOC(entry->replace_email);
204              
205 0           error = git_vector_insert_sorted(&mm->entries, entry, mailmap_entry_replace);
206 0 0         if (error == GIT_EEXISTS)
207 0           error = GIT_OK;
208 0 0         else if (error < 0)
209 0           mailmap_entry_free(entry);
210              
211 0           return error;
212             }
213              
214 0           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 0 0         return mailmap_add_entry_unterminated(
    0          
    0          
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 0           static int mailmap_add_buffer(git_mailmap *mm, const char *buf, size_t len)
227             {
228 0           int error = 0;
229             git_parse_ctx ctx;
230              
231             /* Scratch buffers containing the real parsed names & emails */
232 0           git_buf real_name = GIT_BUF_INIT;
233 0           git_buf real_email = GIT_BUF_INIT;
234 0           git_buf replace_name = GIT_BUF_INIT;
235 0           git_buf replace_email = GIT_BUF_INIT;
236              
237             /* Buffers may not contain '\0's. */
238 0 0         if (memchr(buf, '\0', len) != NULL)
239 0           return -1;
240              
241 0           git_parse_ctx_init(&ctx, buf, len);
242              
243             /* Run the parser */
244 0 0         while (ctx.remain_len > 0) {
245 0           error = parse_mailmap_entry(
246             &real_name, &real_email, &replace_name, &replace_email, &ctx);
247 0 0         if (error < 0) {
248 0           error = 0; /* Skip lines which don't contain a valid entry */
249 0           git_parse_advance_line(&ctx);
250 0           continue; /* TODO: warn */
251             }
252              
253             /* NOTE: Can't use add_entry(...) as our buffers aren't terminated */
254 0           error = mailmap_add_entry_unterminated(
255 0           mm, real_name.ptr, real_name.size, real_email.ptr, real_email.size,
256 0           replace_name.ptr, replace_name.size, replace_email.ptr, replace_email.size);
257 0 0         if (error < 0)
258 0           goto cleanup;
259              
260 0           error = 0;
261             }
262              
263             cleanup:
264 0           git_buf_dispose(&real_name);
265 0           git_buf_dispose(&real_email);
266 0           git_buf_dispose(&replace_name);
267 0           git_buf_dispose(&replace_email);
268 0           return error;
269             }
270              
271 0           int git_mailmap_from_buffer(git_mailmap **out, const char *data, size_t len)
272             {
273 0           int error = git_mailmap_new(out);
274 0 0         if (error < 0)
275 0           return error;
276              
277 0           error = mailmap_add_buffer(*out, data, len);
278 0 0         if (error < 0) {
279 0           git_mailmap_free(*out);
280 0           *out = NULL;
281             }
282 0           return error;
283             }
284              
285 0           static int mailmap_add_blob(
286             git_mailmap *mm, git_repository *repo, const char *rev)
287             {
288 0           git_object *object = NULL;
289 0           git_blob *blob = NULL;
290 0           git_buf content = GIT_BUF_INIT;
291             int error;
292              
293 0 0         assert(mm && repo);
    0          
294              
295 0           error = git_revparse_single(&object, repo, rev);
296 0 0         if (error < 0)
297 0           goto cleanup;
298              
299 0           error = git_object_peel((git_object **)&blob, object, GIT_OBJECT_BLOB);
300 0 0         if (error < 0)
301 0           goto cleanup;
302              
303 0           error = git_blob__getbuf(&content, blob);
304 0 0         if (error < 0)
305 0           goto cleanup;
306              
307 0           error = mailmap_add_buffer(mm, content.ptr, content.size);
308 0 0         if (error < 0)
309 0           goto cleanup;
310              
311             cleanup:
312 0           git_buf_dispose(&content);
313 0           git_blob_free(blob);
314 0           git_object_free(object);
315 0           return error;
316             }
317              
318 0           static int mailmap_add_file_ondisk(
319             git_mailmap *mm, const char *path, git_repository *repo)
320             {
321 0 0         const char *base = repo ? git_repository_workdir(repo) : NULL;
322 0           git_buf fullpath = GIT_BUF_INIT;
323 0           git_buf content = GIT_BUF_INIT;
324             int error;
325              
326 0           error = git_path_join_unrooted(&fullpath, path, base, NULL);
327 0 0         if (error < 0)
328 0           goto cleanup;
329              
330 0           error = git_futils_readbuffer(&content, fullpath.ptr);
331 0 0         if (error < 0)
332 0           goto cleanup;
333              
334 0           error = mailmap_add_buffer(mm, content.ptr, content.size);
335 0 0         if (error < 0)
336 0           goto cleanup;
337              
338             cleanup:
339 0           git_buf_dispose(&fullpath);
340 0           git_buf_dispose(&content);
341 0           return error;
342             }
343              
344             /* NOTE: Only expose with an error return, currently never errors */
345 0           static void mailmap_add_from_repository(git_mailmap *mm, git_repository *repo)
346             {
347 0           git_config *config = NULL;
348 0           git_buf rev_buf = GIT_BUF_INIT;
349 0           git_buf path_buf = GIT_BUF_INIT;
350 0           const char *rev = NULL;
351 0           const char *path = NULL;
352              
353 0 0         assert(mm && repo);
    0          
354              
355             /* If we're in a bare repo, default blob to 'HEAD:.mailmap' */
356 0 0         if (repo->is_bare)
357 0           rev = MM_BLOB_DEFAULT;
358              
359             /* Try to load 'mailmap.file' and 'mailmap.blob' cfgs from the repo */
360 0 0         if (git_repository_config(&config, repo) == 0) {
361 0 0         if (git_config_get_string_buf(&rev_buf, config, MM_BLOB_CONFIG) == 0)
362 0           rev = rev_buf.ptr;
363 0 0         if (git_config_get_path(&path_buf, config, MM_FILE_CONFIG) == 0)
364 0           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 0 0         if (!repo->is_bare)
379 0           mailmap_add_file_ondisk(mm, MM_FILE, repo);
380 0 0         if (rev != NULL)
381 0           mailmap_add_blob(mm, repo, rev);
382 0 0         if (path != NULL)
383 0           mailmap_add_file_ondisk(mm, path, repo);
384              
385 0           git_buf_dispose(&rev_buf);
386 0           git_buf_dispose(&path_buf);
387 0           git_config_free(config);
388 0           }
389              
390 0           int git_mailmap_from_repository(git_mailmap **out, git_repository *repo)
391             {
392 0           int error = git_mailmap_new(out);
393 0 0         if (error < 0)
394 0           return error;
395 0           mailmap_add_from_repository(*out, repo);
396 0           return 0;
397             }
398              
399 3           const git_mailmap_entry *git_mailmap_entry_lookup(
400             const git_mailmap *mm, const char *name, const char *email)
401             {
402             int error;
403 3           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 3           git_mailmap_entry needle = { NULL };
409 3           needle.replace_email = (char *)email;
410              
411 3 50         assert(email);
412              
413 3 50         if (!mm)
414 3           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 0           error = git_vector_bsearch(&idx, (git_vector *)&mm->entries, &needle);
422 0 0         if (error >= 0)
423 0           fallback = idx++;
424 0 0         else if (error != GIT_ENOTFOUND)
425 0           return NULL;
426              
427             /* do a linear search for an exact match */
428 0 0         for (; idx < git_vector_length(&mm->entries); ++idx) {
429 0           entry = git_vector_get(&mm->entries, idx);
430              
431 0 0         if (git__strcmp(entry->replace_email, email))
432 0           break; /* it's a different email, so we're done looking */
433              
434 0 0         assert(entry->replace_name); /* should be specific */
435 0 0         if (!name || !git__strcmp(entry->replace_name, name))
    0          
436 0           return entry;
437             }
438              
439 0 0         if (fallback < 0)
440 0           return NULL; /* no fallback */
441 3           return git_vector_get(&mm->entries, fallback);
442             }
443              
444 3           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 3           const git_mailmap_entry *entry = NULL;
450 3 50         assert(name && email);
    50          
451              
452 3           *real_name = name;
453 3           *real_email = email;
454              
455 3 50         if ((entry = git_mailmap_entry_lookup(mailmap, name, email))) {
456 0 0         if (entry->real_name)
457 0           *real_name = entry->real_name;
458 0 0         if (entry->real_email)
459 0           *real_email = entry->real_email;
460             }
461 3           return 0;
462             }
463              
464 3           int git_mailmap_resolve_signature(
465             git_signature **out, const git_mailmap *mailmap, const git_signature *sig)
466             {
467 3           const char *name = NULL;
468 3           const char *email = NULL;
469             int error;
470              
471 3 50         if (!sig)
472 0           return 0;
473              
474 3           error = git_mailmap_resolve(&name, &email, mailmap, sig->name, sig->email);
475 3 50         if (error < 0)
476 0           return error;
477              
478 3           error = git_signature_new(out, name, email, sig->when.time, sig->when.offset);
479 3 50         if (error < 0)
480 0           return error;
481              
482             /* Copy over the sign, as git_signature_new doesn't let you pass it. */
483 3           (*out)->when.sign = sig->when.sign;
484 3           return 0;
485             }