Add map_user() and clear_mailmap() to mailmap

map_user() allows to lookup and replace both email and
name of a user, based on a new style mailmap file.

The possible mailmap definitions are now:

  proper_name <commit_email>                             # Old style
  <proper_email> <commit_email>                          # New style
  proper_name <proper_email> <commit_email>              # New style
  proper_name <proper_email> commit_name <commit_email>  # New style

map_email() operates the same as before, with the
exception that it also will to try to match on a name
passed in through the name return buffer.

clear_mailmap() is needed to now clear the more complex
mailmap structure.

Signed-off-by: Marius Storm-Olsen <marius@trolltech.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Marius Storm-Olsen 2009-02-08 15:34:29 +01:00 committed by Junio C Hamano
parent cfa1ee6b34
commit 0925ce4d49
3 changed files with 233 additions and 48 deletions

View File

@ -48,24 +48,38 @@ OPTIONS
FILES FILES
----- -----
If a file `.mailmap` exists at the toplevel of the repository, or at the If the file `.mailmap` exists at the toplevel of the repository, or at
location pointed to by the log.mailmap configuration option, the location pointed to by the mailmap.file configuration option, it
it is used to map an author email address to a canonical real name. This is used to map author and committer names and email addresses to
can be used to coalesce together commits by the same person where their canonical real names and email addresses.
name was spelled differently (whether with the same email address or This mapping can be used to coalesce together commits by the same
not). person where their name and/or email address was spelled differently.
Each line in the file consists, in this order, of the canonical real name In the simple form, each line in the file consists of the canonical
of an author, whitespace, and an email address (enclosed by '<' and '>') real name of an author, whitespace, and an email address used in the
to map to the name. Use hash '#' for comments, either on their own line, commit (enclosed by '<' and '>') to map to the name. Thus, looks like
or after the email address. this
--
Proper Name <commit@email.xx>
--
A canonical name may appear in more than one line, associated with The more complex forms are
different email addresses, but it doesn't make sense for a given address --
to appear more than once (if that happens, a later line overrides the <proper@email.xx> <commit@email.xx>
earlier ones). --
which allows mailmap to replace only the email part of a commit, and
--
Proper Name <proper@email.xx> <commit@email.xx>
--
which allows mailmap to replace both the name and the email of a
commit matching the specified commit email address, and
--
Proper Name <proper@email.xx> Commit Name <commit@email.xx>
--
which allows mailmap to replace both the name and the email of a
commit matching both the specified commit name and email address.
So, for example, if your history contains commits by two authors, Jane Example 1: Your history contains commits by two authors, Jane
and Joe, whose names appear in the repository under several forms: and Joe, whose names appear in the repository under several forms:
------------ ------------
@ -76,16 +90,43 @@ Jane Doe <jane@laptop.(none)>
Jane D. <jane@desktop.(none)> Jane D. <jane@desktop.(none)>
------------ ------------
Then, supposing Joe wants his middle name initial used, and Jane prefers Now suppose that Joe wants his middle name initial used, and Jane
her family name fully spelled out, a proper `.mailmap` file would look like: prefers her family name fully spelled out. A proper `.mailmap` file
would look like:
------------ ------------
# Note how we don't need an entry for <jane@laptop.(none)>, because the Jane Doe <jane@desktop.(none)>
# real name of that author is correct already, and coalesced directly.
Jane Doe <jane@desktop.(none)>
Joe R. Developer <joe@example.com> Joe R. Developer <joe@example.com>
------------ ------------
Note how we don't need an entry for <jane@laptop.(none)>, because the
real name of that author is correct already, and coalesced directly.
Example 2: Your repository contains commits from the following
authors:
------------
nick1 <bugs@company.xx>
nick2 <bugs@company.xx>
nick2 <nick2@company.xx>
santa <me@company.xx>
claus <me@company.xx>
CTO <cto@coompany.xx>
------------
Then, you might want a `.mailmap` file looking like:
------------
<cto@company.xx> <cto@coompany.xx>
Some Dude <some@dude.xx> nick1 <bugs@company.xx>
Other Author <other@author.xx> nick2 <bugs@company.xx>
Other Author <other@author.xx> <nick2@company.xx>
Santa Claus <santa.claus@northpole.xx> <me@company.xx>
------------
Use hash '#' for comments that are either on their own line, or after
the email address.
Author Author
------ ------
Written by Jeff Garzik <jgarzik@pobox.com> Written by Jeff Garzik <jgarzik@pobox.com>

196
mailmap.c
View File

@ -2,7 +2,122 @@
#include "string-list.h" #include "string-list.h"
#include "mailmap.h" #include "mailmap.h"
#define DEBUG_MAILMAP 0
#if DEBUG_MAILMAP
#define debug_mm(...) fprintf(stderr, __VA_ARGS__)
#else
static inline void debug_mm(const char *format, ...) {}
#endif
const char *git_mailmap_file; const char *git_mailmap_file;
struct mailmap_info {
char *name;
char *email;
};
struct mailmap_entry {
/* name and email for the simple mail-only case */
char *name;
char *email;
/* name and email for the complex mail and name matching case */
struct string_list namemap;
};
static void free_mailmap_info(void *p, const char *s)
{
struct mailmap_info *mi = (struct mailmap_info *)p;
debug_mm("mailmap: -- complex: '%s' -> '%s' <%s>\n", s, mi->name, mi->email);
free(mi->name);
free(mi->email);
}
static void free_mailmap_entry(void *p, const char *s)
{
struct mailmap_entry *me = (struct mailmap_entry *)p;
debug_mm("mailmap: removing entries for <%s>, with %d sub-entries\n", s, me->namemap.nr);
debug_mm("mailmap: - simple: '%s' <%s>\n", me->name, me->email);
free(me->name);
free(me->email);
me->namemap.strdup_strings = 1;
string_list_clear_func(&me->namemap, free_mailmap_info);
}
static void add_mapping(struct string_list *map,
char *new_name, char *new_email, char *old_name, char *old_email)
{
struct mailmap_entry *me;
int index;
if (old_email == NULL) {
old_email = new_email;
new_email = NULL;
}
if ((index = string_list_find_insert_index(map, old_email, 1)) < 0) {
/* mailmap entry exists, invert index value */
index = -1 - index;
} else {
/* create mailmap entry */
struct string_list_item *item = string_list_insert_at_index(index, old_email, map);
item->util = xmalloc(sizeof(struct mailmap_entry));
memset(item->util, 0, sizeof(struct mailmap_entry));
((struct mailmap_entry *)item->util)->namemap.strdup_strings = 1;
}
me = (struct mailmap_entry *)map->items[index].util;
if (old_name == NULL) {
debug_mm("mailmap: adding (simple) entry for %s at index %d\n", old_email, index);
/* Replace current name and new email for simple entry */
free(me->name);
free(me->email);
if (new_name)
me->name = xstrdup(new_name);
if (new_email)
me->email = xstrdup(new_email);
} else {
struct mailmap_info *mi = xmalloc(sizeof(struct mailmap_info));
debug_mm("mailmap: adding (complex) entry for %s at index %d\n", old_email, index);
if (new_name)
mi->name = xstrdup(new_name);
if (new_email)
mi->email = xstrdup(new_email);
string_list_insert(old_name, &me->namemap)->util = mi;
}
debug_mm("mailmap: '%s' <%s> -> '%s' <%s>\n",
old_name, old_email, new_name, new_email);
}
static char *parse_name_and_email(char *buffer, char **name, char **email)
{
char *left, *right, *nstart, *nend;
*name = *email = 0;
if ((left = strchr(buffer, '<')) == NULL)
return NULL;
if ((right = strchr(left+1, '>')) == NULL)
return NULL;
if (left+1 == right)
return NULL;
/* remove whitespace from beginning and end of name */
nstart = buffer;
while (isspace(*nstart) && nstart < left)
++nstart;
nend = left-1;
while (isspace(*nend) && nend > nstart)
--nend;
*name = (nstart < nend ? nstart : NULL);
*email = left+1;
*(nend+1) = '\0';
*right++ = '\0';
return (*right == '\0' ? NULL : right);
}
static int read_single_mailmap(struct string_list *map, const char *filename, char **repo_abbrev) static int read_single_mailmap(struct string_list *map, const char *filename, char **repo_abbrev)
{ {
char buffer[1024]; char buffer[1024];
@ -11,9 +126,7 @@ static int read_single_mailmap(struct string_list *map, const char *filename, ch
if (f == NULL) if (f == NULL)
return 1; return 1;
while (fgets(buffer, sizeof(buffer), f) != NULL) { while (fgets(buffer, sizeof(buffer), f) != NULL) {
char *end_of_name, *left_bracket, *right_bracket; char *name1 = 0, *email1 = 0, *name2 = 0, *email2 = 0;
char *name, *email;
int i;
if (buffer[0] == '#') { if (buffer[0] == '#') {
static const char abbrev[] = "# repo-abbrev:"; static const char abbrev[] = "# repo-abbrev:";
int abblen = sizeof(abbrev) - 1; int abblen = sizeof(abbrev) - 1;
@ -37,25 +150,11 @@ static int read_single_mailmap(struct string_list *map, const char *filename, ch
} }
continue; continue;
} }
if ((left_bracket = strchr(buffer, '<')) == NULL) if ((name2 = parse_name_and_email(buffer, &name1, &email1)) != NULL)
continue; parse_name_and_email(name2, &name2, &email2);
if ((right_bracket = strchr(left_bracket + 1, '>')) == NULL)
continue; if (email1)
if (right_bracket == left_bracket + 1) add_mapping(map, name1, email1, name2, email2);
continue;
for (end_of_name = left_bracket;
end_of_name != buffer && isspace(end_of_name[-1]);
end_of_name--)
; /* keep on looking */
if (end_of_name == buffer)
continue;
name = xmalloc(end_of_name - buffer + 1);
strlcpy(name, buffer, end_of_name - buffer + 1);
email = xmalloc(right_bracket - left_bracket);
for (i = 0; i < right_bracket - left_bracket - 1; i++)
email[i] = tolower(left_bracket[i + 1]);
email[right_bracket - left_bracket - 1] = '\0';
string_list_insert(email, map)->util = name;
} }
fclose(f); fclose(f);
return 0; return 0;
@ -63,22 +162,37 @@ static int read_single_mailmap(struct string_list *map, const char *filename, ch
int read_mailmap(struct string_list *map, char **repo_abbrev) int read_mailmap(struct string_list *map, char **repo_abbrev)
{ {
map->strdup_strings = 1;
/* each failure returns 1, so >1 means both calls failed */ /* each failure returns 1, so >1 means both calls failed */
return read_single_mailmap(map, ".mailmap", repo_abbrev) + return read_single_mailmap(map, ".mailmap", repo_abbrev) +
read_single_mailmap(map, git_mailmap_file, repo_abbrev) > 1; read_single_mailmap(map, git_mailmap_file, repo_abbrev) > 1;
} }
int map_email(struct string_list *map, const char *email, char *name, int maxlen) void clear_mailmap(struct string_list *map)
{
debug_mm("mailmap: clearing %d entries...\n", map->nr);
map->strdup_strings = 1;
string_list_clear_func(map, free_mailmap_entry);
debug_mm("mailmap: cleared\n");
}
int map_user(struct string_list *map,
char *email, int maxlen_email, char *name, int maxlen_name)
{ {
char *p; char *p;
struct string_list_item *item; struct string_list_item *item;
struct mailmap_entry *me;
char buf[1024], *mailbuf; char buf[1024], *mailbuf;
int i; int i;
/* autocomplete common developers */ /* figure out space requirement for email */
p = strchr(email, '>'); p = strchr(email, '>');
if (!p) if (!p) {
return 0; /* email passed in might not be wrapped in <>, but end with a \0 */
p = memchr(email, '\0', maxlen_email);
if (p == 0)
return 0;
}
if (p - email + 1 < sizeof(buf)) if (p - email + 1 < sizeof(buf))
mailbuf = buf; mailbuf = buf;
else else
@ -88,13 +202,39 @@ int map_email(struct string_list *map, const char *email, char *name, int maxlen
for (i = 0; i < p - email; i++) for (i = 0; i < p - email; i++)
mailbuf[i] = tolower(email[i]); mailbuf[i] = tolower(email[i]);
mailbuf[i] = 0; mailbuf[i] = 0;
debug_mm("map_user: map '%s' <%s>\n", name, mailbuf);
item = string_list_lookup(mailbuf, map); item = string_list_lookup(mailbuf, map);
if (item != NULL) {
me = (struct mailmap_entry *)item->util;
if (me->namemap.nr) {
/* The item has multiple items, so we'll look up on name too */
/* If the name is not found, we choose the simple entry */
struct string_list_item *subitem = string_list_lookup(name, &me->namemap);
if (subitem)
item = subitem;
}
}
if (mailbuf != buf) if (mailbuf != buf)
free(mailbuf); free(mailbuf);
if (item != NULL) { if (item != NULL) {
const char *realname = (const char *)item->util; struct mailmap_info *mi = (struct mailmap_info *)item->util;
strlcpy(name, realname, maxlen); if (mi->name == NULL && (mi->email == NULL || maxlen_email == 0)) {
debug_mm("map_user: -- (no simple mapping)\n");
return 0;
}
if (maxlen_email && mi->email)
strlcpy(email, mi->email, maxlen_email);
if (maxlen_name && mi->name)
strlcpy(name, mi->name, maxlen_name);
debug_mm("map_user: to '%s' <%s>\n", name, mi->email ? mi->email : "");
return 1; return 1;
} }
debug_mm("map_user: --\n");
return 0; return 0;
} }
int map_email(struct string_list *map, const char *email, char *name, int maxlen)
{
return map_user(map, (char *)email, 0, name, maxlen);
}

View File

@ -2,6 +2,10 @@
#define MAILMAP_H #define MAILMAP_H
int read_mailmap(struct string_list *map, char **repo_abbrev); int read_mailmap(struct string_list *map, char **repo_abbrev);
void clear_mailmap(struct string_list *map);
int map_email(struct string_list *mailmap, const char *email, char *name, int maxlen); int map_email(struct string_list *mailmap, const char *email, char *name, int maxlen);
int map_user(struct string_list *mailmap,
char *email, int maxlen_email, char *name, int maxlen_name);
#endif #endif