diff --git a/include/git2.h b/include/git2.h index 5f6104ef7..e182ce924 100644 --- a/include/git2.h +++ b/include/git2.h @@ -29,6 +29,7 @@ #include "git2/ignore.h" #include "git2/index.h" #include "git2/indexer.h" +#include "git2/mailmap.h" #include "git2/merge.h" #include "git2/message.h" #include "git2/net.h" diff --git a/include/git2/mailmap.h b/include/git2/mailmap.h new file mode 100644 index 000000000..bb126d14e --- /dev/null +++ b/include/git2/mailmap.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_mailmap_h__ +#define INCLUDE_mailmap_h__ + +#include "common.h" +#include "repository.h" + +/** + * @file git2/mailmap.h + * @brief Mailmap access subroutines. + * @defgroup git_rebase Git merge routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +typedef struct git_mailmap git_mailmap; + +struct git_mailmap_entry { + const char* name; + const char* email; +}; + +GIT_EXTERN(int) git_mailmap_create(git_mailmap**, git_repository*); +GIT_EXTERN(void) git_mailmap_free(git_mailmap*); +GIT_EXTERN(struct git_mailmap_entry) git_mailmap_lookup( + git_mailmap* map, + const char* name, + const char* email); + +/** @} */ +GIT_END_DECL + +#endif diff --git a/src/mailmap.c b/src/mailmap.c new file mode 100644 index 000000000..26b5f8b26 --- /dev/null +++ b/src/mailmap.c @@ -0,0 +1,208 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "git2/mailmap.h" + +#include "blob.h" +#include "commit.h" +#include "git2/common.h" +#include "git2/repository.h" +#include "git2/revparse.h" +#include "git2/sys/commit.h" + +struct mailmap_entry { + char* to_name; + char* to_email; + char* from_name; + char* from_email; +}; + +struct git_mailmap { + git_vector lines; +}; + +// Returns -1 on failure, length of the string scanned successfully on success, +// guaranteed to be less that `length`. +ssize_t parse_name_and_email( + const char *line, + size_t length, + const char** name, + size_t* name_len, + const char** email, + size_t* email_len, + bool allow_empty_email) +{ + const char* email_start; + const char* email_end; + const char* name_start; + const char* name_end; + + email_start = memchr(line, '<', length); + if (!email_start) + return -1; + email_end = memchr(email_start, '>', length - (email_start - line)); + if (!email_end) + return -1; + assert(email_end > email_start); + + *email_len = email_end - email_start - 1; + *email = email_start + 1; + if (*email == email_end && !allow_empty_email) + return -1; + + // Now look for the name. + name_start = line; + while (name_start < email_start && isspace(*name_start)) + ++name_start; + + *name = name_start; + + name_end = email_start; + while (name_end > name_start && isspace(*(name_end - 1))) + name_end--; + + assert(name_end >= name_start); + *name_len = name_end - name_start; + + return email_end - line; +} + +static void git_mailmap_parse_line( + git_mailmap* mailmap, + const char* contents, + size_t size) +{ + struct mailmap_entry* entry; + + const char* to_name; + size_t to_name_length; + + const char* to_email; + size_t to_email_length; + + const char* from_name; + size_t from_name_length; + + const char* from_email; + size_t from_email_length; + + ssize_t ret; + + if (!size) + return; + if (contents[0] == '#') + return; + + ret = parse_name_and_email( + contents, + size, + &to_name, + &to_name_length, + &to_email, + &to_email_length, + false); + if (ret < 0) + return; + + ret = parse_name_and_email( + contents + ret + 1, + size - ret - 1, + &from_name, + &from_name_length, + &from_email, + &from_email_length, + true); + if (ret < 0) + return; + + entry = git__malloc(sizeof(struct mailmap_entry)); + + entry->to_name = git__strndup(to_name, to_name_length); + entry->to_email = git__strndup(to_email, to_email_length); + entry->from_name = git__strndup(from_name, from_name_length); + entry->from_email = git__strndup(from_email, from_email_length); + + printf("%s <%s> \"%s\" <%s>\n", + entry->to_name, + entry->to_email, + entry->from_name, + entry->from_email); + + git_vector_insert(&mailmap->lines, entry); +} + +static void git_mailmap_parse( + git_mailmap* mailmap, + const char* contents, + size_t size) +{ + size_t start = 0; + size_t i; + for (i = 0; i < size; ++i) { + if (contents[i] != '\n') + continue; + git_mailmap_parse_line(mailmap, contents + start, i - start); + start = i + 1; + } +} + +int git_mailmap_create(git_mailmap** mailmap, git_repository* repo) +{ + git_commit* head = NULL; + git_blob* mailmap_blob = NULL; + git_off_t size = 0; + const char* contents = NULL; + int ret; + + *mailmap = git__malloc(sizeof(struct git_mailmap)); + git_vector_init(&(*mailmap)->lines, 0, NULL); + + ret = git_revparse_single((git_object **)&head, repo, "HEAD"); + if (ret) + goto error; + + ret = git_object_lookup_bypath( + (git_object**) &mailmap_blob, + (const git_object*) head, + ".mailmap", + GIT_OBJ_BLOB); + if (ret) + goto error; + + contents = git_blob_rawcontent(mailmap_blob); + size = git_blob_rawsize(mailmap_blob); + + git_mailmap_parse(*mailmap, contents, size); + + return 0; + +error: + assert(ret); + + if (mailmap_blob) + git_blob_free(mailmap_blob); + if (head) + git_commit_free(head); + git_mailmap_free(*mailmap); + return ret; +} + +void git_mailmap_free(struct git_mailmap* mailmap) +{ + size_t i; + struct mailmap_entry* line; + git_vector_foreach(&mailmap->lines, i, line) { + git__free((char*)line->to_name); + git__free((char*)line->to_email); + git__free((char*)line->from_name); + git__free((char*)line->from_email); + git__free(line); + } + + git_vector_clear(&mailmap->lines); + git__free(mailmap); +}