summaryrefslogtreecommitdiff
path: root/src/mailmap.c
diff options
context:
space:
mode:
authorNika Layzell <nika@thelayzells.com>2018-03-17 02:29:41 -0400
committerNika Layzell <nika@thelayzells.com>2018-06-14 22:43:26 -0700
commit49620359f22c952021645b70480f1f3dc2e74440 (patch)
tree551c634b58355820c3abd876853deca575b85bc1 /src/mailmap.c
parent7a169390b89ad2182f9d5a31851270f0bc37423a (diff)
downloadlibgit2-49620359f22c952021645b70480f1f3dc2e74440.tar.gz
mailmap: Clean up mailmap parser, and finish API
Diffstat (limited to 'src/mailmap.c')
-rw-r--r--src/mailmap.c431
1 files changed, 274 insertions, 157 deletions
diff --git a/src/mailmap.c b/src/mailmap.c
index 26b5f8b26..9e4d57468 100644
--- a/src/mailmap.c
+++ b/src/mailmap.c
@@ -14,195 +14,312 @@
#include "git2/revparse.h"
#include "git2/sys/commit.h"
-struct mailmap_entry {
- char* to_name;
- char* to_email;
- char* from_name;
- char* from_email;
-};
+/**
+ * Helper type and methods for the mailmap parser
+ */
+typedef struct char_range {
+ 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 {
- git_vector lines;
+ git_vector entries;
};
-// 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)
+/**
+ * Parse a single entry out of a mailmap file.
+ * Advances the `file` range past the parsed entry.
+ */
+static int git_mailmap_parse_single(
+ char_range *file,
+ bool *found,
+ char_range *real_name,
+ char_range *real_email,
+ char_range *replace_name,
+ char_range *replace_email)
{
- const char* email_start;
- const char* email_end;
- const char* name_start;
- const char* name_end;
+ char_range line, comment, name_a, email_a, name_b, email_b;
+ bool two_emails = false;
+
+ *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);
- if (!email_start)
- return -1;
- email_end = memchr(email_start, '>', length - (email_start - line));
- if (!email_end)
- return -1;
- assert(email_end > email_start);
+ /* Get the first name and email */
+ if (!range_split(line, '<', &name_a, &line))
+ return -1; /* garbage in line */
+ if (!range_split(line, '>', &email_a, &line))
+ return -1; /* unfinished <> pair */
+
+ /* Get an optional second name and/or email */
+ two_emails = range_split(line, '<', &name_b, &line);
+ if (two_emails && !range_split(line, '>', &email_b, &line))
+ return -1; /* unfinished <> pair */
+
+ if (line.len > 0)
+ return -1; /* junk at end of line */
+
+ /* Trim whitespace from around names */
+ range_trim(&name_a);
+ range_trim(&name_b);
+
+ *found = true;
+ if (name_a.len > 0)
+ *real_name = name_a;
+
+ if (two_emails) {
+ *real_email = email_a;
+ *replace_email = email_b;
+
+ if (name_b.len > 0)
+ *replace_name = name_b;
+ } else {
+ *replace_email = email_a;
+ }
+ break;
+ }
- *email_len = email_end - email_start - 1;
- *email = email_start + 1;
- if (*email == email_end && !allow_empty_email)
- return -1;
+ return 0;
+}
- // Now look for the name.
- name_start = line;
- while (name_start < email_start && isspace(*name_start))
- ++name_start;
+int git_mailmap_parse(
+ git_mailmap **mailmap,
+ const char *data,
+ size_t size)
+{
+ char_range file = { data, size };
+ git_mailmap_entry* entry = NULL;
+ int error = 0;
- *name = name_start;
+ *mailmap = git__calloc(1, sizeof(git_mailmap));
+ if (!*mailmap)
+ return -1;
- name_end = email_start;
- while (name_end > name_start && isspace(*(name_end - 1)))
- name_end--;
+ /* XXX: Is it worth it to precompute the size? */
+ error = git_vector_init(&(*mailmap)->entries, 0, NULL);
+ if (error < 0)
+ goto cleanup;
+
+ while (file.len > 0) {
+ bool found = false;
+ char_range real_name, real_email, replace_name, replace_email;
+ size_t size = 0;
+ char *buf = NULL;
+
+ error = git_mailmap_parse_single(
+ &file, &found,
+ &real_name, &real_email,
+ &replace_name, &replace_email);
+ if (error < 0)
+ goto cleanup;
+ if (!found)
+ break;
+
+ /* Compute how much space we'll need to store our entry */
+ size = sizeof(git_mailmap_entry);
+ range_copyz(NULL, &size, real_name);
+ range_copyz(NULL, &size, real_email);
+ range_copyz(NULL, &size, replace_name);
+ range_copyz(NULL, &size, replace_email);
+
+ entry = git__malloc(size);
+ if (!entry) {
+ error = -1;
+ goto cleanup;
+ }
+
+ buf = (char*)(entry + 1);
+ entry->real_name = range_copyz(&buf, NULL, real_name);
+ entry->real_email = range_copyz(&buf, NULL, real_email);
+ entry->replace_name = range_copyz(&buf, NULL, replace_name);
+ entry->replace_email = range_copyz(&buf, NULL, replace_email);
+ assert(buf == ((char*)entry) + size);
+
+ error = git_vector_insert(&(*mailmap)->entries, entry);
+ if (error < 0)
+ goto cleanup;
+ entry = NULL;
+ }
- assert(name_end >= name_start);
- *name_len = name_end - name_start;
+cleanup:
+ if (entry)
+ git__free(entry);
+ if (error < 0 && *mailmap)
+ git_mailmap_free(*mailmap);
+ return error;
+}
- return email_end - line;
+void git_mailmap_free(git_mailmap *mailmap)
+{
+ git_vector_free_deep(&mailmap->entries);
+ git__free(mailmap);
}
-static void git_mailmap_parse_line(
- git_mailmap* mailmap,
- const char* contents,
- size_t size)
+void git_mailmap_resolve(
+ const char **name_out,
+ const char **email_out,
+ git_mailmap *mailmap,
+ const char *name,
+ const char *email)
{
- 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);
+ 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;
+ }
}
-static void git_mailmap_parse(
- git_mailmap* mailmap,
- const char* contents,
- size_t size)
+git_mailmap_entry* git_mailmap_entry_lookup(
+ git_mailmap *mailmap,
+ const char *name,
+ const char *email)
{
- 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;
+ git_mailmap_entry *entry;
+ assert(mailmap && name && email);
+
+ git_vector_foreach(&mailmap->entries, i, entry) {
+ 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;
- 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);
+ return git_vector_get(&mailmap->entries, idx);
+}
- ret = git_revparse_single((git_object **)&head, repo, "HEAD");
- if (ret)
- goto error;
+size_t git_mailmap_entry_count(git_mailmap *mailmap)
+{
+ return git_vector_length(&mailmap->entries);
+}
- ret = git_object_lookup_bypath(
- (git_object**) &mailmap_blob,
- (const git_object*) head,
- ".mailmap",
- GIT_OBJ_BLOB);
- if (ret)
- goto error;
+int git_mailmap_from_tree(
+ git_mailmap **mailmap,
+ const git_object *treeish)
+{
+ git_blob *blob = NULL;
+ const char *content = NULL;
+ git_off_t size = 0;
+ int error;
- contents = git_blob_rawcontent(mailmap_blob);
- size = git_blob_rawsize(mailmap_blob);
+ *mailmap = NULL;
- git_mailmap_parse(*mailmap, contents, size);
+ error = git_object_lookup_bypath(
+ (git_object **) &blob,
+ treeish,
+ ".mailmap",
+ GIT_OBJ_BLOB);
+ if (error < 0)
+ goto cleanup;
- return 0;
+ content = git_blob_rawcontent(blob);
+ size = git_blob_rawsize(blob);
-error:
- assert(ret);
+ error = git_mailmap_parse(mailmap, content, size);
- if (mailmap_blob)
- git_blob_free(mailmap_blob);
- if (head)
- git_commit_free(head);
- git_mailmap_free(*mailmap);
- return ret;
+cleanup:
+ if (blob != NULL)
+ git_blob_free(blob);
+ return error;
}
-void git_mailmap_free(struct git_mailmap* mailmap)
+int git_mailmap_from_repo(git_mailmap **mailmap, git_repository *repo)
{
- 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_object *head = NULL;
+ int error;
- git_vector_clear(&mailmap->lines);
- git__free(mailmap);
+ *mailmap = NULL;
+
+ 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;
}