2 * Copyright (C) the libgit2 contributors. All rights reserved.
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.
12 #include "repository.h"
13 #include "signature.h"
14 #include "git2/config.h"
15 #include "git2/revparse.h"
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
24 static void mailmap_entry_free(git_mailmap_entry
*entry
)
29 git__free(entry
->real_name
);
30 git__free(entry
->real_email
);
31 git__free(entry
->replace_name
);
32 git__free(entry
->replace_email
);
37 * First we sort by replace_email, then replace_name (if present).
38 * Entries with names are greater than entries without.
40 static int mailmap_entry_cmp(const void *a_raw
, const void *b_raw
)
42 const git_mailmap_entry
*a
= (const git_mailmap_entry
*)a_raw
;
43 const git_mailmap_entry
*b
= (const git_mailmap_entry
*)b_raw
;
46 GIT_ASSERT_ARG(a
&& a
->replace_email
);
47 GIT_ASSERT_ARG(b
&& b
->replace_email
);
49 cmp
= git__strcmp(a
->replace_email
, b
->replace_email
);
53 /* NULL replace_names are less than not-NULL ones */
54 if (a
->replace_name
== NULL
|| b
->replace_name
== NULL
)
55 return (int)(a
->replace_name
!= NULL
) - (int)(b
->replace_name
!= NULL
);
57 return git__strcmp(a
->replace_name
, b
->replace_name
);
60 /* Replace the old entry with the new on duplicate. */
61 static int mailmap_entry_replace(void **old_raw
, void *new_raw
)
63 mailmap_entry_free((git_mailmap_entry
*)*old_raw
);
68 /* Check if we're at the end of line, w/ comments */
69 static bool is_eol(git_parse_ctx
*ctx
)
72 return git_parse_peek(&c
, ctx
, GIT_PARSE_PEEK_SKIP_WHITESPACE
) < 0 || c
== '#';
75 static int advance_until(
76 const char **start
, size_t *len
, git_parse_ctx
*ctx
, char needle
)
79 while (ctx
->line_len
> 0 && *ctx
->line
!= '#' && *ctx
->line
!= needle
)
80 git_parse_advance_chars(ctx
, 1);
82 if (ctx
->line_len
== 0 || *ctx
->line
== '#')
83 return -1; /* end of line */
85 *len
= ctx
->line
- *start
;
86 git_parse_advance_chars(ctx
, 1); /* advance past needle */
91 * Parse a single entry from a mailmap file.
93 * The output git_bufs will be non-owning, and should be copied before being
96 static int parse_mailmap_entry(
97 git_buf
*real_name
, git_buf
*real_email
,
98 git_buf
*replace_name
, git_buf
*replace_email
,
104 git_buf_clear(real_name
);
105 git_buf_clear(real_email
);
106 git_buf_clear(replace_name
);
107 git_buf_clear(replace_email
);
109 git_parse_advance_ws(ctx
);
111 return -1; /* blank line */
113 /* Parse the real name */
114 if (advance_until(&start
, &len
, ctx
, '<') < 0)
117 git_buf_attach_notowned(real_name
, start
, len
);
118 git_buf_rtrim(real_name
);
121 * If this is the last email in the line, this is the email to replace,
122 * otherwise, it's the real email.
124 if (advance_until(&start
, &len
, ctx
, '>') < 0)
127 /* If we aren't at the end of the line, parse a second name and email */
129 git_buf_attach_notowned(real_email
, start
, len
);
131 git_parse_advance_ws(ctx
);
132 if (advance_until(&start
, &len
, ctx
, '<') < 0)
134 git_buf_attach_notowned(replace_name
, start
, len
);
135 git_buf_rtrim(replace_name
);
137 if (advance_until(&start
, &len
, ctx
, '>') < 0)
141 git_buf_attach_notowned(replace_email
, start
, len
);
149 int git_mailmap_new(git_mailmap
**out
)
152 git_mailmap
*mm
= git__calloc(1, sizeof(git_mailmap
));
153 GIT_ERROR_CHECK_ALLOC(mm
);
155 error
= git_vector_init(&mm
->entries
, 0, mailmap_entry_cmp
);
164 void git_mailmap_free(git_mailmap
*mm
)
167 git_mailmap_entry
*entry
;
171 git_vector_foreach(&mm
->entries
, idx
, entry
)
172 mailmap_entry_free(entry
);
174 git_vector_free(&mm
->entries
);
178 static int mailmap_add_entry_unterminated(
180 const char *real_name
, size_t real_name_size
,
181 const char *real_email
, size_t real_email_size
,
182 const char *replace_name
, size_t replace_name_size
,
183 const char *replace_email
, size_t replace_email_size
)
186 git_mailmap_entry
*entry
= git__calloc(1, sizeof(git_mailmap_entry
));
187 GIT_ERROR_CHECK_ALLOC(entry
);
190 GIT_ASSERT_ARG(replace_email
&& *replace_email
);
192 if (real_name_size
> 0) {
193 entry
->real_name
= git__substrdup(real_name
, real_name_size
);
194 GIT_ERROR_CHECK_ALLOC(entry
->real_name
);
196 if (real_email_size
> 0) {
197 entry
->real_email
= git__substrdup(real_email
, real_email_size
);
198 GIT_ERROR_CHECK_ALLOC(entry
->real_email
);
200 if (replace_name_size
> 0) {
201 entry
->replace_name
= git__substrdup(replace_name
, replace_name_size
);
202 GIT_ERROR_CHECK_ALLOC(entry
->replace_name
);
204 entry
->replace_email
= git__substrdup(replace_email
, replace_email_size
);
205 GIT_ERROR_CHECK_ALLOC(entry
->replace_email
);
207 error
= git_vector_insert_sorted(&mm
->entries
, entry
, mailmap_entry_replace
);
208 if (error
== GIT_EEXISTS
)
211 mailmap_entry_free(entry
);
216 int git_mailmap_add_entry(
217 git_mailmap
*mm
, const char *real_name
, const char *real_email
,
218 const char *replace_name
, const char *replace_email
)
220 return mailmap_add_entry_unterminated(
222 real_name
, real_name
? strlen(real_name
) : 0,
223 real_email
, real_email
? strlen(real_email
) : 0,
224 replace_name
, replace_name
? strlen(replace_name
) : 0,
225 replace_email
, strlen(replace_email
));
228 static int mailmap_add_buffer(git_mailmap
*mm
, const char *buf
, size_t len
)
233 /* Scratch buffers containing the real parsed names & emails */
234 git_buf real_name
= GIT_BUF_INIT
;
235 git_buf real_email
= GIT_BUF_INIT
;
236 git_buf replace_name
= GIT_BUF_INIT
;
237 git_buf replace_email
= GIT_BUF_INIT
;
239 /* Buffers may not contain '\0's. */
240 if (memchr(buf
, '\0', len
) != NULL
)
243 git_parse_ctx_init(&ctx
, buf
, len
);
246 while (ctx
.remain_len
> 0) {
247 error
= parse_mailmap_entry(
248 &real_name
, &real_email
, &replace_name
, &replace_email
, &ctx
);
250 error
= 0; /* Skip lines which don't contain a valid entry */
251 git_parse_advance_line(&ctx
);
252 continue; /* TODO: warn */
255 /* NOTE: Can't use add_entry(...) as our buffers aren't terminated */
256 error
= mailmap_add_entry_unterminated(
257 mm
, real_name
.ptr
, real_name
.size
, real_email
.ptr
, real_email
.size
,
258 replace_name
.ptr
, replace_name
.size
, replace_email
.ptr
, replace_email
.size
);
266 git_buf_dispose(&real_name
);
267 git_buf_dispose(&real_email
);
268 git_buf_dispose(&replace_name
);
269 git_buf_dispose(&replace_email
);
273 int git_mailmap_from_buffer(git_mailmap
**out
, const char *data
, size_t len
)
275 int error
= git_mailmap_new(out
);
279 error
= mailmap_add_buffer(*out
, data
, len
);
281 git_mailmap_free(*out
);
287 static int mailmap_add_blob(
288 git_mailmap
*mm
, git_repository
*repo
, const char *rev
)
290 git_object
*object
= NULL
;
291 git_blob
*blob
= NULL
;
292 git_buf content
= GIT_BUF_INIT
;
296 GIT_ASSERT_ARG(repo
);
298 error
= git_revparse_single(&object
, repo
, rev
);
302 error
= git_object_peel((git_object
**)&blob
, object
, GIT_OBJECT_BLOB
);
306 error
= git_blob__getbuf(&content
, blob
);
310 error
= mailmap_add_buffer(mm
, content
.ptr
, content
.size
);
315 git_buf_dispose(&content
);
317 git_object_free(object
);
321 static int mailmap_add_file_ondisk(
322 git_mailmap
*mm
, const char *path
, git_repository
*repo
)
324 const char *base
= repo
? git_repository_workdir(repo
) : NULL
;
325 git_buf fullpath
= GIT_BUF_INIT
;
326 git_buf content
= GIT_BUF_INIT
;
329 error
= git_path_join_unrooted(&fullpath
, path
, base
, NULL
);
333 error
= git_path_validate_workdir_buf(repo
, &fullpath
);
337 error
= git_futils_readbuffer(&content
, fullpath
.ptr
);
341 error
= mailmap_add_buffer(mm
, content
.ptr
, content
.size
);
346 git_buf_dispose(&fullpath
);
347 git_buf_dispose(&content
);
351 /* NOTE: Only expose with an error return, currently never errors */
352 static void mailmap_add_from_repository(git_mailmap
*mm
, git_repository
*repo
)
354 git_config
*config
= NULL
;
355 git_buf rev_buf
= GIT_BUF_INIT
;
356 git_buf path_buf
= GIT_BUF_INIT
;
357 const char *rev
= NULL
;
358 const char *path
= NULL
;
360 /* If we're in a bare repo, default blob to 'HEAD:.mailmap' */
362 rev
= MM_BLOB_DEFAULT
;
364 /* Try to load 'mailmap.file' and 'mailmap.blob' cfgs from the repo */
365 if (git_repository_config(&config
, repo
) == 0) {
366 if (git_config_get_string_buf(&rev_buf
, config
, MM_BLOB_CONFIG
) == 0)
368 if (git_config_get_path(&path_buf
, config
, MM_FILE_CONFIG
) == 0)
373 * Load mailmap files in order, overriding previous entries with new ones.
374 * 1. The '.mailmap' file in the repository's workdir root,
375 * 2. The blob described by the 'mailmap.blob' config (default HEAD:.mailmap),
376 * 3. The file described by the 'mailmap.file' config.
378 * We ignore errors from these loads, as these files may not exist, or may
379 * contain invalid information, and we don't want to report that error.
384 mailmap_add_file_ondisk(mm
, MM_FILE
, repo
);
386 mailmap_add_blob(mm
, repo
, rev
);
388 mailmap_add_file_ondisk(mm
, path
, repo
);
390 git_buf_dispose(&rev_buf
);
391 git_buf_dispose(&path_buf
);
392 git_config_free(config
);
395 int git_mailmap_from_repository(git_mailmap
**out
, git_repository
*repo
)
400 GIT_ASSERT_ARG(repo
);
402 if ((error
= git_mailmap_new(out
)) < 0)
405 mailmap_add_from_repository(*out
, repo
);
409 const git_mailmap_entry
*git_mailmap_entry_lookup(
410 const git_mailmap
*mm
, const char *name
, const char *email
)
413 ssize_t fallback
= -1;
415 git_mailmap_entry
*entry
;
417 /* The lookup needle we want to use only sets the replace_email. */
418 git_mailmap_entry needle
= { NULL
};
419 needle
.replace_email
= (char *)email
;
421 GIT_ASSERT_ARG_WITH_RETVAL(email
, NULL
);
427 * We want to find the place to start looking. so we do a binary search for
428 * the "fallback" nameless entry. If we find it, we advance past it and record
431 error
= git_vector_bsearch(&idx
, (git_vector
*)&mm
->entries
, &needle
);
434 else if (error
!= GIT_ENOTFOUND
)
437 /* do a linear search for an exact match */
438 for (; idx
< git_vector_length(&mm
->entries
); ++idx
) {
439 entry
= git_vector_get(&mm
->entries
, idx
);
441 if (git__strcmp(entry
->replace_email
, email
))
442 break; /* it's a different email, so we're done looking */
444 /* should be specific */
445 GIT_ASSERT_WITH_RETVAL(entry
->replace_name
, NULL
);
446 if (!name
|| !git__strcmp(entry
->replace_name
, name
))
451 return NULL
; /* no fallback */
452 return git_vector_get(&mm
->entries
, fallback
);
455 int git_mailmap_resolve(
456 const char **real_name
, const char **real_email
,
457 const git_mailmap
*mailmap
,
458 const char *name
, const char *email
)
460 const git_mailmap_entry
*entry
= NULL
;
468 if ((entry
= git_mailmap_entry_lookup(mailmap
, name
, email
))) {
469 if (entry
->real_name
)
470 *real_name
= entry
->real_name
;
471 if (entry
->real_email
)
472 *real_email
= entry
->real_email
;
477 int git_mailmap_resolve_signature(
478 git_signature
**out
, const git_mailmap
*mailmap
, const git_signature
*sig
)
480 const char *name
= NULL
;
481 const char *email
= NULL
;
487 error
= git_mailmap_resolve(&name
, &email
, mailmap
, sig
->name
, sig
->email
);
491 error
= git_signature_new(out
, name
, email
, sig
->when
.time
, sig
->when
.offset
);
495 /* Copy over the sign, as git_signature_new doesn't let you pass it. */
496 (*out
)->when
.sign
= sig
->when
.sign
;