Merge branch 'ap/log-mailmap'
authorJunio C Hamano <gitster@pobox.com>
Mon, 21 Jan 2013 01:06:52 +0000 (17:06 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 21 Jan 2013 01:06:53 +0000 (17:06 -0800)
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

13 files changed:
1  2 
Documentation/config.txt
Documentation/git-log.txt
builtin/blame.c
builtin/log.c
builtin/shortlog.c
commit.h
log-tree.c
mailmap.c
pretty.c
revision.h
string-list.c
string-list.h
t/t4203-mailmap.sh
diff --combined Documentation/config.txt
index ef45c99e53ed0b835b70f52346c2937379011918,226362a0ea48e389f66f55ae3c14031c9c1654cc..5ecb0fccb1ceba2bf09acf705a0ce484b3029f16
@@@ -140,11 -140,10 +140,11 @@@ advice.*:
        can tell Git that you do not need help by setting these to 'false':
  +
  --
 -      pushNonFastForward::
 +      pushUpdateRejected::
                Set this variable to 'false' if you want to disable
 -              'pushNonFFCurrent', 'pushNonFFDefault', and
 -              'pushNonFFMatching' simultaneously.
 +              'pushNonFFCurrent', 'pushNonFFDefault',
 +              'pushNonFFMatching', and 'pushAlreadyExists'
 +              simultaneously.
        pushNonFFCurrent::
                Advice shown when linkgit:git-push[1] fails due to a
                non-fast-forward update to the current branch.
                'matching refs' explicitly (i.e. you used ':', or
                specified a refspec that isn't your current branch) and
                it resulted in a non-fast-forward error.
 +      pushAlreadyExists::
 +              Shown when linkgit:git-push[1] rejects an update that
 +              does not qualify for fast-forwarding (e.g., a tag.)
        statusHints::
                Show directions on how to proceed from the current
                state in the output of linkgit:git-status[1], in
@@@ -739,12 -735,6 +739,12 @@@ branch.<name>.rebase:
  it unless you understand the implications (see linkgit:git-rebase[1]
  for details).
  
 +branch.<name>.description::
 +      Branch description, can be edited with
 +      `git branch --edit-description`. Branch description is
 +      automatically added in the format-patch cover letter or
 +      request-pull summary.
 +
  browser.<tool>.cmd::
        Specify the command to invoke the specified browser. The
        specified command is evaluated in shell with the URLs passed
@@@ -1361,12 -1351,6 +1361,12 @@@ help.autocorrect:
        value is 0 - the command will be just shown but not executed.
        This is the default.
  
 +help.htmlpath::
 +      Specify the path where the HTML documentation resides. File system paths
 +      and URLs are supported. HTML pages will be prefixed with this path when
 +      help is displayed in the 'web' format. This defaults to the documentation
 +      path of your Git installation.
 +
  http.proxy::
        Override the HTTP proxy, normally configured using the 'http_proxy',
        'https_proxy', and 'all_proxy' environment variables (see
@@@ -1525,6 -1509,10 +1525,10 @@@ log.showroot:
        Tools like linkgit:git-log[1] or linkgit:git-whatchanged[1], which
        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::
        The location of an augmenting mailmap file. The default
        mailmap, located in the root of the repository, is loaded
        subdirectory, or somewhere outside of the repository itself.
        See linkgit:git-shortlog[1] and linkgit:git-blame[1].
  
 +mailmap.blob::
 +      Like `mailmap.file`, but consider the value as a reference to a
 +      blob in the repository. If both `mailmap.file` and
 +      `mailmap.blob` are given, both are parsed, with entries from
 +      `mailmap.file` taking precedence. In a bare repository, this
 +      defaults to `HEAD:.mailmap`. In a non-bare repository, it
 +      defaults to empty.
 +
  man.viewer::
        Specify the programs that may be used to display help in the
        'man' format. See linkgit:git-help[1].
@@@ -2019,12 -1999,6 +2023,12 @@@ submodule.<name>.update:
        URL and other values found in the `.gitmodules` file.  See
        linkgit:git-submodule[1] and linkgit:gitmodules[5] for details.
  
 +submodule.<name>.branch::
 +      The remote branch name for a submodule, used by `git submodule
 +      update --remote`.  Set this option to override the value found in
 +      the `.gitmodules` file.  See linkgit:git-submodule[1] and
 +      linkgit:gitmodules[5] for details.
 +
  submodule.<name>.fetchRecurseSubmodules::
        This option can be used to control recursive fetching of this
        submodule. It can be overridden by using the --[no-]recurse-submodules
index 08a185db7fb7192b4e94c83ae6781893e119c6cf,a99be97335904f73465ff148b42b04230ed97d3e..22c0d6e4b1074b71468ac1c9b16d7eeb0434e225
@@@ -47,6 -47,11 +47,11 @@@ OPTION
        Print out the ref name given on the command line by which each
        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::
        Without this flag, "git log -p <path>..." shows commits that
        touch the specified paths, and diffs about the same specified
@@@ -167,7 -172,7 +172,7 @@@ log.showroot:
        `git log -p` output would be shown without a diff attached.
        The default is `true`.
  
 -mailmap.file::
 +mailmap.*::
        See linkgit:git-shortlog[1].
  
  notes.displayRef::
diff --combined builtin/blame.c
index bc6c899d302d3027a0531b613d4c2dbf706d558d,9d20e25552c57fbb32173efdf5771f0cdeb48605..b431ba320997918324a76c29b81dd01613cfa9db
@@@ -42,7 -42,6 +42,7 @@@ static int blank_boundary
  static int incremental;
  static int xdl_opts;
  static int abbrev = -1;
 +static int no_whole_file_rename;
  
  static enum date_mode blame_date_mode = DATE_ISO8601;
  static size_t blame_date_width;
@@@ -1227,7 -1226,7 +1227,7 @@@ static void pass_blame(struct scoreboar
         * The first pass looks for unrenamed path to optimize for
         * common cases, then we look for renames in the second pass.
         */
 -      for (pass = 0; pass < 2; pass++) {
 +      for (pass = 0; pass < 2 - no_whole_file_rename; pass++) {
                struct origin *(*find)(struct scoreboard *,
                                       struct commit *, struct origin *);
                find = pass ? find_rename : find_origin;
   * Information on commits, used for output.
   */
  struct commit_info {
-       const char *author;
-       const char *author_mail;
+       struct strbuf author;
+       struct strbuf author_mail;
        unsigned long author_time;
-       const char *author_tz;
+       struct strbuf author_tz;
  
        /* filled only when asked for details */
-       const char *committer;
-       const char *committer_mail;
+       struct strbuf committer;
+       struct strbuf committer_mail;
        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
   */
  static void get_ac_line(const char *inbuf, const char *what,
-                       int person_len, char *person,
-                       int mail_len, char *mail,
-                       unsigned long *time, const char **tz)
+       struct strbuf *name, struct strbuf *mail,
+       unsigned long *time, struct strbuf *tz)
  {
-       int len, tzlen, maillen;
-       char *tmp, *endp, *timepos, *mailpos;
+       struct ident_split ident;
+       size_t len, maillen, namelen;
+       char *tmp, *endp;
+       const char *namebuf, *mailbuf;
  
        tmp = strstr(inbuf, what);
        if (!tmp)
                len = strlen(tmp);
        else
                len = endp - tmp;
-       if (person_len <= len) {
+       if (split_ident_line(&ident, tmp, len)) {
        error_out:
                /* Ugh */
-               *tz = "(unknown)";
-               strcpy(person, *tz);
-               strcpy(mail, *tz);
+               tmp = "(unknown)";
+               strbuf_addstr(name, tmp);
+               strbuf_addstr(mail, tmp);
+               strbuf_addstr(tz, tmp);
                *time = 0;
                return;
        }
-       memcpy(person, tmp, len);
-       tmp = person;
-       tmp += len;
-       *tmp = 0;
-       while (person < tmp && *tmp != ' ')
-               tmp--;
-       if (tmp <= person)
-               goto error_out;
-       *tz = tmp+1;
-       tzlen = (person+len)-(tmp+1);
  
-       *tmp = 0;
-       while (person < tmp && *tmp != ' ')
-               tmp--;
-       if (tmp <= person)
-               goto error_out;
-       *time = strtoul(tmp, NULL, 10);
-       timepos = tmp;
+       namelen = ident.name_end - ident.name_begin;
+       namebuf = ident.name_begin;
  
-       *tmp = 0;
-       while (person < tmp && !(*tmp == ' ' && tmp[1] == '<'))
-               tmp--;
-       if (tmp <= person)
-               return;
-       mailpos = tmp + 1;
-       *tmp = 0;
-       maillen = timepos - tmp;
-       memcpy(mail, mailpos, maillen);
+       maillen = ident.mail_end - ident.mail_begin;
+       mailbuf = ident.mail_begin;
  
-       if (!mailmap.nr)
-               return;
+       *time = strtoul(ident.date_begin, NULL, 10);
  
-       /*
-        * 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;
+       len = ident.tz_end - ident.tz_begin;
+       strbuf_add(tz, ident.tz_begin, len);
  
        /*
         * Now, convert both name and e-mail using mailmap
         */
-       if (map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) {
-               /* Add a trailing '>' to email, since map_user returns plain emails
-                  Note: It already has '<', since we replace from mail+1 */
-               mailpos = memchr(mail, '\0', mail_len);
-               if (mailpos && mailpos-mail < mail_len - 1) {
-                       *mailpos = '>';
-                       *(mailpos+1) = '\0';
-               }
-       }
+       map_user(&mailmap, &mailbuf, &maillen,
+                &namebuf, &namelen);
+       strbuf_addf(mail, "<%.*s>", (int)maillen, mailbuf);
+       strbuf_add(name, namebuf, namelen);
+ }
+ 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,
        int len;
        const char *subject, *encoding;
        char *reencoded, *message;
-       static char author_name[1024];
-       static char author_mail[1024];
-       static char committer_name[1024];
-       static char committer_mail[1024];
-       static char summary_buf[1024];
+       commit_info_init(ret);
  
        /*
         * We've operated without save_commit_buffer, so
        encoding = get_log_output_encoding();
        reencoded = logmsg_reencode(commit, encoding);
        message   = reencoded ? reencoded : commit->buffer;
-       ret->author = author_name;
-       ret->author_mail = author_mail;
        get_ac_line(message, "\nauthor ",
-                   sizeof(author_name), author_name,
-                   sizeof(author_mail), author_mail,
+                   &ret->author, &ret->author_mail,
                    &ret->author_time, &ret->author_tz);
  
        if (!detailed) {
                return;
        }
  
-       ret->committer = committer_name;
-       ret->committer_mail = committer_mail;
        get_ac_line(message, "\ncommitter ",
-                   sizeof(committer_name), committer_name,
-                   sizeof(committer_mail), committer_mail,
+                   &ret->committer, &ret->committer_mail,
                    &ret->committer_time, &ret->committer_tz);
  
-       ret->summary = summary_buf;
        len = find_commit_subject(message, &subject);
-       if (len && len < sizeof(summary_buf)) {
-               memcpy(summary_buf, subject, len);
-               summary_buf[len] = 0;
-       } else {
-               sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1));
-       }
+       if (len)
+               strbuf_add(&ret->summary, subject, len);
+       else
+               strbuf_addf(&ret->summary, "(%s)", sha1_to_hex(commit->object.sha1));
        free(reencoded);
  }
  
@@@ -1505,15 -1486,15 +1487,15 @@@ static int emit_one_suspect_detail(stru
  
        suspect->commit->object.flags |= METAINFO_SHOWN;
        get_commit_info(suspect->commit, &ci, 1);
-       printf("author %s\n", ci.author);
-       printf("author-mail %s\n", ci.author_mail);
+       printf("author %s\n", ci.author.buf);
+       printf("author-mail %s\n", ci.author_mail.buf);
        printf("author-time %lu\n", ci.author_time);
-       printf("author-tz %s\n", ci.author_tz);
-       printf("committer %s\n", ci.committer);
-       printf("committer-mail %s\n", ci.committer_mail);
+       printf("author-tz %s\n", ci.author_tz.buf);
+       printf("committer %s\n", ci.committer.buf);
+       printf("committer-mail %s\n", ci.committer_mail.buf);
        printf("committer-time %lu\n", ci.committer_time);
-       printf("committer-tz %s\n", ci.committer_tz);
-       printf("summary %s\n", ci.summary);
+       printf("committer-tz %s\n", ci.committer_tz.buf);
+       printf("summary %s\n", ci.summary.buf);
        if (suspect->commit->object.flags & UNINTERESTING)
                printf("boundary\n");
        if (suspect->previous) {
                printf("previous %s ", sha1_to_hex(prev->commit->object.sha1));
                write_name_quoted(prev->path, stdout, '\n');
        }
+       commit_info_destroy(&ci);
        return 1;
  }
  
@@@ -1707,11 -1691,11 +1692,11 @@@ static void emit_other(struct scoreboar
                if (opt & OUTPUT_ANNOTATE_COMPAT) {
                        const char *name;
                        if (opt & OUTPUT_SHOW_EMAIL)
-                               name = ci.author_mail;
+                               name = ci.author_mail.buf;
                        else
-                               name = ci.author;
+                               name = ci.author.buf;
                        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),
                               ent->lno + 1 + cnt);
                } else {
                                const char *name;
                                int pad;
                                if (opt & OUTPUT_SHOW_EMAIL)
-                                       name = ci.author_mail;
+                                       name = ci.author_mail.buf;
                                else
-                                       name = ci.author;
+                                       name = ci.author.buf;
                                pad = longest_author - utf8_strwidth(name);
                                printf(" (%s%*s %10s",
                                       name, pad, "",
                                       format_time(ci.author_time,
-                                                  ci.author_tz,
+                                                  ci.author_tz.buf,
                                                   show_raw_time));
                        }
                        printf(" %*d) ",
  
        if (sb->final_buf_size && cp[-1] != '\n')
                putchar('\n');
+       commit_info_destroy(&ci);
  }
  
  static void output(struct scoreboard *sb, int option)
@@@ -1876,9 -1862,9 +1863,9 @@@ static void find_alignment(struct score
                        suspect->commit->object.flags |= METAINFO_SHOWN;
                        get_commit_info(suspect->commit, &ci, 1);
                        if (*option & OUTPUT_SHOW_EMAIL)
-                               num = utf8_strwidth(ci.author_mail);
+                               num = utf8_strwidth(ci.author_mail.buf);
                        else
-                               num = utf8_strwidth(ci.author);
+                               num = utf8_strwidth(ci.author.buf);
                        if (longest_author < num)
                                longest_author = num;
                }
                        longest_dst_lines = num;
                if (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_digits = decimal_width(longest_dst_lines);
@@@ -2404,7 -2392,6 +2393,7 @@@ int cmd_blame(int argc, const char **ar
        init_revisions(&revs, NULL);
        revs.date_mode = blame_date_mode;
        DIFF_OPT_SET(&revs.diffopt, ALLOW_TEXTCONV);
 +      DIFF_OPT_SET(&revs.diffopt, FOLLOW_RENAMES);
  
        save_commit_buffer = 0;
        dashdash_pos = 0;
                parse_revision_opt(&revs, &ctx, options, blame_opt_usage);
        }
  parse_done:
 +      no_whole_file_rename = !DIFF_OPT_TST(&revs.diffopt, FOLLOW_RENAMES);
 +      DIFF_OPT_CLR(&revs.diffopt, FOLLOW_RENAMES);
        argc = parse_options_end(&ctx);
  
        if (0 < abbrev)
diff --combined builtin/log.c
index 5a4055eb0b0d4965785b6f69dbe92efe251c54dd,16e6520a5d40f1837d850956f899bb5ef5a4c46d..8f0b2e84fef5d1b9c07ea8846c9fbc1318d8d51b
@@@ -22,6 -22,7 +22,7 @@@
  #include "branch.h"
  #include "streaming.h"
  #include "version.h"
+ #include "mailmap.h"
  
  /* Set a default date-time format for git log ("log.date" config variable) */
  static const char *default_date_mode = NULL;
@@@ -30,6 -31,7 +31,7 @@@ static int default_abbrev_commit
  static int default_show_root = 1;
  static int decoration_style;
  static int decoration_given;
+ static int use_mailmap_config;
  static const char *fmt_patch_subject_prefix = "PATCH";
  static const char *fmt_pretty;
  
@@@ -94,16 -96,18 +96,18 @@@ static void cmd_log_init_finish(int arg
                         struct rev_info *rev, struct setup_revision_opt *opt)
  {
        struct userformat_want w;
-       int quiet = 0, source = 0;
+       int quiet = 0, source = 0, mailmap = 0;
  
        const struct option builtin_log_options[] = {
                OPT_BOOLEAN(0, "quiet", &quiet, N_("suppress diff output")),
                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"),
                  PARSE_OPT_OPTARG, decorate_callback},
                OPT_END()
        };
  
+       mailmap = use_mailmap_config;
        argc = parse_options(argc, argv, prefix,
                             builtin_log_options, builtin_log_usage,
                             PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
        if (source)
                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) {
                /*
                 * "log --pretty=raw" is special; ignore UI oriented
@@@ -351,6 -360,11 +360,11 @@@ static int git_log_config(const char *v
        }
        if (!prefixcmp(var, "color.decorate."))
                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)
                return -1;
        return git_diff_ui_config(var, value, cb);
@@@ -678,7 -692,7 +692,7 @@@ static int reopen_stdout(struct commit 
                         struct rev_info *rev, int quiet)
  {
        struct strbuf filename = STRBUF_INIT;
 -      int suffix_len = strlen(fmt_patch_suffix) + 1;
 +      int suffix_len = strlen(rev->patch_suffix) + 1;
  
        if (output_directory) {
                strbuf_addstr(&filename, output_directory);
                        strbuf_addch(&filename, '/');
        }
  
 -      get_patch_filename(commit, subject, rev->nr, fmt_patch_suffix, &filename);
 +      if (rev->numbered_files)
 +              strbuf_addf(&filename, "%d", rev->nr);
 +      else if (commit)
 +              fmt_output_commit(&filename, commit, rev);
 +      else
 +              fmt_output_subject(&filename, subject, rev);
  
        if (!quiet)
                fprintf(realstdout, "%s\n", filename.buf + outdir_offset);
@@@ -778,6 -787,7 +792,6 @@@ static void add_branch_description(stru
  }
  
  static void make_cover_letter(struct rev_info *rev, int use_stdout,
 -                            int numbered, int numbered_files,
                              struct commit *origin,
                              int nr, struct commit **list, struct commit *head,
                              const char *branch_name,
        committer = git_committer_info(0);
  
        if (!use_stdout &&
 -          reopen_stdout(NULL, numbered_files ? NULL : "cover-letter", rev, quiet))
 +          reopen_stdout(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet))
                return;
  
        log_write_email_headers(rev, head, &pp.subject, &pp.after_subject,
@@@ -1020,9 -1030,8 +1034,9 @@@ static char *find_branch_name(struct re
  {
        int i, positive = -1;
        unsigned char branch_sha1[20];
 -      struct strbuf buf = STRBUF_INIT;
 -      const char *branch;
 +      const unsigned char *tip_sha1;
 +      const char *ref;
 +      char *full_ref, *branch = NULL;
  
        for (i = 0; i < rev->cmdline.nr; i++) {
                if (rev->cmdline.rev[i].flags & UNINTERESTING)
                else
                        return NULL;
        }
 -      if (positive < 0)
 +      if (0 <= positive) {
 +              ref = rev->cmdline.rev[positive].name;
 +              tip_sha1 = rev->cmdline.rev[positive].item->sha1;
 +      } else if (!rev->cmdline.nr && rev->pending.nr == 1 &&
 +                 !strcmp(rev->pending.objects[0].name, "HEAD")) {
 +              /*
 +               * No actual ref from command line, but "HEAD" from
 +               * rev->def was added in setup_revisions()
 +               * e.g. format-patch --cover-letter -12
 +               */
 +              ref = "HEAD";
 +              tip_sha1 = rev->pending.objects[0].item->sha1;
 +      } else {
                return NULL;
 -      strbuf_addf(&buf, "refs/heads/%s", rev->cmdline.rev[positive].name);
 -      branch = resolve_ref_unsafe(buf.buf, branch_sha1, 1, NULL);
 -      if (!branch ||
 -          prefixcmp(branch, "refs/heads/") ||
 -          hashcmp(rev->cmdline.rev[positive].item->sha1, branch_sha1))
 -              branch = NULL;
 -      strbuf_release(&buf);
 -      if (branch)
 -              return xstrdup(rev->cmdline.rev[positive].name);
 -      return NULL;
 +      }
 +      if (dwim_ref(ref, strlen(ref), branch_sha1, &full_ref) &&
 +          !prefixcmp(full_ref, "refs/heads/") &&
 +          !hashcmp(tip_sha1, branch_sha1))
 +              branch = xstrdup(full_ref + strlen("refs/heads/"));
 +      free(full_ref);
 +      return branch;
  }
  
  int cmd_format_patch(int argc, const char **argv, const char *prefix)
        int nr = 0, total, i;
        int use_stdout = 0;
        int start_number = -1;
 -      int numbered_files = 0;         /* _just_ numbers */
 +      int just_numbers = 0;
        int ignore_if_in_upstream = 0;
        int cover_letter = 0;
        int boundary_count = 0;
        struct strbuf buf = STRBUF_INIT;
        int use_patch_format = 0;
        int quiet = 0;
 +      int reroll_count = -1;
        char *branch_name = NULL;
        const struct option builtin_format_patch_options[] = {
                { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL,
                            N_("print patches to standard out")),
                OPT_BOOLEAN(0, "cover-letter", &cover_letter,
                            N_("generate a cover letter")),
 -              OPT_BOOLEAN(0, "numbered-files", &numbered_files,
 +              OPT_BOOLEAN(0, "numbered-files", &just_numbers,
                            N_("use simple number sequence for output file names")),
                OPT_STRING(0, "suffix", &fmt_patch_suffix, N_("sfx"),
                            N_("use <sfx> instead of '.patch'")),
                OPT_INTEGER(0, "start-number", &start_number,
                            N_("start numbering patches at <n> instead of 1")),
 +              OPT_INTEGER('v', "reroll-count", &reroll_count,
 +                          N_("mark the series as Nth re-roll")),
                { OPTION_CALLBACK, 0, "subject-prefix", &rev, N_("prefix"),
                            N_("Use [<prefix>] instead of [PATCH]"),
                            PARSE_OPT_NONEG, subject_prefix_callback },
                             PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
                             PARSE_OPT_KEEP_DASHDASH);
  
 +      if (0 < reroll_count) {
 +              struct strbuf sprefix = STRBUF_INIT;
 +              strbuf_addf(&sprefix, "%s v%d",
 +                          rev.subject_prefix, reroll_count);
 +              rev.reroll_count = reroll_count;
 +              rev.subject_prefix = strbuf_detach(&sprefix, NULL);
 +      }
 +
        if (do_signoff) {
                const char *committer;
                const char *endpos;
                const char *msgid = clean_message_id(in_reply_to);
                string_list_append(rev.ref_message_ids, msgid);
        }
 -      rev.numbered_files = numbered_files;
 +      rev.numbered_files = just_numbers;
        rev.patch_suffix = fmt_patch_suffix;
        if (cover_letter) {
                if (thread)
                        gen_message_id(&rev, "cover");
 -              make_cover_letter(&rev, use_stdout, numbered, numbered_files,
 +              make_cover_letter(&rev, use_stdout,
                                  origin, nr, list, head, branch_name, quiet);
                total++;
                start_number--;
                }
  
                if (!use_stdout &&
 -                  reopen_stdout(numbered_files ? NULL : commit, NULL, &rev, quiet))
 +                  reopen_stdout(rev.numbered_files ? NULL : commit, NULL, &rev, quiet))
                        die(_("Failed to create output files"));
                shown = log_tree_commit(&rev, commit);
                free(commit->buffer);
diff --combined builtin/shortlog.c
index 83605143ac0cea68b421dd9a3c056d3a27f66be9,1eeed0ffd8c7b6d052be538a10af39861738c2b2..240bff3efa80b3af087fe1e6c16a75108ffee4de
@@@ -36,52 -36,28 +36,28 @@@ static void insert_one_record(struct sh
        const char *dot3 = log->common_repo_prefix;
        char *buffer, *p;
        struct string_list_item *item;
-       char namebuf[1024];
-       char emailbuf[1024];
-       size_t len;
+       const char *mailbuf, *namebuf;
+       size_t namelen, maillen;
        const char *eol;
-       const char *boemail, *eoemail;
        struct strbuf subject = STRBUF_INIT;
+       struct strbuf namemailbuf = STRBUF_INIT;
+       struct ident_split ident;
  
-       boemail = strchr(author, '<');
-       if (!boemail)
-               return;
-       eoemail = strchr(boemail, '>');
-       if (!eoemail)
+       if (split_ident_line(&ident, author, strlen(author)))
                return;
  
-       /* copy author name to namebuf, to support matching on both name and email */
-       memcpy(namebuf, author, boemail - author);
-       len = boemail - author;
-       while (len > 0 && isspace(namebuf[len-1]))
-               len--;
-       namebuf[len] = 0;
+       namebuf = ident.name_begin;
+       mailbuf = ident.mail_begin;
+       namelen = ident.name_end - ident.name_begin;
+       maillen = ident.mail_end - ident.mail_begin;
  
-       /* copy email name to emailbuf, to allow email replacement as well */
-       memcpy(emailbuf, boemail+1, eoemail - boemail);
-       emailbuf[eoemail - boemail - 1] = 0;
+       map_user(&log->mailmap, &mailbuf, &maillen, &namebuf, &namelen);
+       strbuf_add(&namemailbuf, namebuf, namelen);
  
-       if (!map_user(&log->mailmap, emailbuf, sizeof(emailbuf), namebuf, sizeof(namebuf))) {
-               while (author < boemail && isspace(*author))
-                       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) {
-               size_t room = sizeof(namebuf) - len - 1;
-               int maillen = strlen(emailbuf);
-               snprintf(namebuf + len, room, " <%.*s>", maillen, emailbuf);
-       }
+       if (log->email)
+               strbuf_addf(&namemailbuf, " <%.*s>", (int)maillen, mailbuf);
  
-       item = string_list_insert(&log->list, namebuf);
+       item = string_list_insert(&log->list, namemailbuf.buf);
        if (item->util == NULL)
                item->util = xcalloc(1, sizeof(struct string_list));
  
@@@ -306,8 -282,9 +282,8 @@@ parse_done
  static void add_wrapped_shortlog_msg(struct strbuf *sb, const char *s,
                                     const struct shortlog *log)
  {
 -      int col = strbuf_add_wrapped_text(sb, s, log->in1, log->in2, log->wrap);
 -      if (col != log->wrap)
 -              strbuf_addch(sb, '\n');
 +      strbuf_add_wrapped_text(sb, s, log->in1, log->in2, log->wrap);
 +      strbuf_addch(sb, '\n');
  }
  
  void shortlog_output(struct shortlog *log)
diff --combined commit.h
index 0f469e507db7b8517e4f37107d855b7877176085,7f8f9878d8e10a425da3241f75655cc1213debe1..c16c8a75349f2ef4dba1f3c05528d04750a925da
+++ b/commit.h
@@@ -89,7 -89,7 +89,8 @@@ struct pretty_print_context 
        char *notes_message;
        struct reflog_walk_info *reflog_info;
        const char *output_encoding;
+       struct string_list *mailmap;
 +      int color;
  };
  
  struct userformat_want {
diff --combined log-tree.c
index f8487f8a8a7694ce09f1b079c701bceabf688b20,92254fd493e38a7d806e147b85853360b5ccff96..5dc45c4812bdfd0d7a6b71d529eaa37df8178186
@@@ -299,34 -299,26 +299,34 @@@ static unsigned int digits_in_number(un
        return result;
  }
  
 -void get_patch_filename(struct commit *commit, const char *subject, int nr,
 -                      const char *suffix, struct strbuf *buf)
 +void fmt_output_subject(struct strbuf *filename,
 +                      const char *subject,
 +                      struct rev_info *info)
  {
 -      int suffix_len = strlen(suffix) + 1;
 -      int start_len = buf->len;
 -
 -      strbuf_addf(buf, commit || subject ? "%04d-" : "%d", nr);
 -      if (commit || subject) {
 -              int max_len = start_len + FORMAT_PATCH_NAME_MAX - suffix_len;
 -              struct pretty_print_context ctx = {0};
 -
 -              if (subject)
 -                      strbuf_addstr(buf, subject);
 -              else if (commit)
 -                      format_commit_message(commit, "%f", buf, &ctx);
 -
 -              if (max_len < buf->len)
 -                      strbuf_setlen(buf, max_len);
 -              strbuf_addstr(buf, suffix);
 -      }
 +      const char *suffix = info->patch_suffix;
 +      int nr = info->nr;
 +      int start_len = filename->len;
 +      int max_len = start_len + FORMAT_PATCH_NAME_MAX - (strlen(suffix) + 1);
 +
 +      if (0 < info->reroll_count)
 +              strbuf_addf(filename, "v%d-", info->reroll_count);
 +      strbuf_addf(filename, "%04d-%s", nr, subject);
 +
 +      if (max_len < filename->len)
 +              strbuf_setlen(filename, max_len);
 +      strbuf_addstr(filename, suffix);
 +}
 +
 +void fmt_output_commit(struct strbuf *filename,
 +                     struct commit *commit,
 +                     struct rev_info *info)
 +{
 +      struct pretty_print_context ctx = {0};
 +      struct strbuf subject = STRBUF_INIT;
 +
 +      format_commit_message(commit, "%f", &subject, &ctx);
 +      fmt_output_subject(filename, subject.buf, info);
 +      strbuf_release(&subject);
  }
  
  void log_write_email_headers(struct rev_info *opt, struct commit *commit,
                         mime_boundary_leader, opt->mime_boundary);
                extra_headers = subject_buffer;
  
 -              get_patch_filename(opt->numbered_files ? NULL : commit, NULL,
 -                                 opt->nr, opt->patch_suffix, &filename);
 +              if (opt->numbered_files)
 +                      strbuf_addf(&filename, "%d", opt->nr);
 +              else
 +                      fmt_output_commit(&filename, commit, opt);
                snprintf(buffer, sizeof(buffer) - 1,
                         "\n--%s%s\n"
                         "Content-Type: text/x-patch;"
@@@ -681,7 -671,7 +681,8 @@@ void show_log(struct rev_info *opt
        ctx.preserve_subject = opt->preserve_subject;
        ctx.reflog_info = opt->reflog_info;
        ctx.fmt = opt->commit_format;
+       ctx.mailmap = opt->mailmap;
 +      ctx.color = opt->diffopt.use_color;
        pretty_print_commit(&ctx, commit, &msgbuf);
  
        if (opt->add_signoff)
diff --combined mailmap.c
index b16542febec14ed86fd7ef21ba19899d08c95a64,743dbdae2f358665029d025faa7304f5d79dbbf6..2a7b36628cb5623eb0ffb95bfb54e52a6dd300f4
+++ b/mailmap.c
@@@ -10,7 -10,6 +10,7 @@@ static inline void debug_mm(const char 
  #endif
  
  const char *git_mailmap_file;
 +const char *git_mailmap_blob;
  
  struct mailmap_info {
        char *name;
@@@ -130,119 -129,55 +130,120 @@@ static char *parse_name_and_email(char 
        return (*right == '\0' ? NULL : right);
  }
  
 -static int read_single_mailmap(struct string_list *map, const char *filename, char **repo_abbrev)
 +static void read_mailmap_line(struct string_list *map, char *buffer,
 +                            char **repo_abbrev)
  {
 -      char buffer[1024];
 -      FILE *f = (filename == NULL ? NULL : fopen(filename, "r"));
 -
 -      if (f == NULL)
 -              return 1;
 -      while (fgets(buffer, sizeof(buffer), f) != NULL) {
 -              char *name1 = NULL, *email1 = NULL, *name2 = NULL, *email2 = NULL;
 -              if (buffer[0] == '#') {
 -                      static const char abbrev[] = "# repo-abbrev:";
 -                      int abblen = sizeof(abbrev) - 1;
 -                      int len = strlen(buffer);
 -
 -                      if (!repo_abbrev)
 -                              continue;
 -
 -                      if (len && buffer[len - 1] == '\n')
 -                              buffer[--len] = 0;
 -                      if (!strncmp(buffer, abbrev, abblen)) {
 -                              char *cp;
 -
 -                              if (repo_abbrev)
 -                                      free(*repo_abbrev);
 -                              *repo_abbrev = xmalloc(len);
 -
 -                              for (cp = buffer + abblen; isspace(*cp); cp++)
 -                                      ; /* nothing */
 -                              strcpy(*repo_abbrev, cp);
 -                      }
 -                      continue;
 +      char *name1 = NULL, *email1 = NULL, *name2 = NULL, *email2 = NULL;
 +      if (buffer[0] == '#') {
 +              static const char abbrev[] = "# repo-abbrev:";
 +              int abblen = sizeof(abbrev) - 1;
 +              int len = strlen(buffer);
 +
 +              if (!repo_abbrev)
 +                      return;
 +
 +              if (len && buffer[len - 1] == '\n')
 +                      buffer[--len] = 0;
 +              if (!strncmp(buffer, abbrev, abblen)) {
 +                      char *cp;
 +
 +                      if (repo_abbrev)
 +                              free(*repo_abbrev);
 +                      *repo_abbrev = xmalloc(len);
 +
 +                      for (cp = buffer + abblen; isspace(*cp); cp++)
 +                              ; /* nothing */
 +                      strcpy(*repo_abbrev, cp);
                }
 -              if ((name2 = parse_name_and_email(buffer, &name1, &email1, 0)) != NULL)
 -                      parse_name_and_email(name2, &name2, &email2, 1);
 +              return;
 +      }
 +      if ((name2 = parse_name_and_email(buffer, &name1, &email1, 0)) != NULL)
 +              parse_name_and_email(name2, &name2, &email2, 1);
 +
 +      if (email1)
 +              add_mapping(map, name1, email1, name2, email2);
 +}
  
 -              if (email1)
 -                      add_mapping(map, name1, email1, name2, email2);
 +static int read_mailmap_file(struct string_list *map, const char *filename,
 +                           char **repo_abbrev)
 +{
 +      char buffer[1024];
 +      FILE *f;
 +
 +      if (!filename)
 +              return 0;
 +
 +      f = fopen(filename, "r");
 +      if (!f) {
 +              if (errno == ENOENT)
 +                      return 0;
 +              return error("unable to open mailmap at %s: %s",
 +                           filename, strerror(errno));
        }
 +
 +      while (fgets(buffer, sizeof(buffer), f) != NULL)
 +              read_mailmap_line(map, buffer, repo_abbrev);
        fclose(f);
        return 0;
  }
  
 +static void read_mailmap_buf(struct string_list *map,
 +                           const char *buf, unsigned long len,
 +                           char **repo_abbrev)
 +{
 +      while (len) {
 +              const char *end = strchrnul(buf, '\n');
 +              unsigned long linelen = end - buf + 1;
 +              char *line = xmemdupz(buf, linelen);
 +
 +              read_mailmap_line(map, line, repo_abbrev);
 +
 +              free(line);
 +              buf += linelen;
 +              len -= linelen;
 +      }
 +}
 +
 +static int read_mailmap_blob(struct string_list *map,
 +                           const char *name,
 +                           char **repo_abbrev)
 +{
 +      unsigned char sha1[20];
 +      char *buf;
 +      unsigned long size;
 +      enum object_type type;
 +
 +      if (!name)
 +              return 0;
 +      if (get_sha1(name, sha1) < 0)
 +              return 0;
 +
 +      buf = read_sha1_file(sha1, &type, &size);
 +      if (!buf)
 +              return error("unable to read mailmap object at %s", name);
 +      if (type != OBJ_BLOB)
 +              return error("mailmap is not a blob: %s", name);
 +
 +      read_mailmap_buf(map, buf, size, repo_abbrev);
 +
 +      free(buf);
 +      return 0;
 +}
 +
  int read_mailmap(struct string_list *map, char **repo_abbrev)
  {
 +      int err = 0;
 +
        map->strdup_strings = 1;
 -      /* each failure returns 1, so >1 means both calls failed */
 -      return read_single_mailmap(map, ".mailmap", repo_abbrev) +
 -             read_single_mailmap(map, git_mailmap_file, repo_abbrev) > 1;
+       map->cmp = strcasecmp;
 +
 +      if (!git_mailmap_blob && is_bare_repository())
 +              git_mailmap_blob = "HEAD:.mailmap";
 +
 +      err |= read_mailmap_file(map, ".mailmap", repo_abbrev);
 +      err |= read_mailmap_blob(map, git_mailmap_blob, repo_abbrev);
 +      err |= read_mailmap_file(map, git_mailmap_file, repo_abbrev);
 +      return err;
  }
  
  void clear_mailmap(struct string_list *map)
        debug_mm("mailmap: cleared\n");
  }
  
+ /*
+  * 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,
-            char *email, int maxlen_email, char *name, int maxlen_name)
+                        const char **email, size_t *emaillen,
+                        const char **name, size_t *namelen)
  {
-       char *end_of_email;
        struct string_list_item *item;
        struct mailmap_entry *me;
-       char buf[1024], *mailbuf;
-       int i;
-       /* figure out space requirement for email */
-       end_of_email = strchr(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 */
-       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);
+       debug_mm("map_user: map '%.*s' <%.*s>\n",
+                *name, *namelen, *emaillen, *email);
+       item = lookup_prefix(map, *email, *emaillen);
        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(&me->namemap, name);
+                       struct string_list_item *subitem;
+                       subitem = lookup_prefix(&me->namemap, *name, *namelen);
                        if (subitem)
                                item = subitem;
                }
        }
-       if (mailbuf != buf)
-               free(mailbuf);
        if (item != NULL) {
                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");
                        return 0;
                }
-               if (maxlen_email && mi->email)
-                       strlcpy(email, mi->email, maxlen_email);
-               else
-                       *end_of_email = '\0';
-               if (maxlen_name && mi->name)
-                       strlcpy(name, mi->name, maxlen_name);
-               debug_mm("map_user:  to '%s' <%s>\n", name, mi->email ? mi->email : "");
+               if (mi->email) {
+                               *email = mi->email;
+                               *emaillen = strlen(*email);
+               }
+               if (mi->name) {
+                               *name = mi->name;
+                               *namelen = strlen(*name);
+               }
+               debug_mm("map_user:  to '%.*s' <.*%s>\n", *namelen, *name,
+                                *emaillen, *email);
                return 1;
        }
        debug_mm("map_user:  --\n");
diff --combined pretty.c
index 01795de68704f9690aea2b9c36e557b31a151ca6,622275c70b5fb57d08ede37b25b774baee5b72e6..07fc0628656c5e37eaf04c969a7273455a391f8d
+++ b/pretty.c
@@@ -387,56 -387,79 +387,79 @@@ void pp_user_info(const struct pretty_p
                  const char *what, struct strbuf *sb,
                  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 */
-       char *date;
-       int namelen;
        unsigned long time;
        int tz;
  
        if (pp->fmt == CMIT_FMT_ONELINE)
                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;
-       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);
  
        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: ");
-               if (needs_rfc2047_encoding(line, display_name_length, RFC2047_ADDRESS)) {
-                       add_rfc2047(sb, line, display_name_length,
-                                               encoding, RFC2047_ADDRESS);
+               if (needs_rfc2047_encoding(name.buf, name.len, RFC2047_ADDRESS)) {
+                       add_rfc2047(sb, name.buf, name.len,
+                                   encoding, RFC2047_ADDRESS);
                        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;
-                       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,
                                                        -6, 1, max_length);
                        strbuf_release(&quoted);
                } else {
-                       strbuf_add_wrapped_bytes(sb, line, display_name_length,
-                                                       -6, 1, max_length);
+                       strbuf_add_wrapped_bytes(sb, name.buf, name.len,
+                                                -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');
-                       if (!isspace(name_tail[0]))
-                               strbuf_addch(sb, ' ');
-               }
-               strbuf_add(sb, name_tail, namelen - display_name_length);
-               strbuf_addch(sb, '\n');
+               strbuf_addf(sb, " <%s>\n", mail.buf);
        } 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,
-                             "    ", namelen, line);
+                             "    ", name.buf, mail.buf);
        }
+       strbuf_release(&mail);
+       strbuf_release(&name);
        switch (pp->fmt) {
        case CMIT_FMT_MEDIUM:
                strbuf_addf(sb, "Date:   %s\n", show_date(time, tz, pp->date_mode));
@@@ -567,7 -590,7 +590,7 @@@ char *logmsg_reencode(const struct comm
        char *encoding;
        char *out;
  
 -      if (!*output_encoding)
 +      if (!output_encoding || !*output_encoding)
                return NULL;
        encoding = get_header(commit, "encoding");
        use_encoding = encoding ? encoding : utf8;
        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;
        if (!mail_map) {
@@@ -603,36 -627,26 +627,26 @@@ static size_t format_person_part(struc
        const int placeholder_len = 2;
        int tz;
        unsigned long date = 0;
-       char person_name[1024];
-       char person_mail[1024];
        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)
                goto skip;
  
-       name_start = s.name_begin;
-       name_end = s.name_end;
-       mail_start = s.mail_begin;
-       mail_end = s.mail_end;
-       if (part == 'N' || part == 'E') { /* mailmap lookup */
-               snprintf(person_name, sizeof(person_name), "%.*s",
-                        (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);
-       }
+       name = s.name_begin;
+       namelen = s.name_end - s.name_begin;
+       mail = s.mail_begin;
+       maillen = s.mail_end - s.mail_begin;
+       if (part == 'N' || part == 'E') /* mailmap lookup */
+               mailmap_name(&mail, &maillen, &name, &namelen);
        if (part == 'n' || part == 'N') {       /* name */
-               strbuf_add(sb, name_start, name_end-name_start);
+               strbuf_add(sb, name, namelen);
                return placeholder_len;
        }
        if (part == 'e' || part == 'E') {       /* email */
-               strbuf_add(sb, mail_start, mail_end-mail_start);
+               strbuf_add(sb, mail, maillen);
                return placeholder_len;
        }
  
@@@ -960,19 -974,12 +974,19 @@@ static size_t format_commit_one(struct 
        switch (placeholder[0]) {
        case 'C':
                if (placeholder[1] == '(') {
 -                      const char *end = strchr(placeholder + 2, ')');
 +                      const char *begin = placeholder + 2;
 +                      const char *end = strchr(begin, ')');
                        char color[COLOR_MAXLEN];
 +
                        if (!end)
                                return 0;
 -                      color_parse_mem(placeholder + 2,
 -                                      end - (placeholder + 2),
 +                      if (!prefixcmp(begin, "auto,")) {
 +                              if (!want_color(c->pretty_ctx->color))
 +                                      return end - placeholder + 1;
 +                              begin += 5;
 +                      }
 +                      color_parse_mem(begin,
 +                                      end - begin,
                                        "--pretty format", color);
                        strbuf_addstr(sb, color);
                        return end - placeholder + 1;
@@@ -1257,15 -1264,23 +1271,15 @@@ void format_commit_message(const struc
                           const struct pretty_print_context *pretty_ctx)
  {
        struct format_commit_context context;
 -      static const char utf8[] = "UTF-8";
        const char *output_enc = pretty_ctx->output_encoding;
  
        memset(&context, 0, sizeof(context));
        context.commit = commit;
        context.pretty_ctx = pretty_ctx;
        context.wrap_start = sb->len;
 -      context.message = commit->buffer;
 -      if (output_enc) {
 -              char *enc = get_header(commit, "encoding");
 -              if (strcmp(enc ? enc : utf8, output_enc)) {
 -                      context.message = logmsg_reencode(commit, output_enc);
 -                      if (!context.message)
 -                              context.message = commit->buffer;
 -              }
 -              free(enc);
 -      }
 +      context.message = logmsg_reencode(commit, output_enc);
 +      if (!context.message)
 +              context.message = commit->buffer;
  
        strbuf_expand(sb, format, format_commit_item, &context);
        rewrap_message_tail(sb, &context, 0, 0, 0);
@@@ -1301,7 -1316,7 +1315,7 @@@ static void pp_header(const struct pret
                        continue;
                }
  
 -              if (!memcmp(line, "parent ", 7)) {
 +              if (!prefixcmp(line, "parent ")) {
                        if (linelen != 48)
                                die("bad parent line in commit");
                        continue;
                 * FULL shows both authors but not dates.
                 * FULLER shows both authors and dates.
                 */
 -              if (!memcmp(line, "author ", 7)) {
 +              if (!prefixcmp(line, "author ")) {
                        strbuf_grow(sb, linelen + 80);
                        pp_user_info(pp, "Author", sb, line + 7, encoding);
                }
 -              if (!memcmp(line, "committer ", 10) &&
 +              if (!prefixcmp(line, "committer ") &&
                    (pp->fmt == CMIT_FMT_FULL || pp->fmt == CMIT_FMT_FULLER)) {
                        strbuf_grow(sb, linelen + 80);
                        pp_user_info(pp, "Commit", sb, line + 10, encoding);
diff --combined revision.h
index a395c3639a30d4d0d55036a5fec316e4cdb9546f,83a79f5815f7d868dc7d36f61d9890625251e6cc..5da09ee3efa976b503cba5d13e347aad0f6c764c
@@@ -135,7 -135,6 +135,7 @@@ struct rev_info 
        const char      *mime_boundary;
        const char      *patch_suffix;
        int             numbered_files;
 +      int             reroll_count;
        char            *message_id;
        struct string_list *ref_message_ids;
        const char      *add_signoff;
        const char      *subject_prefix;
        int             no_inline;
        int             show_log_size;
+       struct string_list *mailmap;
  
        /* Filter by commit log message */
        struct grep_opt grep_filter;
diff --combined string-list.c
index 480173fe6dbee5428e3b135ff27ba7551c527ad8,6f3d8cf091d32bef43564aecc74d84e9fab3a99d..aabb25ef4c1040dde015d6ac37b8213d9bc958ea
@@@ -7,10 -7,11 +7,11 @@@ static int get_entry_index(const struc
                int *exact_match)
  {
        int left = -1, right = list->nr;
+       compare_strings_fn cmp = list->cmp ? list->cmp : strcmp;
  
        while (left + 1 < right) {
                int middle = (left + right) / 2;
-               int compare = strcmp(string, list->items[middle].string);
+               int compare = cmp(string, list->items[middle].string);
                if (compare < 0)
                        right = middle;
                else if (compare > 0)
@@@ -96,8 -97,9 +97,9 @@@ void string_list_remove_duplicates(stru
  {
        if (list->nr > 1) {
                int src, dst;
+               compare_strings_fn cmp = list->cmp ? list->cmp : strcmp;
                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)
                                        free(list->items[src].string);
                                if (free_util)
@@@ -145,6 -147,26 +147,6 @@@ void string_list_remove_empty_items(str
        filter_string_list(list, free_util, item_is_not_empty, NULL);
  }
  
 -char *string_list_longest_prefix(const struct string_list *prefixes,
 -                               const char *string)
 -{
 -      int i, max_len = -1;
 -      char *retval = NULL;
 -
 -      for (i = 0; i < prefixes->nr; i++) {
 -              char *prefix = prefixes->items[i].string;
 -              if (!prefixcmp(string, prefix)) {
 -                      int len = strlen(prefix);
 -                      if (len > max_len) {
 -                              retval = prefix;
 -                              max_len = len;
 -                      }
 -              }
 -      }
 -
 -      return retval;
 -}
 -
  void string_list_clear(struct string_list *list, int free_util)
  {
        if (list->items) {
@@@ -210,15 -232,20 +212,20 @@@ struct string_list_item *string_list_ap
                        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)
  {
        const struct string_list_item *one = a;
        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)
  {
+       compare_for_qsort = list->cmp ? list->cmp : strcmp;
        qsort(list->items, list->nr, sizeof(*list->items), cmp_items);
  }
  
@@@ -226,8 -253,10 +233,10 @@@ struct string_list_item *unsorted_strin
                                                     const char *string)
  {
        int i;
+       compare_strings_fn cmp = list->cmp ? list->cmp : strcmp;
        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 NULL;
  }
diff --combined string-list.h
index db1284861adb707b675714edc1ecacf9357f6661,446e79e4255791a6855b659b8cf171d9e27323b7..de6769c92dd109791c872b8d320c2caeee9f2b8d
@@@ -5,10 -5,14 +5,14 @@@ struct string_list_item 
        char *string;
        void *util;
  };
+ typedef int (*compare_strings_fn)(const char *, const char *);
  struct string_list {
        struct string_list_item *items;
        unsigned int nr, alloc;
        unsigned int strdup_strings:1;
+       compare_strings_fn cmp; /* NULL uses strcmp() */
  };
  
  #define STRING_LIST_INIT_NODUP { NULL, 0, 0, 0 }
@@@ -45,6 -49,15 +49,6 @@@ void filter_string_list(struct string_l
   */
  void string_list_remove_empty_items(struct string_list *list, int free_util);
  
 -/*
 - * Return the longest string in prefixes that is a prefix (in the
 - * sense of prefixcmp()) of string, or NULL if no such prefix exists.
 - * This function does not require the string_list to be sorted (it
 - * does a linear search).
 - */
 -char *string_list_longest_prefix(const struct string_list *prefixes, const char *string);
 -
 -
  /* Use these functions only on sorted lists: */
  int string_list_has_string(const struct string_list *list, const char *string);
  int string_list_find_insert_index(const struct string_list *list, const char *string,
diff --combined t/t4203-mailmap.sh
index aae30d97b1a0b95f9cae5c2659f77cf2cb9c3947,7d4d31c830cbdf1bb300f563863c61494a783989..842b7549ec3ed6b1fa268a96d085048d2f0b7793
@@@ -149,104 -149,6 +149,104 @@@ test_expect_success 'No mailmap files, 
        test_cmp expect actual
  '
  
 +test_expect_success 'setup mailmap blob tests' '
 +      git checkout -b map &&
 +      test_when_finished "git checkout master" &&
 +      cat >just-bugs <<-\EOF &&
 +      Blob Guy <bugs@company.xx>
 +      EOF
 +      cat >both <<-\EOF &&
 +      Blob Guy <author@example.com>
 +      Blob Guy <bugs@company.xx>
 +      EOF
 +      git add just-bugs both &&
 +      git commit -m "my mailmaps" &&
 +      echo "Repo Guy <author@example.com>" >.mailmap &&
 +      echo "Internal Guy <author@example.com>" >internal.map
 +'
 +
 +test_expect_success 'mailmap.blob set' '
 +      cat >expect <<-\EOF &&
 +      Blob Guy (1):
 +            second
 +
 +      Repo Guy (1):
 +            initial
 +
 +      EOF
 +      git -c mailmap.blob=map:just-bugs shortlog HEAD >actual &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'mailmap.blob overrides .mailmap' '
 +      cat >expect <<-\EOF &&
 +      Blob Guy (2):
 +            initial
 +            second
 +
 +      EOF
 +      git -c mailmap.blob=map:both shortlog HEAD >actual &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'mailmap.file overrides mailmap.blob' '
 +      cat >expect <<-\EOF &&
 +      Blob Guy (1):
 +            second
 +
 +      Internal Guy (1):
 +            initial
 +
 +      EOF
 +      git \
 +        -c mailmap.blob=map:both \
 +        -c mailmap.file=internal.map \
 +        shortlog HEAD >actual &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'mailmap.blob can be missing' '
 +      cat >expect <<-\EOF &&
 +      Repo Guy (1):
 +            initial
 +
 +      nick1 (1):
 +            second
 +
 +      EOF
 +      git -c mailmap.blob=map:nonexistent shortlog HEAD >actual &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'mailmap.blob defaults to off in non-bare repo' '
 +      git init non-bare &&
 +      (
 +              cd non-bare &&
 +              test_commit one .mailmap "Fake Name <author@example.com>" &&
 +              echo "     1    Fake Name" >expect &&
 +              git shortlog -ns HEAD >actual &&
 +              test_cmp expect actual &&
 +              rm .mailmap &&
 +              echo "     1    A U Thor" >expect &&
 +              git shortlog -ns HEAD >actual &&
 +              test_cmp expect actual
 +      )
 +'
 +
 +test_expect_success 'mailmap.blob defaults to HEAD:.mailmap in bare repo' '
 +      git clone --bare non-bare bare &&
 +      (
 +              cd bare &&
 +              echo "     1    Fake Name" >expect &&
 +              git shortlog -ns HEAD >actual &&
 +              test_cmp expect actual
 +      )
 +'
 +
 +test_expect_success 'cleanup after mailmap.blob tests' '
 +      rm -f .mailmap
 +'
 +
  # Extended mailmap configurations should give us the following output for shortlog
  cat >expect <<\EOF
  A U Thor <author@example.com> (1):
@@@ -337,6 -239,62 +337,62 @@@ test_expect_success 'Log output (comple
        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
  cat >expect <<\EOF
  ^OBJI (A U Thor     DATE 1) one