Merge branch 'ap/log-mailmap'

Teach commands in the "log" family to optionally pay attention to
the mailmap.

* ap/log-mailmap:
  log --use-mailmap: optimize for cases without --author/--committer search
  log: add log.mailmap configuration option
  log: grep author/committer using mailmap
  test: add test for --use-mailmap option
  log: add --use-mailmap option
  pretty: use mailmap to display username and email
  mailmap: add mailmap structure to rev_info and pp
  mailmap: simplify map_user() interface
  mailmap: remove email copy and length limitation
  Use split_ident_line to parse author and committer
  string-list: allow case-insensitive string list
This commit is contained in:
Junio C Hamano 2013-01-20 17:06:52 -08:00
commit 577f63e781
15 changed files with 390 additions and 226 deletions

View File

@ -1525,6 +1525,10 @@ log.showroot::
Tools like linkgit:git-log[1] or linkgit:git-whatchanged[1], which Tools like linkgit:git-log[1] or linkgit:git-whatchanged[1], which
normally hide the root commit will now show it. True by default. normally hide the root commit will now show it. True by default.
log.mailmap::
If true, makes linkgit:git-log[1], linkgit:git-show[1], and
linkgit:git-whatchanged[1] assume `--use-mailmap`.
mailmap.file:: mailmap.file::
The location of an augmenting mailmap file. The default The location of an augmenting mailmap file. The default
mailmap, located in the root of the repository, is loaded mailmap, located in the root of the repository, is loaded

View File

@ -47,6 +47,11 @@ OPTIONS
Print out the ref name given on the command line by which each Print out the ref name given on the command line by which each
commit was reached. commit was reached.
--use-mailmap::
Use mailmap file to map author and committer names and email
to canonical real names and email addresses. See
linkgit:git-shortlog[1].
--full-diff:: --full-diff::
Without this flag, "git log -p <path>..." shows commits that Without this flag, "git log -p <path>..." shows commits that
touch the specified paths, and diffs about the same specified touch the specified paths, and diffs about the same specified

View File

@ -1322,30 +1322,31 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
* Information on commits, used for output. * Information on commits, used for output.
*/ */
struct commit_info { struct commit_info {
const char *author; struct strbuf author;
const char *author_mail; struct strbuf author_mail;
unsigned long author_time; unsigned long author_time;
const char *author_tz; struct strbuf author_tz;
/* filled only when asked for details */ /* filled only when asked for details */
const char *committer; struct strbuf committer;
const char *committer_mail; struct strbuf committer_mail;
unsigned long committer_time; unsigned long committer_time;
const char *committer_tz; struct strbuf committer_tz;
const char *summary; struct strbuf summary;
}; };
/* /*
* Parse author/committer line in the commit object buffer * Parse author/committer line in the commit object buffer
*/ */
static void get_ac_line(const char *inbuf, const char *what, static void get_ac_line(const char *inbuf, const char *what,
int person_len, char *person, struct strbuf *name, struct strbuf *mail,
int mail_len, char *mail, unsigned long *time, struct strbuf *tz)
unsigned long *time, const char **tz)
{ {
int len, tzlen, maillen; struct ident_split ident;
char *tmp, *endp, *timepos, *mailpos; size_t len, maillen, namelen;
char *tmp, *endp;
const char *namebuf, *mailbuf;
tmp = strstr(inbuf, what); tmp = strstr(inbuf, what);
if (!tmp) if (!tmp)
@ -1356,69 +1357,61 @@ static void get_ac_line(const char *inbuf, const char *what,
len = strlen(tmp); len = strlen(tmp);
else else
len = endp - tmp; len = endp - tmp;
if (person_len <= len) {
if (split_ident_line(&ident, tmp, len)) {
error_out: error_out:
/* Ugh */ /* Ugh */
*tz = "(unknown)"; tmp = "(unknown)";
strcpy(person, *tz); strbuf_addstr(name, tmp);
strcpy(mail, *tz); strbuf_addstr(mail, tmp);
strbuf_addstr(tz, tmp);
*time = 0; *time = 0;
return; return;
} }
memcpy(person, tmp, len);
tmp = person; namelen = ident.name_end - ident.name_begin;
tmp += len; namebuf = ident.name_begin;
*tmp = 0;
while (person < tmp && *tmp != ' ')
tmp--;
if (tmp <= person)
goto error_out;
*tz = tmp+1;
tzlen = (person+len)-(tmp+1);
*tmp = 0; maillen = ident.mail_end - ident.mail_begin;
while (person < tmp && *tmp != ' ') mailbuf = ident.mail_begin;
tmp--;
if (tmp <= person)
goto error_out;
*time = strtoul(tmp, NULL, 10);
timepos = tmp;
*tmp = 0; *time = strtoul(ident.date_begin, NULL, 10);
while (person < tmp && !(*tmp == ' ' && tmp[1] == '<'))
tmp--;
if (tmp <= person)
return;
mailpos = tmp + 1;
*tmp = 0;
maillen = timepos - tmp;
memcpy(mail, mailpos, maillen);
if (!mailmap.nr) len = ident.tz_end - ident.tz_begin;
return; strbuf_add(tz, ident.tz_begin, len);
/*
* mailmap expansion may make the name longer.
* make room by pushing stuff down.
*/
tmp = person + person_len - (tzlen + 1);
memmove(tmp, *tz, tzlen);
tmp[tzlen] = 0;
*tz = tmp;
/* /*
* Now, convert both name and e-mail using mailmap * Now, convert both name and e-mail using mailmap
*/ */
if (map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) { map_user(&mailmap, &mailbuf, &maillen,
/* Add a trailing '>' to email, since map_user returns plain emails &namebuf, &namelen);
Note: It already has '<', since we replace from mail+1 */
mailpos = memchr(mail, '\0', mail_len); strbuf_addf(mail, "<%.*s>", (int)maillen, mailbuf);
if (mailpos && mailpos-mail < mail_len - 1) { strbuf_add(name, namebuf, namelen);
*mailpos = '>'; }
*(mailpos+1) = '\0';
} static void commit_info_init(struct commit_info *ci)
} {
strbuf_init(&ci->author, 0);
strbuf_init(&ci->author_mail, 0);
strbuf_init(&ci->author_tz, 0);
strbuf_init(&ci->committer, 0);
strbuf_init(&ci->committer_mail, 0);
strbuf_init(&ci->committer_tz, 0);
strbuf_init(&ci->summary, 0);
}
static void commit_info_destroy(struct commit_info *ci)
{
strbuf_release(&ci->author);
strbuf_release(&ci->author_mail);
strbuf_release(&ci->author_tz);
strbuf_release(&ci->committer);
strbuf_release(&ci->committer_mail);
strbuf_release(&ci->committer_tz);
strbuf_release(&ci->summary);
} }
static void get_commit_info(struct commit *commit, static void get_commit_info(struct commit *commit,
@ -1428,11 +1421,8 @@ static void get_commit_info(struct commit *commit,
int len; int len;
const char *subject, *encoding; const char *subject, *encoding;
char *reencoded, *message; char *reencoded, *message;
static char author_name[1024];
static char author_mail[1024]; commit_info_init(ret);
static char committer_name[1024];
static char committer_mail[1024];
static char summary_buf[1024];
/* /*
* We've operated without save_commit_buffer, so * We've operated without save_commit_buffer, so
@ -1450,11 +1440,8 @@ static void get_commit_info(struct commit *commit,
encoding = get_log_output_encoding(); encoding = get_log_output_encoding();
reencoded = logmsg_reencode(commit, encoding); reencoded = logmsg_reencode(commit, encoding);
message = reencoded ? reencoded : commit->buffer; message = reencoded ? reencoded : commit->buffer;
ret->author = author_name;
ret->author_mail = author_mail;
get_ac_line(message, "\nauthor ", get_ac_line(message, "\nauthor ",
sizeof(author_name), author_name, &ret->author, &ret->author_mail,
sizeof(author_mail), author_mail,
&ret->author_time, &ret->author_tz); &ret->author_time, &ret->author_tz);
if (!detailed) { if (!detailed) {
@ -1462,21 +1449,16 @@ static void get_commit_info(struct commit *commit,
return; return;
} }
ret->committer = committer_name;
ret->committer_mail = committer_mail;
get_ac_line(message, "\ncommitter ", get_ac_line(message, "\ncommitter ",
sizeof(committer_name), committer_name, &ret->committer, &ret->committer_mail,
sizeof(committer_mail), committer_mail,
&ret->committer_time, &ret->committer_tz); &ret->committer_time, &ret->committer_tz);
ret->summary = summary_buf;
len = find_commit_subject(message, &subject); len = find_commit_subject(message, &subject);
if (len && len < sizeof(summary_buf)) { if (len)
memcpy(summary_buf, subject, len); strbuf_add(&ret->summary, subject, len);
summary_buf[len] = 0; else
} else { strbuf_addf(&ret->summary, "(%s)", sha1_to_hex(commit->object.sha1));
sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1));
}
free(reencoded); free(reencoded);
} }
@ -1505,15 +1487,15 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat)
suspect->commit->object.flags |= METAINFO_SHOWN; suspect->commit->object.flags |= METAINFO_SHOWN;
get_commit_info(suspect->commit, &ci, 1); get_commit_info(suspect->commit, &ci, 1);
printf("author %s\n", ci.author); printf("author %s\n", ci.author.buf);
printf("author-mail %s\n", ci.author_mail); printf("author-mail %s\n", ci.author_mail.buf);
printf("author-time %lu\n", ci.author_time); printf("author-time %lu\n", ci.author_time);
printf("author-tz %s\n", ci.author_tz); printf("author-tz %s\n", ci.author_tz.buf);
printf("committer %s\n", ci.committer); printf("committer %s\n", ci.committer.buf);
printf("committer-mail %s\n", ci.committer_mail); printf("committer-mail %s\n", ci.committer_mail.buf);
printf("committer-time %lu\n", ci.committer_time); printf("committer-time %lu\n", ci.committer_time);
printf("committer-tz %s\n", ci.committer_tz); printf("committer-tz %s\n", ci.committer_tz.buf);
printf("summary %s\n", ci.summary); printf("summary %s\n", ci.summary.buf);
if (suspect->commit->object.flags & UNINTERESTING) if (suspect->commit->object.flags & UNINTERESTING)
printf("boundary\n"); printf("boundary\n");
if (suspect->previous) { if (suspect->previous) {
@ -1521,6 +1503,9 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat)
printf("previous %s ", sha1_to_hex(prev->commit->object.sha1)); printf("previous %s ", sha1_to_hex(prev->commit->object.sha1));
write_name_quoted(prev->path, stdout, '\n'); write_name_quoted(prev->path, stdout, '\n');
} }
commit_info_destroy(&ci);
return 1; return 1;
} }
@ -1707,11 +1692,11 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
if (opt & OUTPUT_ANNOTATE_COMPAT) { if (opt & OUTPUT_ANNOTATE_COMPAT) {
const char *name; const char *name;
if (opt & OUTPUT_SHOW_EMAIL) if (opt & OUTPUT_SHOW_EMAIL)
name = ci.author_mail; name = ci.author_mail.buf;
else else
name = ci.author; name = ci.author.buf;
printf("\t(%10s\t%10s\t%d)", name, printf("\t(%10s\t%10s\t%d)", name,
format_time(ci.author_time, ci.author_tz, format_time(ci.author_time, ci.author_tz.buf,
show_raw_time), show_raw_time),
ent->lno + 1 + cnt); ent->lno + 1 + cnt);
} else { } else {
@ -1730,14 +1715,14 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
const char *name; const char *name;
int pad; int pad;
if (opt & OUTPUT_SHOW_EMAIL) if (opt & OUTPUT_SHOW_EMAIL)
name = ci.author_mail; name = ci.author_mail.buf;
else else
name = ci.author; name = ci.author.buf;
pad = longest_author - utf8_strwidth(name); pad = longest_author - utf8_strwidth(name);
printf(" (%s%*s %10s", printf(" (%s%*s %10s",
name, pad, "", name, pad, "",
format_time(ci.author_time, format_time(ci.author_time,
ci.author_tz, ci.author_tz.buf,
show_raw_time)); show_raw_time));
} }
printf(" %*d) ", printf(" %*d) ",
@ -1752,6 +1737,8 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
if (sb->final_buf_size && cp[-1] != '\n') if (sb->final_buf_size && cp[-1] != '\n')
putchar('\n'); putchar('\n');
commit_info_destroy(&ci);
} }
static void output(struct scoreboard *sb, int option) static void output(struct scoreboard *sb, int option)
@ -1876,9 +1863,9 @@ static void find_alignment(struct scoreboard *sb, int *option)
suspect->commit->object.flags |= METAINFO_SHOWN; suspect->commit->object.flags |= METAINFO_SHOWN;
get_commit_info(suspect->commit, &ci, 1); get_commit_info(suspect->commit, &ci, 1);
if (*option & OUTPUT_SHOW_EMAIL) if (*option & OUTPUT_SHOW_EMAIL)
num = utf8_strwidth(ci.author_mail); num = utf8_strwidth(ci.author_mail.buf);
else else
num = utf8_strwidth(ci.author); num = utf8_strwidth(ci.author.buf);
if (longest_author < num) if (longest_author < num)
longest_author = num; longest_author = num;
} }
@ -1890,6 +1877,8 @@ static void find_alignment(struct scoreboard *sb, int *option)
longest_dst_lines = num; longest_dst_lines = num;
if (largest_score < ent_score(sb, e)) if (largest_score < ent_score(sb, e))
largest_score = ent_score(sb, e); largest_score = ent_score(sb, e);
commit_info_destroy(&ci);
} }
max_orig_digits = decimal_width(longest_src_lines); max_orig_digits = decimal_width(longest_src_lines);
max_digits = decimal_width(longest_dst_lines); max_digits = decimal_width(longest_dst_lines);

View File

@ -22,6 +22,7 @@
#include "branch.h" #include "branch.h"
#include "streaming.h" #include "streaming.h"
#include "version.h" #include "version.h"
#include "mailmap.h"
/* Set a default date-time format for git log ("log.date" config variable) */ /* Set a default date-time format for git log ("log.date" config variable) */
static const char *default_date_mode = NULL; static const char *default_date_mode = NULL;
@ -30,6 +31,7 @@ static int default_abbrev_commit;
static int default_show_root = 1; static int default_show_root = 1;
static int decoration_style; static int decoration_style;
static int decoration_given; static int decoration_given;
static int use_mailmap_config;
static const char *fmt_patch_subject_prefix = "PATCH"; static const char *fmt_patch_subject_prefix = "PATCH";
static const char *fmt_pretty; static const char *fmt_pretty;
@ -94,16 +96,18 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
struct rev_info *rev, struct setup_revision_opt *opt) struct rev_info *rev, struct setup_revision_opt *opt)
{ {
struct userformat_want w; struct userformat_want w;
int quiet = 0, source = 0; int quiet = 0, source = 0, mailmap = 0;
const struct option builtin_log_options[] = { const struct option builtin_log_options[] = {
OPT_BOOLEAN(0, "quiet", &quiet, N_("suppress diff output")), OPT_BOOLEAN(0, "quiet", &quiet, N_("suppress diff output")),
OPT_BOOLEAN(0, "source", &source, N_("show source")), OPT_BOOLEAN(0, "source", &source, N_("show source")),
OPT_BOOLEAN(0, "use-mailmap", &mailmap, N_("Use mail map file")),
{ OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"), { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"),
PARSE_OPT_OPTARG, decorate_callback}, PARSE_OPT_OPTARG, decorate_callback},
OPT_END() OPT_END()
}; };
mailmap = use_mailmap_config;
argc = parse_options(argc, argv, prefix, argc = parse_options(argc, argv, prefix,
builtin_log_options, builtin_log_usage, builtin_log_options, builtin_log_usage,
PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
@ -136,6 +140,11 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
if (source) if (source)
rev->show_source = 1; rev->show_source = 1;
if (mailmap) {
rev->mailmap = xcalloc(1, sizeof(struct string_list));
read_mailmap(rev->mailmap, NULL);
}
if (rev->pretty_given && rev->commit_format == CMIT_FMT_RAW) { if (rev->pretty_given && rev->commit_format == CMIT_FMT_RAW) {
/* /*
* "log --pretty=raw" is special; ignore UI oriented * "log --pretty=raw" is special; ignore UI oriented
@ -351,6 +360,11 @@ static int git_log_config(const char *var, const char *value, void *cb)
} }
if (!prefixcmp(var, "color.decorate.")) if (!prefixcmp(var, "color.decorate."))
return parse_decorate_color_config(var, 15, value); return parse_decorate_color_config(var, 15, value);
if (!strcmp(var, "log.mailmap")) {
use_mailmap_config = git_config_bool(var, value);
return 0;
}
if (grep_config(var, value, cb) < 0) if (grep_config(var, value, cb) < 0)
return -1; return -1;
return git_diff_ui_config(var, value, cb); return git_diff_ui_config(var, value, cb);

View File

@ -36,52 +36,28 @@ static void insert_one_record(struct shortlog *log,
const char *dot3 = log->common_repo_prefix; const char *dot3 = log->common_repo_prefix;
char *buffer, *p; char *buffer, *p;
struct string_list_item *item; struct string_list_item *item;
char namebuf[1024]; const char *mailbuf, *namebuf;
char emailbuf[1024]; size_t namelen, maillen;
size_t len;
const char *eol; const char *eol;
const char *boemail, *eoemail;
struct strbuf subject = STRBUF_INIT; struct strbuf subject = STRBUF_INIT;
struct strbuf namemailbuf = STRBUF_INIT;
struct ident_split ident;
boemail = strchr(author, '<'); if (split_ident_line(&ident, author, strlen(author)))
if (!boemail)
return;
eoemail = strchr(boemail, '>');
if (!eoemail)
return; return;
/* copy author name to namebuf, to support matching on both name and email */ namebuf = ident.name_begin;
memcpy(namebuf, author, boemail - author); mailbuf = ident.mail_begin;
len = boemail - author; namelen = ident.name_end - ident.name_begin;
while (len > 0 && isspace(namebuf[len-1])) maillen = ident.mail_end - ident.mail_begin;
len--;
namebuf[len] = 0;
/* copy email name to emailbuf, to allow email replacement as well */ map_user(&log->mailmap, &mailbuf, &maillen, &namebuf, &namelen);
memcpy(emailbuf, boemail+1, eoemail - boemail); strbuf_add(&namemailbuf, namebuf, namelen);
emailbuf[eoemail - boemail - 1] = 0;
if (!map_user(&log->mailmap, emailbuf, sizeof(emailbuf), namebuf, sizeof(namebuf))) { if (log->email)
while (author < boemail && isspace(*author)) strbuf_addf(&namemailbuf, " <%.*s>", (int)maillen, mailbuf);
author++;
for (len = 0;
len < sizeof(namebuf) - 1 && author + len < boemail;
len++)
namebuf[len] = author[len];
while (0 < len && isspace(namebuf[len-1]))
len--;
namebuf[len] = '\0';
}
else
len = strlen(namebuf);
if (log->email) { item = string_list_insert(&log->list, namemailbuf.buf);
size_t room = sizeof(namebuf) - len - 1;
int maillen = strlen(emailbuf);
snprintf(namebuf + len, room, " <%.*s>", maillen, emailbuf);
}
item = string_list_insert(&log->list, namebuf);
if (item->util == NULL) if (item->util == NULL)
item->util = xcalloc(1, sizeof(struct string_list)); item->util = xcalloc(1, sizeof(struct string_list));

View File

@ -89,6 +89,7 @@ struct pretty_print_context {
char *notes_message; char *notes_message;
struct reflog_walk_info *reflog_info; struct reflog_walk_info *reflog_info;
const char *output_encoding; const char *output_encoding;
struct string_list *mailmap;
int color; int color;
}; };

View File

@ -681,6 +681,7 @@ void show_log(struct rev_info *opt)
ctx.preserve_subject = opt->preserve_subject; ctx.preserve_subject = opt->preserve_subject;
ctx.reflog_info = opt->reflog_info; ctx.reflog_info = opt->reflog_info;
ctx.fmt = opt->commit_format; ctx.fmt = opt->commit_format;
ctx.mailmap = opt->mailmap;
ctx.color = opt->diffopt.use_color; ctx.color = opt->diffopt.use_color;
pretty_print_commit(&ctx, commit, &msgbuf); pretty_print_commit(&ctx, commit, &msgbuf);

106
mailmap.c
View File

@ -235,6 +235,7 @@ int read_mailmap(struct string_list *map, char **repo_abbrev)
int err = 0; int err = 0;
map->strdup_strings = 1; map->strdup_strings = 1;
map->cmp = strcasecmp;
if (!git_mailmap_blob && is_bare_repository()) if (!git_mailmap_blob && is_bare_repository())
git_mailmap_blob = "HEAD:.mailmap"; git_mailmap_blob = "HEAD:.mailmap";
@ -253,60 +254,95 @@ void clear_mailmap(struct string_list *map)
debug_mm("mailmap: cleared\n"); debug_mm("mailmap: cleared\n");
} }
int map_user(struct string_list *map, /*
char *email, int maxlen_email, char *name, int maxlen_name) * Look for an entry in map that match string[0:len]; string[len]
* does not have to be NUL (but it could be).
*/
static struct string_list_item *lookup_prefix(struct string_list *map,
const char *string, size_t len)
{
int i = string_list_find_insert_index(map, string, 1);
if (i < 0) {
/* exact match */
i = -1 - i;
if (!string[len])
return &map->items[i];
/*
* that map entry matches exactly to the string, including
* the cruft at the end beyond "len". That is not a match
* with string[0:len] that we are looking for.
*/
} else if (!string[len]) {
/*
* asked with the whole string, and got nothing. No
* matching entry can exist in the map.
*/
return NULL;
}
/*
* i is at the exact match to an overlong key, or location the
* overlong key would be inserted, which must come after the
* real location of the key if one exists.
*/
while (0 <= --i && i < map->nr) {
int cmp = strncasecmp(map->items[i].string, string, len);
if (cmp < 0)
/*
* "i" points at a key definitely below the prefix;
* the map does not have string[0:len] in it.
*/
break;
else if (!cmp && !map->items[i].string[len])
/* found it */
return &map->items[i];
/*
* otherwise, the string at "i" may be string[0:len]
* followed by a string that sorts later than string[len:];
* keep trying.
*/
}
return NULL;
}
int map_user(struct string_list *map,
const char **email, size_t *emaillen,
const char **name, size_t *namelen)
{ {
char *end_of_email;
struct string_list_item *item; struct string_list_item *item;
struct mailmap_entry *me; struct mailmap_entry *me;
char buf[1024], *mailbuf;
int i;
/* figure out space requirement for email */ debug_mm("map_user: map '%.*s' <%.*s>\n",
end_of_email = strchr(email, '>'); *name, *namelen, *emaillen, *email);
if (!end_of_email) {
/* email passed in might not be wrapped in <>, but end with a \0 */
end_of_email = memchr(email, '\0', maxlen_email);
if (!end_of_email)
return 0;
}
if (end_of_email - email + 1 < sizeof(buf))
mailbuf = buf;
else
mailbuf = xmalloc(end_of_email - email + 1);
/* downcase the email address */ item = lookup_prefix(map, *email, *emaillen);
for (i = 0; i < end_of_email - email; i++)
mailbuf[i] = tolower(email[i]);
mailbuf[i] = 0;
debug_mm("map_user: map '%s' <%s>\n", name, mailbuf);
item = string_list_lookup(map, mailbuf);
if (item != NULL) { if (item != NULL) {
me = (struct mailmap_entry *)item->util; me = (struct mailmap_entry *)item->util;
if (me->namemap.nr) { if (me->namemap.nr) {
/* The item has multiple items, so we'll look up on name too */ /* The item has multiple items, so we'll look up on name too */
/* If the name is not found, we choose the simple entry */ /* If the name is not found, we choose the simple entry */
struct string_list_item *subitem = string_list_lookup(&me->namemap, name); struct string_list_item *subitem;
subitem = lookup_prefix(&me->namemap, *name, *namelen);
if (subitem) if (subitem)
item = subitem; item = subitem;
} }
} }
if (mailbuf != buf)
free(mailbuf);
if (item != NULL) { if (item != NULL) {
struct mailmap_info *mi = (struct mailmap_info *)item->util; struct mailmap_info *mi = (struct mailmap_info *)item->util;
if (mi->name == NULL && (mi->email == NULL || maxlen_email == 0)) { if (mi->name == NULL && mi->email == NULL) {
debug_mm("map_user: -- (no simple mapping)\n"); debug_mm("map_user: -- (no simple mapping)\n");
return 0; return 0;
} }
if (maxlen_email && mi->email) if (mi->email) {
strlcpy(email, mi->email, maxlen_email); *email = mi->email;
else *emaillen = strlen(*email);
*end_of_email = '\0'; }
if (maxlen_name && mi->name) if (mi->name) {
strlcpy(name, mi->name, maxlen_name); *name = mi->name;
debug_mm("map_user: to '%s' <%s>\n", name, mi->email ? mi->email : ""); *namelen = strlen(*name);
}
debug_mm("map_user: to '%.*s' <.*%s>\n", *namelen, *name,
*emaillen, *email);
return 1; return 1;
} }
debug_mm("map_user: --\n"); debug_mm("map_user: --\n");

View File

@ -4,7 +4,7 @@
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); void clear_mailmap(struct string_list *map);
int map_user(struct string_list *mailmap, int map_user(struct string_list *map,
char *email, int maxlen_email, char *name, int maxlen_name); const char **email, size_t *emaillen, const char **name, size_t *namelen);
#endif #endif

112
pretty.c
View File

@ -387,56 +387,79 @@ void pp_user_info(const struct pretty_print_context *pp,
const char *what, struct strbuf *sb, const char *what, struct strbuf *sb,
const char *line, const char *encoding) const char *line, const char *encoding)
{ {
struct strbuf name;
struct strbuf mail;
struct ident_split ident;
int linelen;
char *line_end, *date;
const char *mailbuf, *namebuf;
size_t namelen, maillen;
int max_length = 78; /* per rfc2822 */ int max_length = 78; /* per rfc2822 */
char *date;
int namelen;
unsigned long time; unsigned long time;
int tz; int tz;
if (pp->fmt == CMIT_FMT_ONELINE) if (pp->fmt == CMIT_FMT_ONELINE)
return; return;
date = strchr(line, '>');
if (!date) line_end = strchr(line, '\n');
if (!line_end) {
line_end = strchr(line, '\0');
if (!line_end)
return;
}
linelen = ++line_end - line;
if (split_ident_line(&ident, line, linelen))
return; return;
namelen = ++date - line;
time = strtoul(date, &date, 10);
mailbuf = ident.mail_begin;
maillen = ident.mail_end - ident.mail_begin;
namebuf = ident.name_begin;
namelen = ident.name_end - ident.name_begin;
if (pp->mailmap)
map_user(pp->mailmap, &mailbuf, &maillen, &namebuf, &namelen);
strbuf_init(&mail, 0);
strbuf_init(&name, 0);
strbuf_add(&mail, mailbuf, maillen);
strbuf_add(&name, namebuf, namelen);
namelen = name.len + mail.len + 3; /* ' ' + '<' + '>' */
time = strtoul(ident.date_begin, &date, 10);
tz = strtol(date, NULL, 10); tz = strtol(date, NULL, 10);
if (pp->fmt == CMIT_FMT_EMAIL) { if (pp->fmt == CMIT_FMT_EMAIL) {
char *name_tail = strchr(line, '<');
int display_name_length;
if (!name_tail)
return;
while (line < name_tail && isspace(name_tail[-1]))
name_tail--;
display_name_length = name_tail - line;
strbuf_addstr(sb, "From: "); strbuf_addstr(sb, "From: ");
if (needs_rfc2047_encoding(line, display_name_length, RFC2047_ADDRESS)) { if (needs_rfc2047_encoding(name.buf, name.len, RFC2047_ADDRESS)) {
add_rfc2047(sb, line, display_name_length, add_rfc2047(sb, name.buf, name.len,
encoding, RFC2047_ADDRESS); encoding, RFC2047_ADDRESS);
max_length = 76; /* per rfc2047 */ max_length = 76; /* per rfc2047 */
} else if (needs_rfc822_quoting(line, display_name_length)) { } else if (needs_rfc822_quoting(name.buf, name.len)) {
struct strbuf quoted = STRBUF_INIT; struct strbuf quoted = STRBUF_INIT;
add_rfc822_quoted(&quoted, line, display_name_length); add_rfc822_quoted(&quoted, name.buf, name.len);
strbuf_add_wrapped_bytes(sb, quoted.buf, quoted.len, strbuf_add_wrapped_bytes(sb, quoted.buf, quoted.len,
-6, 1, max_length); -6, 1, max_length);
strbuf_release(&quoted); strbuf_release(&quoted);
} else { } else {
strbuf_add_wrapped_bytes(sb, line, display_name_length, strbuf_add_wrapped_bytes(sb, name.buf, name.len,
-6, 1, max_length); -6, 1, max_length);
} }
if (namelen - display_name_length + last_line_length(sb) > max_length) { if (namelen - name.len + last_line_length(sb) > max_length)
strbuf_addch(sb, '\n'); strbuf_addch(sb, '\n');
if (!isspace(name_tail[0]))
strbuf_addch(sb, ' '); strbuf_addf(sb, " <%s>\n", mail.buf);
}
strbuf_add(sb, name_tail, namelen - display_name_length);
strbuf_addch(sb, '\n');
} else { } else {
strbuf_addf(sb, "%s: %.*s%.*s\n", what, strbuf_addf(sb, "%s: %.*s%s <%s>\n", what,
(pp->fmt == CMIT_FMT_FULLER) ? 4 : 0, (pp->fmt == CMIT_FMT_FULLER) ? 4 : 0,
" ", namelen, line); " ", name.buf, mail.buf);
} }
strbuf_release(&mail);
strbuf_release(&name);
switch (pp->fmt) { switch (pp->fmt) {
case CMIT_FMT_MEDIUM: case CMIT_FMT_MEDIUM:
strbuf_addf(sb, "Date: %s\n", show_date(time, tz, pp->date_mode)); strbuf_addf(sb, "Date: %s\n", show_date(time, tz, pp->date_mode));
@ -586,7 +609,8 @@ char *logmsg_reencode(const struct commit *commit,
return out; return out;
} }
static int mailmap_name(char *email, int email_len, char *name, int name_len) static int mailmap_name(const char **email, size_t *email_len,
const char **name, size_t *name_len)
{ {
static struct string_list *mail_map; static struct string_list *mail_map;
if (!mail_map) { if (!mail_map) {
@ -603,36 +627,26 @@ static size_t format_person_part(struct strbuf *sb, char part,
const int placeholder_len = 2; const int placeholder_len = 2;
int tz; int tz;
unsigned long date = 0; unsigned long date = 0;
char person_name[1024];
char person_mail[1024];
struct ident_split s; struct ident_split s;
const char *name_start, *name_end, *mail_start, *mail_end; const char *name, *mail;
size_t maillen, namelen;
if (split_ident_line(&s, msg, len) < 0) if (split_ident_line(&s, msg, len) < 0)
goto skip; goto skip;
name_start = s.name_begin; name = s.name_begin;
name_end = s.name_end; namelen = s.name_end - s.name_begin;
mail_start = s.mail_begin; mail = s.mail_begin;
mail_end = s.mail_end; maillen = s.mail_end - s.mail_begin;
if (part == 'N' || part == 'E') { /* mailmap lookup */ if (part == 'N' || part == 'E') /* mailmap lookup */
snprintf(person_name, sizeof(person_name), "%.*s", mailmap_name(&mail, &maillen, &name, &namelen);
(int)(name_end - name_start), name_start);
snprintf(person_mail, sizeof(person_mail), "%.*s",
(int)(mail_end - mail_start), mail_start);
mailmap_name(person_mail, sizeof(person_mail), person_name, sizeof(person_name));
name_start = person_name;
name_end = name_start + strlen(person_name);
mail_start = person_mail;
mail_end = mail_start + strlen(person_mail);
}
if (part == 'n' || part == 'N') { /* name */ if (part == 'n' || part == 'N') { /* name */
strbuf_add(sb, name_start, name_end-name_start); strbuf_add(sb, name, namelen);
return placeholder_len; return placeholder_len;
} }
if (part == 'e' || part == 'E') { /* email */ if (part == 'e' || part == 'E') { /* email */
strbuf_add(sb, mail_start, mail_end-mail_start); strbuf_add(sb, mail, maillen);
return placeholder_len; return placeholder_len;
} }

View File

@ -13,6 +13,7 @@
#include "decorate.h" #include "decorate.h"
#include "log-tree.h" #include "log-tree.h"
#include "string-list.h" #include "string-list.h"
#include "mailmap.h"
volatile show_early_output_fn_t show_early_output; volatile show_early_output_fn_t show_early_output;
@ -2219,6 +2220,51 @@ static int rewrite_parents(struct rev_info *revs, struct commit *commit)
return 0; return 0;
} }
static int commit_rewrite_person(struct strbuf *buf, const char *what, struct string_list *mailmap)
{
char *person, *endp;
size_t len, namelen, maillen;
const char *name;
const char *mail;
struct ident_split ident;
person = strstr(buf->buf, what);
if (!person)
return 0;
person += strlen(what);
endp = strchr(person, '\n');
if (!endp)
return 0;
len = endp - person;
if (split_ident_line(&ident, person, len))
return 0;
mail = ident.mail_begin;
maillen = ident.mail_end - ident.mail_begin;
name = ident.name_begin;
namelen = ident.name_end - ident.name_begin;
if (map_user(mailmap, &mail, &maillen, &name, &namelen)) {
struct strbuf namemail = STRBUF_INIT;
strbuf_addf(&namemail, "%.*s <%.*s>",
(int)namelen, name, (int)maillen, mail);
strbuf_splice(buf, ident.name_begin - buf->buf,
ident.mail_end - ident.name_begin + 1,
namemail.buf, namemail.len);
strbuf_release(&namemail);
return 1;
}
return 0;
}
static int commit_match(struct commit *commit, struct rev_info *opt) static int commit_match(struct commit *commit, struct rev_info *opt)
{ {
int retval; int retval;
@ -2237,6 +2283,14 @@ static int commit_match(struct commit *commit, struct rev_info *opt)
if (buf.len) if (buf.len)
strbuf_addstr(&buf, commit->buffer); strbuf_addstr(&buf, commit->buffer);
if (opt->grep_filter.header_list && opt->mailmap) {
if (!buf.len)
strbuf_addstr(&buf, commit->buffer);
commit_rewrite_person(&buf, "\nauthor ", opt->mailmap);
commit_rewrite_person(&buf, "\ncommitter ", opt->mailmap);
}
/* Append "fake" message parts as needed */ /* Append "fake" message parts as needed */
if (opt->show_notes) { if (opt->show_notes) {
if (!buf.len) if (!buf.len)

View File

@ -144,6 +144,7 @@ struct rev_info {
const char *subject_prefix; const char *subject_prefix;
int no_inline; int no_inline;
int show_log_size; int show_log_size;
struct string_list *mailmap;
/* Filter by commit log message */ /* Filter by commit log message */
struct grep_opt grep_filter; struct grep_opt grep_filter;

View File

@ -7,10 +7,11 @@ static int get_entry_index(const struct string_list *list, const char *string,
int *exact_match) int *exact_match)
{ {
int left = -1, right = list->nr; int left = -1, right = list->nr;
compare_strings_fn cmp = list->cmp ? list->cmp : strcmp;
while (left + 1 < right) { while (left + 1 < right) {
int middle = (left + right) / 2; int middle = (left + right) / 2;
int compare = strcmp(string, list->items[middle].string); int compare = cmp(string, list->items[middle].string);
if (compare < 0) if (compare < 0)
right = middle; right = middle;
else if (compare > 0) else if (compare > 0)
@ -96,8 +97,9 @@ void string_list_remove_duplicates(struct string_list *list, int free_util)
{ {
if (list->nr > 1) { if (list->nr > 1) {
int src, dst; int src, dst;
compare_strings_fn cmp = list->cmp ? list->cmp : strcmp;
for (src = dst = 1; src < list->nr; src++) { for (src = dst = 1; src < list->nr; src++) {
if (!strcmp(list->items[dst - 1].string, list->items[src].string)) { if (!cmp(list->items[dst - 1].string, list->items[src].string)) {
if (list->strdup_strings) if (list->strdup_strings)
free(list->items[src].string); free(list->items[src].string);
if (free_util) if (free_util)
@ -210,15 +212,20 @@ struct string_list_item *string_list_append(struct string_list *list,
list->strdup_strings ? xstrdup(string) : (char *)string); list->strdup_strings ? xstrdup(string) : (char *)string);
} }
/* Yuck */
static compare_strings_fn compare_for_qsort;
/* Only call this from inside sort_string_list! */
static int cmp_items(const void *a, const void *b) static int cmp_items(const void *a, const void *b)
{ {
const struct string_list_item *one = a; const struct string_list_item *one = a;
const struct string_list_item *two = b; const struct string_list_item *two = b;
return strcmp(one->string, two->string); return compare_for_qsort(one->string, two->string);
} }
void sort_string_list(struct string_list *list) void sort_string_list(struct string_list *list)
{ {
compare_for_qsort = list->cmp ? list->cmp : strcmp;
qsort(list->items, list->nr, sizeof(*list->items), cmp_items); qsort(list->items, list->nr, sizeof(*list->items), cmp_items);
} }
@ -226,8 +233,10 @@ struct string_list_item *unsorted_string_list_lookup(struct string_list *list,
const char *string) const char *string)
{ {
int i; int i;
compare_strings_fn cmp = list->cmp ? list->cmp : strcmp;
for (i = 0; i < list->nr; i++) for (i = 0; i < list->nr; i++)
if (!strcmp(string, list->items[i].string)) if (!cmp(string, list->items[i].string))
return list->items + i; return list->items + i;
return NULL; return NULL;
} }

View File

@ -5,10 +5,14 @@ struct string_list_item {
char *string; char *string;
void *util; void *util;
}; };
typedef int (*compare_strings_fn)(const char *, const char *);
struct string_list { struct string_list {
struct string_list_item *items; struct string_list_item *items;
unsigned int nr, alloc; unsigned int nr, alloc;
unsigned int strdup_strings:1; unsigned int strdup_strings:1;
compare_strings_fn cmp; /* NULL uses strcmp() */
}; };
#define STRING_LIST_INIT_NODUP { NULL, 0, 0, 0 } #define STRING_LIST_INIT_NODUP { NULL, 0, 0, 0 }

View File

@ -337,6 +337,62 @@ test_expect_success 'Log output (complex mapping)' '
test_cmp expect actual test_cmp expect actual
' '
cat >expect <<\EOF
Author: CTO <cto@company.xx>
Author: Santa Claus <santa.claus@northpole.xx>
Author: Santa Claus <santa.claus@northpole.xx>
Author: Other Author <other@author.xx>
Author: Other Author <other@author.xx>
Author: Some Dude <some@dude.xx>
Author: A U Thor <author@example.com>
EOF
test_expect_success 'Log output with --use-mailmap' '
git log --use-mailmap | grep Author >actual &&
test_cmp expect actual
'
cat >expect <<\EOF
Author: CTO <cto@company.xx>
Author: Santa Claus <santa.claus@northpole.xx>
Author: Santa Claus <santa.claus@northpole.xx>
Author: Other Author <other@author.xx>
Author: Other Author <other@author.xx>
Author: Some Dude <some@dude.xx>
Author: A U Thor <author@example.com>
EOF
test_expect_success 'Log output with log.mailmap' '
git -c log.mailmap=True log | grep Author >actual &&
test_cmp expect actual
'
cat >expect <<\EOF
Author: Santa Claus <santa.claus@northpole.xx>
Author: Santa Claus <santa.claus@northpole.xx>
EOF
test_expect_success 'Grep author with --use-mailmap' '
git log --use-mailmap --author Santa | grep Author >actual &&
test_cmp expect actual
'
cat >expect <<\EOF
Author: Santa Claus <santa.claus@northpole.xx>
Author: Santa Claus <santa.claus@northpole.xx>
EOF
test_expect_success 'Grep author with log.mailmap' '
git -c log.mailmap=True log --author Santa | grep Author >actual &&
test_cmp expect actual
'
>expect
test_expect_success 'Only grep replaced author with --use-mailmap' '
git log --use-mailmap --author "<cto@coompany.xx>" >actual &&
test_cmp expect actual
'
# git blame # git blame
cat >expect <<\EOF cat >expect <<\EOF
^OBJI (A U Thor DATE 1) one ^OBJI (A U Thor DATE 1) one