Commit 49620359 by Nika Layzell

mailmap: Clean up mailmap parser, and finish API

parent 7a169390
...@@ -4,16 +4,16 @@ ...@@ -4,16 +4,16 @@
* This file is part of libgit2, distributed under the GNU GPL v2 with * This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file. * a Linking Exception. For full terms see the included COPYING file.
*/ */
#ifndef INCLUDE_mailmap_h__ #ifndef INCLUDE_git_mailmap_h__
#define INCLUDE_mailmap_h__ #define INCLUDE_git_mailmap_h__
#include "common.h" #include "common.h"
#include "repository.h" #include "tree.h"
/** /**
* @file git2/mailmap.h * @file git2/mailmap.h
* @brief Mailmap access subroutines. * @brief Mailmap parsing routines
* @defgroup git_rebase Git merge routines * @defgroup git_mailmap Git mailmap routines
* @ingroup Git * @ingroup Git
* @{ * @{
*/ */
...@@ -21,19 +21,106 @@ GIT_BEGIN_DECL ...@@ -21,19 +21,106 @@ GIT_BEGIN_DECL
typedef struct git_mailmap git_mailmap; typedef struct git_mailmap git_mailmap;
struct git_mailmap_entry { /**
const char* name; * A single entry parsed from a mailmap.
const char* email; */
}; typedef struct git_mailmap_entry {
const char *real_name; /**< the real name (may be NULL) */
const char *real_email; /**< the real email (may be NULL) */
const char *replace_name; /**< the name to replace (may be NULL) */
const char *replace_email; /**< the email to replace */
} git_mailmap_entry;
/**
* Create a mailmap object by parsing a mailmap file.
*
* The mailmap must be freed with 'git_mailmap_free'.
*
* @param out Pointer to store the mailmap
* @param data raw data buffer to parse
* @param size size of the raw data buffer
* @return 0 on success
*/
GIT_EXTERN(int) git_mailmap_parse(
git_mailmap **out,
const char *data,
size_t size);
/**
* Create a mailmap object by parsing the ".mailmap" file in the tree root.
*
* The mailmap must be freed with 'git_mailmap_free'.
*
* @param out pointer to store the mailmap
* @param treeish root object that can be peeled to a tree
* @return 0 on success; GIT_ENOTFOUND if .mailmap does not exist.
*/
GIT_EXTERN(int) git_mailmap_from_tree(
git_mailmap **out,
const git_object *treeish);
GIT_EXTERN(int) git_mailmap_create(git_mailmap**, git_repository*); /**
GIT_EXTERN(void) git_mailmap_free(git_mailmap*); * Create a mailmap object by parsing the ".mailmap" file in the repository's
GIT_EXTERN(struct git_mailmap_entry) git_mailmap_lookup( * HEAD's tree root.
git_mailmap* map, *
const char* name, * The mailmap must be freed with 'git_mailmap_free'.
const char* email); *
* @param out pointer to store the mailmap
* @param repo repository to find the .mailmap in
* @return 0 on success; GIT_ENOTFOUND if .mailmap does not exist.
*/
GIT_EXTERN(int) git_mailmap_from_repo(
git_mailmap **out,
git_repository *repo);
/**
* Free a mailmap created by 'git_mailmap_parse', 'git_mailmap_from_tree' or
* 'git_mailmap_from_repo'.
*/
GIT_EXTERN(void) git_mailmap_free(git_mailmap *mailmap);
/**
* Resolve a name and email to the corresponding real name and email.
*
* @param name_out either 'name', or the real name to use.
* You should NOT free this value.
* @param email_out either 'email' or the real email to use,
* You should NOT free this value.
* @param mailmap the mailmap to perform the lookup in.
* @param name the name to resolve.
* @param email the email to resolve.
*/
GIT_EXTERN(void) git_mailmap_resolve(
const char **name_out,
const char **email_out,
git_mailmap *mailmap,
const char *name,
const char *email);
/**
* Get the number of mailmap entries.
*/
GIT_EXTERN(size_t) git_mailmap_entry_count(git_mailmap *mailmap);
/**
* Lookup a mailmap entry by index.
*
* Do not free the mailmap entry, it is owned by the mailmap.
*/
GIT_EXTERN(git_mailmap_entry *) git_mailmap_entry_byindex(
git_mailmap *mailmap,
size_t idx);
/**
* Lookup a mailmap entry by name/email pair.
*
* Do not free the mailmap entry, it is owned by the mailmap.
*/
GIT_EXTERN(git_mailmap_entry *) git_mailmap_entry_lookup(
git_mailmap *mailmap,
const char *name,
const char *email);
/** @} */ /** @} */
GIT_END_DECL GIT_END_DECL
#endif #endif
...@@ -14,195 +14,312 @@ ...@@ -14,195 +14,312 @@
#include "git2/revparse.h" #include "git2/revparse.h"
#include "git2/sys/commit.h" #include "git2/sys/commit.h"
struct mailmap_entry { /**
char* to_name; * Helper type and methods for the mailmap parser
char* to_email; */
char* from_name; typedef struct char_range {
char* from_email; const char *p;
}; size_t len;
} char_range;
static const char_range NULL_RANGE = {0};
/* Split a range at the first instance of 'c'. Returns whether 'c' was found */
static bool range_split(
char_range range,
char c,
char_range *before,
char_range *after)
{
const char *off;
*before = *after = NULL_RANGE;
before->p = range.p;
off = memchr(range.p, c, range.len);
if (!off) {
before->len = range.len;
return false;
}
before->len = off - range.p;
after->p = off + 1;
after->len = (range.p + range.len) - after->p;
return true;
}
/* Trim whitespace from the beginning and end of the range */
static void range_trim(char_range *range) {
while (range->len > 0 && git__isspace(range->p[0])) {
++range->p;
--range->len;
}
while (range->len > 0 && git__isspace(range->p[range->len - 1]))
--range->len;
}
/**
* If `buf` is not NULL, copies range into it with a '\0', and bumps buf.
* If `size` is not NULL, adds the number of bytes to be written to it.
* returns a pointer to the copied string, or NULL.
*/
static const char *range_copyz(char **buf, size_t *size, char_range src)
{
char *s = NULL;
if (src.p == NULL)
return NULL;
if (size)
*size += src.len + 1;
if (buf) {
s = *buf;
memcpy(s, src.p, src.len);
s[src.len] = '\0';
*buf += src.len + 1;
}
return s;
}
struct git_mailmap { struct git_mailmap {
git_vector lines; git_vector entries;
}; };
// Returns -1 on failure, length of the string scanned successfully on success, /**
// guaranteed to be less that `length`. * Parse a single entry out of a mailmap file.
ssize_t parse_name_and_email( * Advances the `file` range past the parsed entry.
const char *line, */
size_t length, static int git_mailmap_parse_single(
const char** name, char_range *file,
size_t* name_len, bool *found,
const char** email, char_range *real_name,
size_t* email_len, char_range *real_email,
bool allow_empty_email) char_range *replace_name,
char_range *replace_email)
{ {
const char* email_start; char_range line, comment, name_a, email_a, name_b, email_b;
const char* email_end; bool two_emails = false;
const char* name_start;
const char* name_end; *found = false;
*real_name = NULL_RANGE;
*real_email = NULL_RANGE;
*replace_name = NULL_RANGE;
*replace_email = NULL_RANGE;
while (file->len) {
/* Get the line, and remove any comments */
range_split(*file, '\n', &line, file);
range_split(line, '#', &line, &comment);
/* Skip blank lines */
range_trim(&line);
if (line.len == 0)
continue;
email_start = memchr(line, '<', length); /* Get the first name and email */
if (!email_start) if (!range_split(line, '<', &name_a, &line))
return -1; return -1; /* garbage in line */
email_end = memchr(email_start, '>', length - (email_start - line)); if (!range_split(line, '>', &email_a, &line))
if (!email_end) return -1; /* unfinished <> pair */
return -1;
assert(email_end > email_start);
*email_len = email_end - email_start - 1; /* Get an optional second name and/or email */
*email = email_start + 1; two_emails = range_split(line, '<', &name_b, &line);
if (*email == email_end && !allow_empty_email) if (two_emails && !range_split(line, '>', &email_b, &line))
return -1; return -1; /* unfinished <> pair */
// Now look for the name. if (line.len > 0)
name_start = line; return -1; /* junk at end of line */
while (name_start < email_start && isspace(*name_start))
++name_start;
*name = name_start; /* Trim whitespace from around names */
range_trim(&name_a);
range_trim(&name_b);
name_end = email_start; *found = true;
while (name_end > name_start && isspace(*(name_end - 1))) if (name_a.len > 0)
name_end--; *real_name = name_a;
assert(name_end >= name_start); if (two_emails) {
*name_len = name_end - name_start; *real_email = email_a;
*replace_email = email_b;
if (name_b.len > 0)
*replace_name = name_b;
} else {
*replace_email = email_a;
}
break;
}
return email_end - line; return 0;
} }
static void git_mailmap_parse_line( int git_mailmap_parse(
git_mailmap* mailmap, git_mailmap **mailmap,
const char* contents, const char *data,
size_t size) size_t size)
{ {
struct mailmap_entry* entry; char_range file = { data, size };
git_mailmap_entry* entry = NULL;
const char* to_name; int error = 0;
size_t to_name_length;
*mailmap = git__calloc(1, sizeof(git_mailmap));
const char* to_email; if (!*mailmap)
size_t to_email_length; return -1;
const char* from_name; /* XXX: Is it worth it to precompute the size? */
size_t from_name_length; error = git_vector_init(&(*mailmap)->entries, 0, NULL);
if (error < 0)
const char* from_email; goto cleanup;
size_t from_email_length;
while (file.len > 0) {
ssize_t ret; bool found = false;
char_range real_name, real_email, replace_name, replace_email;
if (!size) size_t size = 0;
return; char *buf = NULL;
if (contents[0] == '#')
return; error = git_mailmap_parse_single(
&file, &found,
ret = parse_name_and_email( &real_name, &real_email,
contents, &replace_name, &replace_email);
size, if (error < 0)
&to_name, goto cleanup;
&to_name_length, if (!found)
&to_email, break;
&to_email_length,
false); /* Compute how much space we'll need to store our entry */
if (ret < 0) size = sizeof(git_mailmap_entry);
return; range_copyz(NULL, &size, real_name);
range_copyz(NULL, &size, real_email);
ret = parse_name_and_email( range_copyz(NULL, &size, replace_name);
contents + ret + 1, range_copyz(NULL, &size, replace_email);
size - ret - 1,
&from_name, entry = git__malloc(size);
&from_name_length, if (!entry) {
&from_email, error = -1;
&from_email_length, goto cleanup;
true); }
if (ret < 0)
return; buf = (char*)(entry + 1);
entry->real_name = range_copyz(&buf, NULL, real_name);
entry = git__malloc(sizeof(struct mailmap_entry)); entry->real_email = range_copyz(&buf, NULL, real_email);
entry->replace_name = range_copyz(&buf, NULL, replace_name);
entry->to_name = git__strndup(to_name, to_name_length); entry->replace_email = range_copyz(&buf, NULL, replace_email);
entry->to_email = git__strndup(to_email, to_email_length); assert(buf == ((char*)entry) + size);
entry->from_name = git__strndup(from_name, from_name_length);
entry->from_email = git__strndup(from_email, from_email_length); error = git_vector_insert(&(*mailmap)->entries, entry);
if (error < 0)
printf("%s <%s> \"%s\" <%s>\n", goto cleanup;
entry->to_name, entry = NULL;
entry->to_email, }
entry->from_name,
entry->from_email); cleanup:
if (entry)
git_vector_insert(&mailmap->lines, entry); git__free(entry);
if (error < 0 && *mailmap)
git_mailmap_free(*mailmap);
return error;
} }
static void git_mailmap_parse( void git_mailmap_free(git_mailmap *mailmap)
git_mailmap* mailmap, {
const char* contents, git_vector_free_deep(&mailmap->entries);
size_t size) git__free(mailmap);
}
void git_mailmap_resolve(
const char **name_out,
const char **email_out,
git_mailmap *mailmap,
const char *name,
const char *email)
{
git_mailmap_entry *entry = NULL;
*name_out = name;
*email_out = email;
entry = git_mailmap_entry_lookup(mailmap, name, email);
if (entry) {
if (entry->real_name)
*name_out = entry->real_name;
if (entry->real_email)
*email_out = entry->real_email;
}
}
git_mailmap_entry* git_mailmap_entry_lookup(
git_mailmap *mailmap,
const char *name,
const char *email)
{ {
size_t start = 0;
size_t i; size_t i;
for (i = 0; i < size; ++i) { git_mailmap_entry *entry;
if (contents[i] != '\n') assert(mailmap && name && email);
continue;
git_mailmap_parse_line(mailmap, contents + start, i - start); git_vector_foreach(&mailmap->entries, i, entry) {
start = i + 1; if (!git__strcmp(email, entry->replace_email) &&
(!entry->replace_name || !git__strcmp(name, entry->replace_name))) {
return entry;
}
} }
return NULL;
} }
int git_mailmap_create(git_mailmap** mailmap, git_repository* repo) git_mailmap_entry* git_mailmap_entry_byindex(git_mailmap *mailmap, size_t idx)
{ {
git_commit* head = NULL; return git_vector_get(&mailmap->entries, idx);
git_blob* mailmap_blob = NULL; }
git_off_t size = 0;
const char* contents = NULL; size_t git_mailmap_entry_count(git_mailmap *mailmap)
int ret; {
return git_vector_length(&mailmap->entries);
}
*mailmap = git__malloc(sizeof(struct git_mailmap)); int git_mailmap_from_tree(
git_vector_init(&(*mailmap)->lines, 0, NULL); git_mailmap **mailmap,
const git_object *treeish)
{
git_blob *blob = NULL;
const char *content = NULL;
git_off_t size = 0;
int error;
ret = git_revparse_single((git_object **)&head, repo, "HEAD"); *mailmap = NULL;
if (ret)
goto error;
ret = git_object_lookup_bypath( error = git_object_lookup_bypath(
(git_object**) &mailmap_blob, (git_object **) &blob,
(const git_object*) head, treeish,
".mailmap", ".mailmap",
GIT_OBJ_BLOB); GIT_OBJ_BLOB);
if (ret) if (error < 0)
goto error; goto cleanup;
contents = git_blob_rawcontent(mailmap_blob); content = git_blob_rawcontent(blob);
size = git_blob_rawsize(mailmap_blob); size = git_blob_rawsize(blob);
git_mailmap_parse(*mailmap, contents, size); error = git_mailmap_parse(mailmap, content, size);
return 0; cleanup:
if (blob != NULL)
error: git_blob_free(blob);
assert(ret); return error;
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) int git_mailmap_from_repo(git_mailmap **mailmap, git_repository *repo)
{ {
size_t i; git_object *head = NULL;
struct mailmap_entry* line; int error;
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); *mailmap = NULL;
git__free(mailmap);
error = git_revparse_single(&head, repo, "HEAD");
if (error < 0)
goto cleanup;
error = git_mailmap_from_tree(mailmap, head);
cleanup:
if (head)
git_object_free(head);
return error;
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment