{"thread":{"id":"17653","subject":"[PATCH v5 1/5] Add mailmap.file as configurational option for mailmap location","startedAt":"2009-02-08T14:34:26Z","lastAt":"2009-02-08T14:56:06Z","messageCount":7,"participants":["Marius Storm-Olsen"],"isPatch":true,"patchVersion":5,"patchTotal":5},"messages":[{"id":"103748","messageId":"cover.1234102794.git.marius@trolltech.com","threadId":"17653","inReplyTo":null,"subject":"[PATCH v5 0/5] Extend mailmap functionality","fromName":"Marius Storm-Olsen","fromEmail":"marius@trolltech.com","sentAt":"2009-02-08T14:34:26Z","receivedAt":"2009-02-08T14:34:26Z","isPatch":true,"sender":{"key":"marius@trolltech.com","avatar":"https://gravatar.com/avatar/a40071d8f651862c6ab10bd7996f0ad84d94f06c0399de9e3fa4f06beb390a71?d=mp&s=160"},"body":"  v5:\n  ---\n  * Rename log.mailmap variable to mailmap.file\n  * Fix mailmap tests using shortlog, by adding HEAD\n  * Factor out the mailmap documentation, and include it from both\n    git-shortlog.txt and git-blame.txt, since they both use mailmap\n    directly. Link to these from pretty-format.txt, for all commands\n    using the pretty option.\n  v4:\n  ---\n  * Refactored out name and email parsing into separate function\n  * Added support for only email replacement (<new> <old> construct)\n  * Added \"static\" to functions local to compile unit only\n  * Fixed C++ style comment to bash style comment in commit message\n  * Added more air in the commit messages\n  * Rebased ontop of latest master\n  v3:\n  ---\n  * Make log.mailmap augment repo \"/.mailmap\" rather than override\n  * Remove second argument of read_mailmap(<map>, <file>, <abbrev>);\n  * Wrap commit messages within column 70\n  v2:\n  ---\n  * Folded in documentation fixup from patch 4 into patch 3.\n\n\nThis patch series extends the mailmap functionality to:\n  1) Allow the mailmap file in any location (also outside repo)\n  2) Enable mailmap to match on both Name and Email\n\nSo, why would this be a good thing?\n\n2) Lets you replace both name and email of an author/committer, based\non a name and/or email, _and_ replace email only, based on old email.\nSo, should you have done commits with faulty address, or if an old\nemail simply isn't valid anymore, you can add a mapping for that to\nreplace it. So, the old style mapping is\n    Proper Name <commit@email.xx>\n\nwhile this patch series adds support for\n    <proper@email.xx> <commit@email.xx>\n    Proper Name <proper@email.xx> <commit@email.xx>\n    Proper Name <proper@email.xx> Commit Name <commit@email.xx>\n\n1) Lets you keep a private mailmap file, which is not distributed with\nyour repository.\n\n\nThis extended mapping is necessary when a company wants to have their\nrepositories open to the public, but needs to protect the identities\nof the developers. It enables you to only show nicks and standardized\nemails, like 'Dev123 <bugs@company.xx>' in the public repo, but by\nusing an private mailmap file, map the name back to\n'John Doe <john.doe@company.xx>' inside the company.\n\n\nPatch serie applies cleanly on master branch (88ccb9f9), and test run\nshows no regressions.\n\n\nMarius Storm-Olsen (5):\n  Add mailmap.file as configurational option for mailmap location\n  Add find_insert_index, insert_at_index and clear_func functions to\n    string_list\n  Add map_user() and clear_mailmap() to mailmap\n  Change current mailmap usage to do matching on both name and email of\n    author/committer.\n  Move mailmap documentation into separate file\n\n Documentation/config.txt         |    8 ++\n Documentation/git-blame.txt      |    6 +\n Documentation/git-shortlog.txt   |   49 ++-------\n Documentation/mailmap.txt        |   75 +++++++++++++\n Documentation/pretty-formats.txt |    6 +-\n builtin-blame.c                  |   52 ++++++----\n builtin-shortlog.c               |   25 ++++-\n cache.h                          |    1 +\n config.c                         |   12 ++\n mailmap.c                        |  208 +++++++++++++++++++++++++++++++------\n mailmap.h                        |    6 +-\n pretty.c                         |   59 ++++++-----\n string-list.c                    |   43 +++++++-\n string-list.h                    |    9 ++\n t/t4203-mailmap.sh               |  215 ++++++++++++++++++++++++++++++++++++++\n 15 files changed, 644 insertions(+), 130 deletions(-)\n create mode 100644 Documentation/mailmap.txt\n create mode 100755 t/t4203-mailmap.sh\n"},{"id":"103747","messageId":"3db7411da93f5ae4de5247170d4b821e7b0ed88a.1234102794.git.marius@trolltech.com","threadId":"17653","inReplyTo":"cover.1234102794.git.marius@trolltech.com","subject":"[PATCH v5 1/5] Add mailmap.file as configurational option for mailmap location","fromName":"Marius Storm-Olsen","fromEmail":"marius@trolltech.com","sentAt":"2009-02-08T14:34:27Z","receivedAt":"2009-02-08T14:34:27Z","isPatch":true,"sender":{"key":"marius@trolltech.com","avatar":"https://gravatar.com/avatar/a40071d8f651862c6ab10bd7996f0ad84d94f06c0399de9e3fa4f06beb390a71?d=mp&s=160"},"body":"This allows us to augment the repo mailmap file, and to use\nmailmap files elsewhere than the repository root. Meaning\nthat the entries in mailmap.file will override the entries\nin \"./.mailmap\", should they match.\n\nSigned-off-by: Marius Storm-Olsen <marius@trolltech.com>\n---\n Documentation/config.txt       |    8 +++\n Documentation/git-shortlog.txt |    3 +-\n builtin-blame.c                |    2 +-\n builtin-shortlog.c             |    3 +-\n cache.h                        |    1 +\n config.c                       |   12 ++++\n mailmap.c                      |   12 ++++-\n mailmap.h                      |    2 +-\n pretty.c                       |    2 +-\n t/t4203-mailmap.sh             |  109 ++++++++++++++++++++++++++++++++++++++++\n 10 files changed, 147 insertions(+), 7 deletions(-)\n create mode 100755 t/t4203-mailmap.sh\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 1dd18c9..3117d65 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1032,6 +1032,14 @@ log.showroot::\n \tTools like linkgit:git-log[1] or linkgit:git-whatchanged[1], which\n \tnormally hide the root commit will now show it. True by default.\n \n+mailmap.file::\n+\tThe location of an augmenting mailmap file. The default\n+\tmailmap, located in the root of the repository, is loaded\n+\tfirst, then the mailmap file pointed to by this variable.\n+\tThe location of the mailmap file may be in a repository\n+\tsubdirectory, or somewhere outside of the repository itself.\n+\tSee linkgit:git-shortlog[1] and linkgit:git-blame[1].\n+\n man.viewer::\n \tSpecify the programs that may be used to display help in the\n \t'man' format. See linkgit:git-help[1].\ndiff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt\nindex 498bd28..66b6045 100644\n--- a/Documentation/git-shortlog.txt\n+++ b/Documentation/git-shortlog.txt\n@@ -48,7 +48,8 @@ OPTIONS\n FILES\n -----\n \n-If a file `.mailmap` exists at the toplevel of the repository,\n+If a file `.mailmap` exists at the toplevel of the repository, or at the\n+location pointed to by the log.mailmap configuration option,\n it is used to map an author email address to a canonical real name. This\n can be used to coalesce together commits by the same person where their\n name was spelled differently (whether with the same email address or\ndiff --git a/builtin-blame.c b/builtin-blame.c\nindex 9b9f544..9cfa090 100644\n--- a/builtin-blame.c\n+++ b/builtin-blame.c\n@@ -2396,7 +2396,7 @@ parse_done:\n \t\tdie(\"reading graft file %s failed: %s\",\n \t\t    revs_file, strerror(errno));\n \n-\tread_mailmap(&mailmap, \".mailmap\", NULL);\n+\tread_mailmap(&mailmap, NULL);\n \n \tif (!incremental)\n \t\tsetup_pager();\ndiff --git a/builtin-shortlog.c b/builtin-shortlog.c\nindex 5f9f3f0..314b6bc 100644\n--- a/builtin-shortlog.c\n+++ b/builtin-shortlog.c\n@@ -219,7 +219,7 @@ void shortlog_init(struct shortlog *log)\n {\n \tmemset(log, 0, sizeof(*log));\n \n-\tread_mailmap(&log->mailmap, \".mailmap\", &log->common_repo_prefix);\n+\tread_mailmap(&log->mailmap, &log->common_repo_prefix);\n \n \tlog->list.strdup_strings = 1;\n \tlog->wrap = DEFAULT_WRAPLEN;\n@@ -248,6 +248,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)\n \tstruct parse_opt_ctx_t ctx;\n \n \tprefix = setup_git_directory_gently(&nongit);\n+\tgit_config(git_default_config, NULL);\n \tshortlog_init(&log);\n \tinit_revisions(&rev, prefix);\n \tparse_options_start(&ctx, argc, argv, PARSE_OPT_KEEP_DASHDASH |\ndiff --git a/cache.h b/cache.h\nindex 2d889de..87d9d8e 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -870,6 +870,7 @@ extern int user_ident_explicitly_given;\n \n extern const char *git_commit_encoding;\n extern const char *git_log_output_encoding;\n+extern const char *git_mailmap_file;\n \n /* IO helper functions */\n extern void maybe_flush_or_die(FILE *, const char *);\ndiff --git a/config.c b/config.c\nindex e5d5b4b..7dc1b0f 100644\n--- a/config.c\n+++ b/config.c\n@@ -570,6 +570,15 @@ static int git_default_branch_config(const char *var, const char *value)\n \treturn 0;\n }\n \n+static int git_default_mailmap_config(const char *var, const char *value)\n+{\n+\tif (!strcmp(var, \"mailmap.file\"))\n+\t\treturn git_config_string(&git_mailmap_file, var, value);\n+\n+\t/* Add other config variables here and to Documentation/config.txt. */\n+\treturn 0;\n+}\n+\n int git_default_config(const char *var, const char *value, void *dummy)\n {\n \tif (!prefixcmp(var, \"core.\"))\n@@ -584,6 +593,9 @@ int git_default_config(const char *var, const char *value, void *dummy)\n \tif (!prefixcmp(var, \"branch.\"))\n \t\treturn git_default_branch_config(var, value);\n \n+\tif (!prefixcmp(var, \"mailmap.\"))\n+\t\treturn git_default_mailmap_config(var, value);\n+\n \tif (!strcmp(var, \"pager.color\") || !strcmp(var, \"color.pager\")) {\n \t\tpager_use_color = git_config_bool(var,value);\n \t\treturn 0;\ndiff --git a/mailmap.c b/mailmap.c\nindex 88fc6f3..d006dad 100644\n--- a/mailmap.c\n+++ b/mailmap.c\n@@ -2,10 +2,11 @@\n #include \"string-list.h\"\n #include \"mailmap.h\"\n \n-int read_mailmap(struct string_list *map, const char *filename, char **repo_abbrev)\n+const char *git_mailmap_file;\n+static int read_single_mailmap(struct string_list *map, const char *filename, char **repo_abbrev)\n {\n \tchar buffer[1024];\n-\tFILE *f = fopen(filename, \"r\");\n+\tFILE *f = (filename == NULL ? NULL : fopen(filename, \"r\"));\n \n \tif (f == NULL)\n \t\treturn 1;\n@@ -60,6 +61,13 @@ int read_mailmap(struct string_list *map, const char *filename, char **repo_abbr\n \treturn 0;\n }\n \n+int read_mailmap(struct string_list *map, char **repo_abbrev)\n+{\n+\t/* each failure returns 1, so >1 means both calls failed */\n+\treturn read_single_mailmap(map, \".mailmap\", repo_abbrev) +\n+\t       read_single_mailmap(map, git_mailmap_file, repo_abbrev) > 1;\n+}\n+\n int map_email(struct string_list *map, const char *email, char *name, int maxlen)\n {\n \tchar *p;\ndiff --git a/mailmap.h b/mailmap.h\nindex 6e48f83..ba2ee76 100644\n--- a/mailmap.h\n+++ b/mailmap.h\n@@ -1,7 +1,7 @@\n #ifndef MAILMAP_H\n #define MAILMAP_H\n \n-int read_mailmap(struct string_list *map, const char *filename, char **repo_abbrev);\n+int read_mailmap(struct string_list *map, char **repo_abbrev);\n int map_email(struct string_list *mailmap, const char *email, char *name, int maxlen);\n \n #endif\ndiff --git a/pretty.c b/pretty.c\nindex 8d4dbc9..cecd9fc 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -313,7 +313,7 @@ static int mailmap_name(struct strbuf *sb, const char *email)\n \n \tif (!mail_map) {\n \t\tmail_map = xcalloc(1, sizeof(*mail_map));\n-\t\tread_mailmap(mail_map, \".mailmap\", NULL);\n+\t\tread_mailmap(mail_map, NULL);\n \t}\n \n \tif (!mail_map->nr)\ndiff --git a/t/t4203-mailmap.sh b/t/t4203-mailmap.sh\nnew file mode 100755\nindex 0000000..fc50ac2\n--- /dev/null\n+++ b/t/t4203-mailmap.sh\n@@ -0,0 +1,109 @@\n+#!/bin/sh\n+\n+test_description='.mailmap configurations'\n+\n+. ./test-lib.sh\n+\n+test_expect_success setup '\n+\techo one >one &&\n+\tgit add one &&\n+\ttest_tick &&\n+\tgit commit -m initial &&\n+\techo two >>one &&\n+\tgit add one &&\n+\tgit commit --author \"nick1 <bugs@company.xx>\" -m second\n+'\n+\n+cat >expect <<\\EOF\n+A U Thor (1):\n+      initial\n+\n+nick1 (1):\n+      second\n+\n+EOF\n+\n+test_expect_success 'No mailmap' '\n+\tgit shortlog HEAD >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+cat >expect <<\\EOF\n+Repo Guy (1):\n+      initial\n+\n+nick1 (1):\n+      second\n+\n+EOF\n+\n+test_expect_success 'default .mailmap' '\n+\techo \"Repo Guy <author@example.com>\" > .mailmap &&\n+\tgit shortlog HEAD >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+# Using a mailmap file in a subdirectory of the repo here, but\n+# could just as well have been a file outside of the repository\n+cat >expect <<\\EOF\n+Internal Guy (1):\n+      second\n+\n+Repo Guy (1):\n+      initial\n+\n+EOF\n+test_expect_success 'mailmap.file set' '\n+\tmkdir internal_mailmap &&\n+\techo \"Internal Guy <bugs@company.xx>\" > internal_mailmap/.mailmap &&\n+\tgit config mailmap.file internal_mailmap/.mailmap &&\n+\tgit shortlog HEAD >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+cat >expect <<\\EOF\n+External Guy (1):\n+      initial\n+\n+Internal Guy (1):\n+      second\n+\n+EOF\n+test_expect_success 'mailmap.file override' '\n+\techo \"External Guy <author@example.com>\" >> internal_mailmap/.mailmap &&\n+\tgit config mailmap.file internal_mailmap/.mailmap &&\n+\tgit shortlog HEAD >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+cat >expect <<\\EOF\n+Repo Guy (1):\n+      initial\n+\n+nick1 (1):\n+      second\n+\n+EOF\n+\n+test_expect_success 'mailmap.file non-existant' '\n+\trm internal_mailmap/.mailmap &&\n+\trmdir internal_mailmap &&\n+\tgit shortlog HEAD >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+cat >expect <<\\EOF\n+A U Thor (1):\n+      initial\n+\n+nick1 (1):\n+      second\n+\n+EOF\n+test_expect_success 'No mailmap files, but configured' '\n+\trm .mailmap &&\n+\tgit shortlog HEAD >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\n-- \n1.6.1.2.354.g9a90\n"},{"id":"103749","messageId":"8b7c8612d6948421813b707ae4e265d950b4bf41.1234102794.git.marius@trolltech.com","threadId":"17653","inReplyTo":"3db7411da93f5ae4de5247170d4b821e7b0ed88a.1234102794.git.marius@trolltech.com","subject":"[PATCH v5 2/5] Add find_insert_index, insert_at_index and clear_func functions to string_list","fromName":"Marius Storm-Olsen","fromEmail":"marius@trolltech.com","sentAt":"2009-02-08T14:34:28Z","receivedAt":"2009-02-08T14:34:28Z","isPatch":true,"sender":{"key":"marius@trolltech.com","avatar":"https://gravatar.com/avatar/a40071d8f651862c6ab10bd7996f0ad84d94f06c0399de9e3fa4f06beb390a71?d=mp&s=160"},"body":"string_list_find_insert_index() and string_list_insert_at_index()\nenables you to see if an item is in the string_list, and to\ninsert at the appropriate index in the list, if not there.\nThis is usefull if you need to manipulate an existing item,\nif present, and insert a new item if not.\n\nFuture mailmap code will use this construct to enable\ncomplex (old_name, old_email) -> (new_name, new_email)\nlookups.\n\nThe string_list_clear_func() allows to call a custom\ncleanup function on each item in a string_list, which is\nuseful is the util member points to a complex structure.\n\nSigned-off-by: Marius Storm-Olsen <marius@trolltech.com>\n---\n string-list.c |   43 +++++++++++++++++++++++++++++++++++++++----\n string-list.h |    9 +++++++++\n 2 files changed, 48 insertions(+), 4 deletions(-)\n\ndiff --git a/string-list.c b/string-list.c\nindex ddd83c8..15e14cf 100644\n--- a/string-list.c\n+++ b/string-list.c\n@@ -26,10 +26,10 @@ static int get_entry_index(const struct string_list *list, const char *string,\n }\n \n /* returns -1-index if already exists */\n-static int add_entry(struct string_list *list, const char *string)\n+static int add_entry(int insert_at, struct string_list *list, const char *string)\n {\n-\tint exact_match;\n-\tint index = get_entry_index(list, string, &exact_match);\n+\tint exact_match = 0;\n+\tint index = insert_at != -1 ? insert_at : get_entry_index(list, string, &exact_match);\n \n \tif (exact_match)\n \t\treturn -1 - index;\n@@ -53,7 +53,13 @@ static int add_entry(struct string_list *list, const char *string)\n \n struct string_list_item *string_list_insert(const char *string, struct string_list *list)\n {\n-\tint index = add_entry(list, string);\n+\treturn string_list_insert_at_index(-1, string, list);\n+}\n+\n+struct string_list_item *string_list_insert_at_index(int insert_at,\n+\t\t\t\t\t\t     const char *string, struct string_list *list)\n+{\n+\tint index = add_entry(insert_at, list, string);\n \n \tif (index < 0)\n \t\tindex = -1 - index;\n@@ -68,6 +74,16 @@ int string_list_has_string(const struct string_list *list, const char *string)\n \treturn exact_match;\n }\n \n+int string_list_find_insert_index(const struct string_list *list, const char *string,\n+\t\t\t\t  int negative_existing_index)\n+{\n+\tint exact_match;\n+\tint index = get_entry_index(list, string, &exact_match);\n+\tif (exact_match)\n+\t\tindex = -1 - (negative_existing_index ? index : 0);\n+\treturn index;\n+}\n+\n struct string_list_item *string_list_lookup(const char *string, struct string_list *list)\n {\n \tint exact_match, i = get_entry_index(list, string, &exact_match);\n@@ -94,6 +110,25 @@ void string_list_clear(struct string_list *list, int free_util)\n \tlist->nr = list->alloc = 0;\n }\n \n+void string_list_clear_func(struct string_list *list, string_list_clear_func_t clearfunc)\n+{\n+\tif (list->items) {\n+\t\tint i;\n+\t\tif (clearfunc) {\n+\t\t\tfor (i = 0; i < list->nr; i++)\n+\t\t\t\tclearfunc(list->items[i].util, list->items[i].string);\n+\t\t}\n+\t\tif (list->strdup_strings) {\n+\t\t\tfor (i = 0; i < list->nr; i++)\n+\t\t\t\tfree(list->items[i].string);\n+\t\t}\n+\t\tfree(list->items);\n+\t}\n+\tlist->items = NULL;\n+\tlist->nr = list->alloc = 0;\n+}\n+\n+\n void print_string_list(const char *text, const struct string_list *p)\n {\n \tint i;\ndiff --git a/string-list.h b/string-list.h\nindex 4d6a705..d32ba05 100644\n--- a/string-list.h\n+++ b/string-list.h\n@@ -15,9 +15,18 @@ struct string_list\n void print_string_list(const char *text, const struct string_list *p);\n void string_list_clear(struct string_list *list, int free_util);\n \n+/* Use this function to call a custom clear function on each util pointer */\n+/* The string associated with the util pointer is passed as the second argument */\n+typedef void (*string_list_clear_func_t)(void *p, const char *str);\n+void string_list_clear_func(struct string_list *list, string_list_clear_func_t clearfunc);\n+\n /* Use these functions only on sorted lists: */\n int string_list_has_string(const struct string_list *list, const char *string);\n+int string_list_find_insert_index(const struct string_list *list, const char *string,\n+\t\t\t\t  int negative_existing_index);\n struct string_list_item *string_list_insert(const char *string, struct string_list *list);\n+struct string_list_item *string_list_insert_at_index(int insert_at,\n+\t\t\t\t\t\t     const char *string, struct string_list *list);\n struct string_list_item *string_list_lookup(const char *string, struct string_list *list);\n \n /* Use these functions only on unsorted lists: */\n-- \n1.6.1.2.354.g9a90\n"},{"id":"103751","messageId":"b94c3ab1a2afb95b4243dea381024a432a2d9cc5.1234102794.git.marius@trolltech.com","threadId":"17653","inReplyTo":"8b7c8612d6948421813b707ae4e265d950b4bf41.1234102794.git.marius@trolltech.com","subject":"[PATCH v5 3/5] Add map_user() and clear_mailmap() to mailmap","fromName":"Marius Storm-Olsen","fromEmail":"marius@trolltech.com","sentAt":"2009-02-08T14:34:29Z","receivedAt":"2009-02-08T14:34:29Z","isPatch":true,"sender":{"key":"marius@trolltech.com","avatar":"https://gravatar.com/avatar/a40071d8f651862c6ab10bd7996f0ad84d94f06c0399de9e3fa4f06beb390a71?d=mp&s=160"},"body":"map_user() allows to lookup and replace both email and\nname of a user, based on a new style mailmap file.\n\nThe possible mailmap definitions are now:\n\n  proper_name <commit_email>                             # Old style\n  <proper_email> <commit_email>                          # New style\n  proper_name <proper_email> <commit_email>              # New style\n  proper_name <proper_email> commit_name <commit_email>  # New style\n\nmap_email() operates the same as before, with the\nexception that it also will to try to match on a name\npassed in through the name return buffer.\n\nclear_mailmap() is needed to now clear the more complex\nmailmap structure.\n\nSigned-off-by: Marius Storm-Olsen <marius@trolltech.com>\n---\n Documentation/git-shortlog.txt |   87 +++++++++++++-----\n mailmap.c                      |  196 ++++++++++++++++++++++++++++++++++------\n mailmap.h                      |    4 +\n 3 files changed, 236 insertions(+), 51 deletions(-)\n\ndiff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt\nindex 66b6045..a0eaab5 100644\n--- a/Documentation/git-shortlog.txt\n+++ b/Documentation/git-shortlog.txt\n@@ -48,24 +48,38 @@ OPTIONS\n FILES\n -----\n \n-If a file `.mailmap` exists at the toplevel of the repository, or at the\n-location pointed to by the log.mailmap configuration option,\n-it is used to map an author email address to a canonical real name. This\n-can be used to coalesce together commits by the same person where their\n-name was spelled differently (whether with the same email address or\n-not).\n-\n-Each line in the file consists, in this order, of the canonical real name\n-of an author, whitespace, and an email address (enclosed by '<' and '>')\n-to map to the name. Use hash '#' for comments, either on their own line,\n-or after the email address.\n-\n-A canonical name may appear in more than one line, associated with\n-different email addresses, but it doesn't make sense for a given address\n-to appear more than once (if that happens, a later line overrides the\n-earlier ones).\n-\n-So, for example, if your history contains commits by two authors, Jane\n+If the file `.mailmap` exists at the toplevel of the repository, or at\n+the location pointed to by the mailmap.file configuration option, it\n+is used to map author and committer names and email addresses to\n+canonical real names and email addresses.\n+This mapping can be used to coalesce together commits by the same\n+person where their name and/or email address was spelled differently.\n+\n+In the simple form, each line in the file consists of the canonical\n+real name of an author, whitespace, and an email address used in the\n+commit (enclosed by '<' and '>') to map to the name. Thus, looks like\n+this\n+--\n+\tProper Name <commit@email.xx>\n+--\n+\n+The more complex forms are\n+--\n+\t<proper@email.xx> <commit@email.xx>\n+--\n+which allows mailmap to replace only the email part of a commit, and\n+--\n+\tProper Name <proper@email.xx> <commit@email.xx>\n+--\n+which allows mailmap to replace both the name and the email of a\n+commit matching the specified commit email address, and\n+--\n+\tProper Name <proper@email.xx> Commit Name <commit@email.xx>\n+--\n+which allows mailmap to replace both the name and the email of a\n+commit matching both the specified commit name and email address.\n+\n+Example 1: Your history contains commits by two authors, Jane\n and Joe, whose names appear in the repository under several forms:\n \n ------------\n@@ -76,16 +90,43 @@ Jane Doe <jane@laptop.(none)>\n Jane D. <jane@desktop.(none)>\n ------------\n \n-Then, supposing Joe wants his middle name initial used, and Jane prefers\n-her family name fully spelled out, a proper `.mailmap` file would look like:\n+Now suppose that Joe wants his middle name initial used, and Jane\n+prefers her family name fully spelled out. A proper `.mailmap` file\n+would look like:\n \n ------------\n-# Note how we don't need an entry for <jane@laptop.(none)>, because the\n-# real name of that author is correct already, and coalesced directly.\n-Jane Doe <jane@desktop.(none)>\n+Jane Doe         <jane@desktop.(none)>\n Joe R. Developer <joe@example.com>\n ------------\n \n+Note how we don't need an entry for <jane@laptop.(none)>, because the\n+real name of that author is correct already, and coalesced directly.\n+\n+Example 2: Your repository contains commits from the following\n+authors:\n+\n+------------\n+nick1 <bugs@company.xx>\n+nick2 <bugs@company.xx>\n+nick2 <nick2@company.xx>\n+santa <me@company.xx>\n+claus <me@company.xx>\n+CTO <cto@coompany.xx>\n+------------\n+\n+Then, you might want a `.mailmap` file looking like:\n+------------\n+<cto@company.xx>                       <cto@coompany.xx>\n+Some Dude <some@dude.xx>         nick1 <bugs@company.xx>\n+Other Author <other@author.xx>   nick2 <bugs@company.xx>\n+Other Author <other@author.xx>         <nick2@company.xx>\n+Santa Claus <santa.claus@northpole.xx> <me@company.xx>\n+------------\n+\n+Use hash '#' for comments that are either on their own line, or after\n+the email address.\n+\n+\n Author\n ------\n Written by Jeff Garzik <jgarzik@pobox.com>\ndiff --git a/mailmap.c b/mailmap.c\nindex d006dad..f12bb45 100644\n--- a/mailmap.c\n+++ b/mailmap.c\n@@ -2,7 +2,122 @@\n #include \"string-list.h\"\n #include \"mailmap.h\"\n \n+#define DEBUG_MAILMAP 0\n+#if DEBUG_MAILMAP\n+#define debug_mm(...) fprintf(stderr, __VA_ARGS__)\n+#else\n+static inline void debug_mm(const char *format, ...) {}\n+#endif\n+\n const char *git_mailmap_file;\n+\n+struct mailmap_info {\n+\tchar *name;\n+\tchar *email;\n+};\n+\n+struct mailmap_entry {\n+\t/* name and email for the simple mail-only case */\n+\tchar *name;\n+\tchar *email;\n+\n+\t/* name and email for the complex mail and name matching case */\n+\tstruct string_list namemap;\n+};\n+\n+static void free_mailmap_info(void *p, const char *s)\n+{\n+\tstruct mailmap_info *mi = (struct mailmap_info *)p;\n+\tdebug_mm(\"mailmap: -- complex: '%s' -> '%s' <%s>\\n\", s, mi->name, mi->email);\n+\tfree(mi->name);\n+\tfree(mi->email);\n+}\n+\n+static void free_mailmap_entry(void *p, const char *s)\n+{\n+\tstruct mailmap_entry *me = (struct mailmap_entry *)p;\n+\tdebug_mm(\"mailmap: removing entries for <%s>, with %d sub-entries\\n\", s, me->namemap.nr);\n+\tdebug_mm(\"mailmap: - simple: '%s' <%s>\\n\", me->name, me->email);\n+\tfree(me->name);\n+\tfree(me->email);\n+\n+\tme->namemap.strdup_strings = 1;\n+\tstring_list_clear_func(&me->namemap, free_mailmap_info);\n+}\n+\n+static void add_mapping(struct string_list *map,\n+\t\t\tchar *new_name, char *new_email, char *old_name, char *old_email)\n+{\n+\tstruct mailmap_entry *me;\n+\tint index;\n+\tif (old_email == NULL) {\n+\t\told_email = new_email;\n+\t\tnew_email = NULL;\n+\t}\n+\n+\tif ((index = string_list_find_insert_index(map, old_email, 1)) < 0) {\n+\t\t/* mailmap entry exists, invert index value */\n+\t\tindex = -1 - index;\n+\t} else {\n+\t\t/* create mailmap entry */\n+\t\tstruct string_list_item *item = string_list_insert_at_index(index, old_email, map);\n+\t\titem->util = xmalloc(sizeof(struct mailmap_entry));\n+\t\tmemset(item->util, 0, sizeof(struct mailmap_entry));\n+\t\t((struct mailmap_entry *)item->util)->namemap.strdup_strings = 1;\n+\t}\n+\tme = (struct mailmap_entry *)map->items[index].util;\n+\n+\tif (old_name == NULL) {\n+\t\tdebug_mm(\"mailmap: adding (simple) entry for %s at index %d\\n\", old_email, index);\n+\t\t/* Replace current name and new email for simple entry */\n+\t\tfree(me->name);\n+\t\tfree(me->email);\n+\t\tif (new_name)\n+\t\t\tme->name = xstrdup(new_name);\n+\t\tif (new_email)\n+\t\t\tme->email = xstrdup(new_email);\n+\t} else {\n+\t\tstruct mailmap_info *mi = xmalloc(sizeof(struct mailmap_info));\n+\t\tdebug_mm(\"mailmap: adding (complex) entry for %s at index %d\\n\", old_email, index);\n+\t\tif (new_name)\n+\t\t\tmi->name = xstrdup(new_name);\n+\t\tif (new_email)\n+\t\t\tmi->email = xstrdup(new_email);\n+\t\tstring_list_insert(old_name, &me->namemap)->util = mi;\n+\t}\n+\n+\tdebug_mm(\"mailmap:  '%s' <%s> -> '%s' <%s>\\n\",\n+\t\t old_name, old_email, new_name, new_email);\n+}\n+\n+static char *parse_name_and_email(char *buffer, char **name, char **email)\n+{\n+\tchar *left, *right, *nstart, *nend;\n+\t*name = *email = 0;\n+\n+\tif ((left = strchr(buffer, '<')) == NULL)\n+\t\treturn NULL;\n+\tif ((right = strchr(left+1, '>')) == NULL)\n+\t\treturn NULL;\n+\tif (left+1 == right)\n+\t\treturn NULL;\n+\n+\t/* remove whitespace from beginning and end of name */\n+\tnstart = buffer;\n+\twhile (isspace(*nstart) && nstart < left)\n+\t\t++nstart;\n+\tnend = left-1;\n+\twhile (isspace(*nend) && nend > nstart)\n+\t\t--nend;\n+\n+\t*name = (nstart < nend ? nstart : NULL);\n+\t*email = left+1;\n+\t*(nend+1) = '\\0';\n+\t*right++ = '\\0';\n+\n+\treturn (*right == '\\0' ? NULL : right);\n+}\n+\n static int read_single_mailmap(struct string_list *map, const char *filename, char **repo_abbrev)\n {\n \tchar buffer[1024];\n@@ -11,9 +126,7 @@ static int read_single_mailmap(struct string_list *map, const char *filename, ch\n \tif (f == NULL)\n \t\treturn 1;\n \twhile (fgets(buffer, sizeof(buffer), f) != NULL) {\n-\t\tchar *end_of_name, *left_bracket, *right_bracket;\n-\t\tchar *name, *email;\n-\t\tint i;\n+\t\tchar *name1 = 0, *email1 = 0, *name2 = 0, *email2 = 0;\n \t\tif (buffer[0] == '#') {\n \t\t\tstatic const char abbrev[] = \"# repo-abbrev:\";\n \t\t\tint abblen = sizeof(abbrev) - 1;\n@@ -37,25 +150,11 @@ static int read_single_mailmap(struct string_list *map, const char *filename, ch\n \t\t\t}\n \t\t\tcontinue;\n \t\t}\n-\t\tif ((left_bracket = strchr(buffer, '<')) == NULL)\n-\t\t\tcontinue;\n-\t\tif ((right_bracket = strchr(left_bracket + 1, '>')) == NULL)\n-\t\t\tcontinue;\n-\t\tif (right_bracket == left_bracket + 1)\n-\t\t\tcontinue;\n-\t\tfor (end_of_name = left_bracket;\n-\t\t     end_of_name != buffer && isspace(end_of_name[-1]);\n-\t\t     end_of_name--)\n-\t\t\t; /* keep on looking */\n-\t\tif (end_of_name == buffer)\n-\t\t\tcontinue;\n-\t\tname = xmalloc(end_of_name - buffer + 1);\n-\t\tstrlcpy(name, buffer, end_of_name - buffer + 1);\n-\t\temail = xmalloc(right_bracket - left_bracket);\n-\t\tfor (i = 0; i < right_bracket - left_bracket - 1; i++)\n-\t\t\temail[i] = tolower(left_bracket[i + 1]);\n-\t\temail[right_bracket - left_bracket - 1] = '\\0';\n-\t\tstring_list_insert(email, map)->util = name;\n+\t\tif ((name2 = parse_name_and_email(buffer, &name1, &email1)) != NULL)\n+\t\t\tparse_name_and_email(name2, &name2, &email2);\n+\n+\t\tif (email1)\n+\t\t\tadd_mapping(map, name1, email1, name2, email2);\n \t}\n \tfclose(f);\n \treturn 0;\n@@ -63,22 +162,37 @@ static int read_single_mailmap(struct string_list *map, const char *filename, ch\n \n int read_mailmap(struct string_list *map, char **repo_abbrev)\n {\n+\tmap->strdup_strings = 1;\n \t/* each failure returns 1, so >1 means both calls failed */\n \treturn read_single_mailmap(map, \".mailmap\", repo_abbrev) +\n \t       read_single_mailmap(map, git_mailmap_file, repo_abbrev) > 1;\n }\n \n-int map_email(struct string_list *map, const char *email, char *name, int maxlen)\n+void clear_mailmap(struct string_list *map)\n+{\n+\tdebug_mm(\"mailmap: clearing %d entries...\\n\", map->nr);\n+\tmap->strdup_strings = 1;\n+\tstring_list_clear_func(map, free_mailmap_entry);\n+\tdebug_mm(\"mailmap: cleared\\n\");\n+}\n+\n+int map_user(struct string_list *map,\n+\t     char *email, int maxlen_email, char *name, int maxlen_name)\n {\n \tchar *p;\n \tstruct string_list_item *item;\n+\tstruct mailmap_entry *me;\n \tchar buf[1024], *mailbuf;\n \tint i;\n \n-\t/* autocomplete common developers */\n+\t/* figure out space requirement for email */\n \tp = strchr(email, '>');\n-\tif (!p)\n-\t\treturn 0;\n+\tif (!p) {\n+\t\t/* email passed in might not be wrapped in <>, but end with a \\0 */\n+\t\tp = memchr(email, '\\0', maxlen_email);\n+\t\tif (p == 0)\n+\t\t\treturn 0;\n+\t}\n \tif (p - email + 1 < sizeof(buf))\n \t\tmailbuf = buf;\n \telse\n@@ -88,13 +202,39 @@ int map_email(struct string_list *map, const char *email, char *name, int maxlen\n \tfor (i = 0; i < p - email; i++)\n \t\tmailbuf[i] = tolower(email[i]);\n \tmailbuf[i] = 0;\n+\n+\tdebug_mm(\"map_user: map '%s' <%s>\\n\", name, mailbuf);\n \titem = string_list_lookup(mailbuf, map);\n+\tif (item != NULL) {\n+\t\tme = (struct mailmap_entry *)item->util;\n+\t\tif (me->namemap.nr) {\n+\t\t\t/* The item has multiple items, so we'll look up on name too */\n+\t\t\t/* If the name is not found, we choose the simple entry      */\n+\t\t\tstruct string_list_item *subitem = string_list_lookup(name, &me->namemap);\n+\t\t\tif (subitem)\n+\t\t\t\titem = subitem;\n+\t\t}\n+\t}\n \tif (mailbuf != buf)\n \t\tfree(mailbuf);\n \tif (item != NULL) {\n-\t\tconst char *realname = (const char *)item->util;\n-\t\tstrlcpy(name, realname, maxlen);\n+\t\tstruct mailmap_info *mi = (struct mailmap_info *)item->util;\n+\t\tif (mi->name == NULL && (mi->email == NULL || maxlen_email == 0)) {\n+\t\t\tdebug_mm(\"map_user:  -- (no simple mapping)\\n\");\n+\t\t\treturn 0;\n+\t\t}\n+\t\tif (maxlen_email && mi->email)\n+\t\t\tstrlcpy(email, mi->email, maxlen_email);\n+\t\tif (maxlen_name && mi->name)\n+\t\t\tstrlcpy(name, mi->name, maxlen_name);\n+\t\tdebug_mm(\"map_user:  to '%s' <%s>\\n\", name, mi->email ? mi->email : \"\");\n \t\treturn 1;\n \t}\n+\tdebug_mm(\"map_user:  --\\n\");\n \treturn 0;\n }\n+\n+int map_email(struct string_list *map, const char *email, char *name, int maxlen)\n+{\n+\treturn map_user(map, (char *)email, 0, name, maxlen);\n+}\ndiff --git a/mailmap.h b/mailmap.h\nindex ba2ee76..4b2ca3a 100644\n--- a/mailmap.h\n+++ b/mailmap.h\n@@ -2,6 +2,10 @@\n #define MAILMAP_H\n \n int read_mailmap(struct string_list *map, char **repo_abbrev);\n+void clear_mailmap(struct string_list *map);\n+\n int map_email(struct string_list *mailmap, const char *email, char *name, int maxlen);\n+int map_user(struct string_list *mailmap,\n+\t     char *email, int maxlen_email, char *name, int maxlen_name);\n \n #endif\n-- \n1.6.1.2.354.g9a90\n"},{"id":"103752","messageId":"6e190d2b99c8044b782cf5991821e82da80ea957.1234102794.git.marius@trolltech.com","threadId":"17653","inReplyTo":"b94c3ab1a2afb95b4243dea381024a432a2d9cc5.1234102794.git.marius@trolltech.com","subject":"[PATCH v5 4/5] Change current mailmap usage to do matching on both name and email of author/committer.","fromName":"Marius Storm-Olsen","fromEmail":"marius@trolltech.com","sentAt":"2009-02-08T14:34:30Z","receivedAt":"2009-02-08T14:34:30Z","isPatch":true,"sender":{"key":"marius@trolltech.com","avatar":"https://gravatar.com/avatar/a40071d8f651862c6ab10bd7996f0ad84d94f06c0399de9e3fa4f06beb390a71?d=mp&s=160"},"body":"\nSigned-off-by: Marius Storm-Olsen <marius@trolltech.com>\n---\n Documentation/pretty-formats.txt |    2 +\n builtin-blame.c                  |   50 +++++++++++-------\n builtin-shortlog.c               |   22 ++++++--\n pretty.c                         |   57 +++++++++++----------\n t/t4203-mailmap.sh               |  106 ++++++++++++++++++++++++++++++++++++++\n 5 files changed, 186 insertions(+), 51 deletions(-)\n\ndiff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt\nindex 3d87d3e..28808b7 100644\n--- a/Documentation/pretty-formats.txt\n+++ b/Documentation/pretty-formats.txt\n@@ -103,6 +103,7 @@ The placeholders are:\n - '%an': author name\n - '%aN': author name (respecting .mailmap)\n - '%ae': author email\n+- '%aE': author email (respecting .mailmap)\n - '%ad': author date (format respects --date= option)\n - '%aD': author date, RFC2822 style\n - '%ar': author date, relative\n@@ -111,6 +112,7 @@ The placeholders are:\n - '%cn': committer name\n - '%cN': committer name (respecting .mailmap)\n - '%ce': committer email\n+- '%cE': committer email (respecting .mailmap)\n - '%cd': committer date\n - '%cD': committer date, RFC2822 style\n - '%cr': committer date, relative\ndiff --git a/builtin-blame.c b/builtin-blame.c\nindex 9cfa090..114a214 100644\n--- a/builtin-blame.c\n+++ b/builtin-blame.c\n@@ -1264,11 +1264,12 @@ struct commit_info\n  * Parse author/committer line in the commit object buffer\n  */\n static void get_ac_line(const char *inbuf, const char *what,\n-\t\t\tint bufsz, char *person, const char **mail,\n+\t\t\tint person_len, char *person,\n+\t\t\tint mail_len, char *mail,\n \t\t\tunsigned long *time, const char **tz)\n {\n \tint len, tzlen, maillen;\n-\tchar *tmp, *endp, *timepos;\n+\tchar *tmp, *endp, *timepos, *mailpos;\n \n \ttmp = strstr(inbuf, what);\n \tif (!tmp)\n@@ -1279,10 +1280,11 @@ static void get_ac_line(const char *inbuf, const char *what,\n \t\tlen = strlen(tmp);\n \telse\n \t\tlen = endp - tmp;\n-\tif (bufsz <= len) {\n+\tif (person_len <= len) {\n \terror_out:\n \t\t/* Ugh */\n-\t\t*mail = *tz = \"(unknown)\";\n+\t\t*tz = \"(unknown)\";\n+\t\tstrcpy(mail, *tz);\n \t\t*time = 0;\n \t\treturn;\n \t}\n@@ -1305,9 +1307,10 @@ static void get_ac_line(const char *inbuf, const char *what,\n \t*tmp = 0;\n \twhile (*tmp != ' ')\n \t\ttmp--;\n-\t*mail = tmp + 1;\n+\tmailpos = tmp + 1;\n \t*tmp = 0;\n \tmaillen = timepos - tmp;\n+\tmemcpy(mail, mailpos, maillen);\n \n \tif (!mailmap.nr)\n \t\treturn;\n@@ -1316,20 +1319,23 @@ static void get_ac_line(const char *inbuf, const char *what,\n \t * mailmap expansion may make the name longer.\n \t * make room by pushing stuff down.\n \t */\n-\ttmp = person + bufsz - (tzlen + 1);\n+\ttmp = person + person_len - (tzlen + 1);\n \tmemmove(tmp, *tz, tzlen);\n \ttmp[tzlen] = 0;\n \t*tz = tmp;\n \n-\ttmp = tmp - (maillen + 1);\n-\tmemmove(tmp, *mail, maillen);\n-\ttmp[maillen] = 0;\n-\t*mail = tmp;\n-\n \t/*\n-\t * Now, convert e-mail using mailmap\n+\t * Now, convert both name and e-mail using mailmap\n \t */\n-\tmap_email(&mailmap, tmp + 1, person, tmp-person-1);\n+\tif(map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) {\n+\t\t/* Add a trailing '>' to email, since map_user returns plain emails\n+\t\t   Note: It already has '<', since we replace from mail+1 */\n+\t\tmailpos = memchr(mail, '\\0', mail_len);\n+\t\tif (mailpos && mailpos-mail < mail_len - 1) {\n+\t\t\t*mailpos = '>';\n+\t\t\t*(mailpos+1) = '\\0';\n+\t\t}\n+\t}\n }\n \n static void get_commit_info(struct commit *commit,\n@@ -1338,8 +1344,10 @@ static void get_commit_info(struct commit *commit,\n {\n \tint len;\n \tchar *tmp, *endp, *reencoded, *message;\n-\tstatic char author_buf[1024];\n-\tstatic char committer_buf[1024];\n+\tstatic char author_name[1024];\n+\tstatic char author_mail[1024];\n+\tstatic char committer_name[1024];\n+\tstatic char committer_mail[1024];\n \tstatic char summary_buf[1024];\n \n \t/*\n@@ -1357,9 +1365,11 @@ static void get_commit_info(struct commit *commit,\n \t}\n \treencoded = reencode_commit_message(commit, NULL);\n \tmessage   = reencoded ? reencoded : commit->buffer;\n-\tret->author = author_buf;\n+\tret->author = author_name;\n+\tret->author_mail = author_mail;\n \tget_ac_line(message, \"\\nauthor \",\n-\t\t    sizeof(author_buf), author_buf, &ret->author_mail,\n+\t\t    sizeof(author_name), author_name,\n+\t\t    sizeof(author_mail), author_mail,\n \t\t    &ret->author_time, &ret->author_tz);\n \n \tif (!detailed) {\n@@ -1367,9 +1377,11 @@ static void get_commit_info(struct commit *commit,\n \t\treturn;\n \t}\n \n-\tret->committer = committer_buf;\n+\tret->committer = committer_name;\n+\tret->committer_mail = committer_mail;\n \tget_ac_line(message, \"\\ncommitter \",\n-\t\t    sizeof(committer_buf), committer_buf, &ret->committer_mail,\n+\t\t    sizeof(committer_name), committer_name,\n+\t\t    sizeof(committer_mail), committer_mail,\n \t\t    &ret->committer_time, &ret->committer_tz);\n \n \tret->summary = summary_buf;\ndiff --git a/builtin-shortlog.c b/builtin-shortlog.c\nindex 314b6bc..badd912 100644\n--- a/builtin-shortlog.c\n+++ b/builtin-shortlog.c\n@@ -40,6 +40,7 @@ static void insert_one_record(struct shortlog *log,\n \tchar *buffer, *p;\n \tstruct string_list_item *item;\n \tchar namebuf[1024];\n+\tchar emailbuf[1024];\n \tsize_t len;\n \tconst char *eol;\n \tconst char *boemail, *eoemail;\n@@ -51,7 +52,19 @@ static void insert_one_record(struct shortlog *log,\n \teoemail = strchr(boemail, '>');\n \tif (!eoemail)\n \t\treturn;\n-\tif (!map_email(&log->mailmap, boemail+1, namebuf, sizeof(namebuf))) {\n+\n+\t/* copy author name to namebuf, to support matching on both name and email */\n+\tmemcpy(namebuf, author, boemail - author);\n+\tlen = boemail - author;\n+\twhile(len > 0 && isspace(namebuf[len-1]))\n+\t\tlen--;\n+\tnamebuf[len] = 0;\n+\n+\t/* copy email name to emailbuf, to allow email replacement as well */\n+\tmemcpy(emailbuf, boemail+1, eoemail - boemail);\n+\temailbuf[eoemail - boemail - 1] = 0;\n+\n+\tif (!map_user(&log->mailmap, emailbuf, sizeof(emailbuf), namebuf, sizeof(namebuf))) {\n \t\twhile (author < boemail && isspace(*author))\n \t\t\tauthor++;\n \t\tfor (len = 0;\n@@ -67,8 +80,8 @@ static void insert_one_record(struct shortlog *log,\n \n \tif (log->email) {\n \t\tsize_t room = sizeof(namebuf) - len - 1;\n-\t\tint maillen = eoemail - boemail + 1;\n-\t\tsnprintf(namebuf + len, room, \" %.*s\", maillen, boemail);\n+\t\tint maillen = strlen(emailbuf);\n+\t\tsnprintf(namebuf + len, room, \" <%.*s>\", maillen, emailbuf);\n \t}\n \n \titem = string_list_insert(namebuf, &log->list);\n@@ -321,6 +334,5 @@ void shortlog_output(struct shortlog *log)\n \n \tlog->list.strdup_strings = 1;\n \tstring_list_clear(&log->list, 1);\n-\tlog->mailmap.strdup_strings = 1;\n-\tstring_list_clear(&log->mailmap, 1);\n+\tclear_mailmap(&log->mailmap);\n }\ndiff --git a/pretty.c b/pretty.c\nindex cecd9fc..518b40a 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -306,23 +306,14 @@ static char *logmsg_reencode(const struct commit *commit,\n \treturn out;\n }\n \n-static int mailmap_name(struct strbuf *sb, const char *email)\n+static int mailmap_name(char *email, int email_len, char *name, int name_len)\n {\n \tstatic struct string_list *mail_map;\n-\tchar buffer[1024];\n-\n \tif (!mail_map) {\n \t\tmail_map = xcalloc(1, sizeof(*mail_map));\n \t\tread_mailmap(mail_map, NULL);\n \t}\n-\n-\tif (!mail_map->nr)\n-\t\treturn -1;\n-\n-\tif (!map_email(mail_map, email, buffer, sizeof(buffer)))\n-\t\treturn -1;\n-\tstrbuf_addstr(sb, buffer);\n-\treturn 0;\n+\treturn mail_map->nr && map_user(mail_map, email, email_len, name, name_len);\n }\n \n static size_t format_person_part(struct strbuf *sb, char part,\n@@ -333,6 +324,9 @@ static size_t format_person_part(struct strbuf *sb, char part,\n \tint start, end, tz = 0;\n \tunsigned long date = 0;\n \tchar *ep;\n+\tconst char *name_start, *name_end, *mail_start, *mail_end, *msg_end = msg+len;\n+\tchar person_name[1024];\n+\tchar person_mail[1024];\n \n \t/* advance 'end' to point to email start delimiter */\n \tfor (end = 0; end < len && msg[end] != '<'; end++)\n@@ -346,25 +340,34 @@ static size_t format_person_part(struct strbuf *sb, char part,\n \tif (end >= len - 2)\n \t\tgoto skip;\n \n+\t/* Seek for both name and email part */\n+\tname_start = msg;\n+\tname_end = msg+end;\n+\twhile (name_end > name_start && isspace(*(name_end-1)))\n+\t\tname_end--;\n+\tmail_start = msg+end+1;\n+\tmail_end = mail_start;\n+\twhile (mail_end < msg_end && *mail_end != '>')\n+\t\tmail_end++;\n+\tif (mail_end == msg_end)\n+\t\tgoto skip;\n+\tend = mail_end-msg;\n+\n+\tif (part == 'N' || part == 'E') { /* mailmap lookup */\n+\t\tstrlcpy(person_name, name_start, name_end-name_start+1);\n+\t\tstrlcpy(person_mail, mail_start, mail_end-mail_start+1);\n+\t\tmailmap_name(person_mail, sizeof(person_mail), person_name, sizeof(person_name));\n+\t\tname_start = person_name;\n+\t\tname_end = name_start + strlen(person_name);\n+\t\tmail_start = person_mail;\n+\t\tmail_end = mail_start +  strlen(person_mail);\n+\t}\n \tif (part == 'n' || part == 'N') {\t/* name */\n-\t\twhile (end > 0 && isspace(msg[end - 1]))\n-\t\t\tend--;\n-\t\tif (part != 'N' || !msg[end] || !msg[end + 1] ||\n-\t\t    mailmap_name(sb, msg + end + 2) < 0)\n-\t\t\tstrbuf_add(sb, msg, end);\n+\t\tstrbuf_add(sb, name_start, name_end-name_start);\n \t\treturn placeholder_len;\n \t}\n-\tstart = ++end; /* save email start position */\n-\n-\t/* advance 'end' to point to email end delimiter */\n-\tfor ( ; end < len && msg[end] != '>'; end++)\n-\t\t; /* do nothing */\n-\n-\tif (end >= len)\n-\t\tgoto skip;\n-\n-\tif (part == 'e') {\t/* email */\n-\t\tstrbuf_add(sb, msg + start, end - start);\n+\tif (part == 'e' || part == 'E') {\t/* email */\n+\t\tstrbuf_add(sb, mail_start, mail_end-mail_start);\n \t\treturn placeholder_len;\n \t}\n \ndiff --git a/t/t4203-mailmap.sh b/t/t4203-mailmap.sh\nindex fc50ac2..9a7d1b4 100755\n--- a/t/t4203-mailmap.sh\n+++ b/t/t4203-mailmap.sh\n@@ -106,4 +106,110 @@ test_expect_success 'No mailmap files, but configured' '\n \ttest_cmp expect actual\n '\n \n+# Extended mailmap configurations should give us the following output for shortlog\n+cat >expect <<\\EOF\n+A U Thor <author@example.com> (1):\n+      initial\n+\n+CTO <cto@company.xx> (1):\n+      seventh\n+\n+Other Author <other@author.xx> (2):\n+      third\n+      fourth\n+\n+Santa Claus <santa.claus@northpole.xx> (2):\n+      fifth\n+      sixth\n+\n+Some Dude <some@dude.xx> (1):\n+      second\n+\n+EOF\n+\n+test_expect_success 'Shortlog output (complex mapping)' '\n+\techo three >>one &&\n+\tgit add one &&\n+\ttest_tick &&\n+\tgit commit --author \"nick2 <bugs@company.xx>\" -m third &&\n+\n+\techo four >>one &&\n+\tgit add one &&\n+\ttest_tick &&\n+\tgit commit --author \"nick2 <nick2@company.xx>\" -m fourth &&\n+\n+\techo five >>one &&\n+\tgit add one &&\n+\ttest_tick &&\n+\tgit commit --author \"santa <me@company.xx>\" -m fifth &&\n+\n+\techo six >>one &&\n+\tgit add one &&\n+\ttest_tick &&\n+\tgit commit --author \"claus <me@company.xx>\" -m sixth &&\n+\n+\techo seven >>one &&\n+\tgit add one &&\n+\ttest_tick &&\n+\tgit commit --author \"CTO <cto@coompany.xx>\" -m seventh &&\n+\n+\tmkdir internal_mailmap &&\n+\techo \"Committed <committer@example.com>\" > internal_mailmap/.mailmap &&\n+\techo \"<cto@company.xx>                       <cto@coompany.xx>\" >> internal_mailmap/.mailmap &&\n+\techo \"Some Dude <some@dude.xx>         nick1 <bugs@company.xx>\" >> internal_mailmap/.mailmap &&\n+\techo \"Other Author <other@author.xx>   nick2 <bugs@company.xx>\" >> internal_mailmap/.mailmap &&\n+\techo \"Other Author <other@author.xx>         <nick2@company.xx>\" >> internal_mailmap/.mailmap &&\n+\techo \"Santa Claus <santa.claus@northpole.xx> <me@company.xx>\" >> internal_mailmap/.mailmap &&\n+\techo \"Santa Claus <santa.claus@northpole.xx> <me@company.xx>\" >> internal_mailmap/.mailmap &&\n+\n+\tgit shortlog -e HEAD >actual &&\n+\ttest_cmp expect actual\n+\n+'\n+\n+# git log with --pretty format which uses the name and email mailmap placemarkers\n+cat >expect <<\\EOF\n+Author CTO <cto@coompany.xx> maps to CTO <cto@company.xx>\n+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>\n+\n+Author claus <me@company.xx> maps to Santa Claus <santa.claus@northpole.xx>\n+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>\n+\n+Author santa <me@company.xx> maps to Santa Claus <santa.claus@northpole.xx>\n+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>\n+\n+Author nick2 <nick2@company.xx> maps to Other Author <other@author.xx>\n+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>\n+\n+Author nick2 <bugs@company.xx> maps to Other Author <other@author.xx>\n+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>\n+\n+Author nick1 <bugs@company.xx> maps to Some Dude <some@dude.xx>\n+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>\n+\n+Author A U Thor <author@example.com> maps to A U Thor <author@example.com>\n+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>\n+EOF\n+\n+test_expect_success 'Log output (complex mapping)' '\n+\tgit log --pretty=format:\"Author %an <%ae> maps to %aN <%aE>%nCommitter %cn <%ce> maps to %cN <%cE>%n\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+# git blame\n+cat >expect <<\\EOF\n+^3a2fdcb (A U Thor     2005-04-07 15:13:13 -0700 1) one\n+7de6f99b (Some Dude    2005-04-07 15:13:13 -0700 2) two\n+5815879d (Other Author 2005-04-07 15:14:13 -0700 3) three\n+ff859d96 (Other Author 2005-04-07 15:15:13 -0700 4) four\n+5ab6d4fa (Santa Claus  2005-04-07 15:16:13 -0700 5) five\n+38a42d8b (Santa Claus  2005-04-07 15:17:13 -0700 6) six\n+8ddc0386 (CTO          2005-04-07 15:18:13 -0700 7) seven\n+EOF\n+\n+test_expect_success 'Blame output (complex mapping)' '\n+\tgit blame one >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n1.6.1.2.354.g9a90\n"},{"id":"103750","messageId":"f970142e47c9992abb2aafd40843a5f4be846e96.1234102794.git.marius@trolltech.com","threadId":"17653","inReplyTo":"6e190d2b99c8044b782cf5991821e82da80ea957.1234102794.git.marius@trolltech.com","subject":"[PATCH v5 5/5] Move mailmap documentation into separate file","fromName":"Marius Storm-Olsen","fromEmail":"marius@trolltech.com","sentAt":"2009-02-08T14:34:31Z","receivedAt":"2009-02-08T14:34:31Z","isPatch":true,"sender":{"key":"marius@trolltech.com","avatar":"https://gravatar.com/avatar/a40071d8f651862c6ab10bd7996f0ad84d94f06c0399de9e3fa4f06beb390a71?d=mp&s=160"},"body":"Include it directly from git-shortlog.txt, and refer\nto it from pretty-format.txt.\n\nSigned-off-by: Marius Storm-Olsen <marius@trolltech.com>\n---\n Documentation/git-blame.txt      |    6 +++\n Documentation/git-shortlog.txt   |   89 ++++----------------------------------\n Documentation/mailmap.txt        |   75 ++++++++++++++++++++++++++++++++\n Documentation/pretty-formats.txt |    8 ++--\n 4 files changed, 94 insertions(+), 84 deletions(-)\n create mode 100644 Documentation/mailmap.txt\n\ndiff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt\nindex fba374d..6999cf2 100644\n--- a/Documentation/git-blame.txt\n+++ b/Documentation/git-blame.txt\n@@ -184,6 +184,12 @@ there is ever added information (like the commit encoding or extended\n commit commentary), a blame viewer won't ever care.\n \n \n+MAPPING AUTHORS\n+---------------\n+\n+include::mailmap.txt[]\n+\n+\n SEE ALSO\n --------\n linkgit:git-annotate[1]\ndiff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt\nindex a0eaab5..42463a9 100644\n--- a/Documentation/git-shortlog.txt\n+++ b/Documentation/git-shortlog.txt\n@@ -45,86 +45,15 @@ OPTIONS\n \tand subsequent lines are indented by `indent2` spaces. `width`,\n \t`indent1`, and `indent2` default to 76, 6 and 9 respectively.\n \n-FILES\n------\n-\n-If the file `.mailmap` exists at the toplevel of the repository, or at\n-the location pointed to by the mailmap.file configuration option, it\n-is used to map author and committer names and email addresses to\n-canonical real names and email addresses.\n-This mapping can be used to coalesce together commits by the same\n-person where their name and/or email address was spelled differently.\n-\n-In the simple form, each line in the file consists of the canonical\n-real name of an author, whitespace, and an email address used in the\n-commit (enclosed by '<' and '>') to map to the name. Thus, looks like\n-this\n---\n-\tProper Name <commit@email.xx>\n---\n-\n-The more complex forms are\n---\n-\t<proper@email.xx> <commit@email.xx>\n---\n-which allows mailmap to replace only the email part of a commit, and\n---\n-\tProper Name <proper@email.xx> <commit@email.xx>\n---\n-which allows mailmap to replace both the name and the email of a\n-commit matching the specified commit email address, and\n---\n-\tProper Name <proper@email.xx> Commit Name <commit@email.xx>\n---\n-which allows mailmap to replace both the name and the email of a\n-commit matching both the specified commit name and email address.\n-\n-Example 1: Your history contains commits by two authors, Jane\n-and Joe, whose names appear in the repository under several forms:\n-\n-------------\n-Joe Developer <joe@example.com>\n-Joe R. Developer <joe@example.com>\n-Jane Doe <jane@example.com>\n-Jane Doe <jane@laptop.(none)>\n-Jane D. <jane@desktop.(none)>\n-------------\n-\n-Now suppose that Joe wants his middle name initial used, and Jane\n-prefers her family name fully spelled out. A proper `.mailmap` file\n-would look like:\n-\n-------------\n-Jane Doe         <jane@desktop.(none)>\n-Joe R. Developer <joe@example.com>\n-------------\n-\n-Note how we don't need an entry for <jane@laptop.(none)>, because the\n-real name of that author is correct already, and coalesced directly.\n-\n-Example 2: Your repository contains commits from the following\n-authors:\n-\n-------------\n-nick1 <bugs@company.xx>\n-nick2 <bugs@company.xx>\n-nick2 <nick2@company.xx>\n-santa <me@company.xx>\n-claus <me@company.xx>\n-CTO <cto@coompany.xx>\n-------------\n-\n-Then, you might want a `.mailmap` file looking like:\n-------------\n-<cto@company.xx>                       <cto@coompany.xx>\n-Some Dude <some@dude.xx>         nick1 <bugs@company.xx>\n-Other Author <other@author.xx>   nick2 <bugs@company.xx>\n-Other Author <other@author.xx>         <nick2@company.xx>\n-Santa Claus <santa.claus@northpole.xx> <me@company.xx>\n-------------\n-\n-Use hash '#' for comments that are either on their own line, or after\n-the email address.\n+\n+MAPPING AUTHORS\n+---------------\n+\n+The `.mailmap` feature is used to coalesce together commits by the same\n+person in the shortlog, where their name and/or email address was\n+spelled differently.\n+\n+include::mailmap.txt[]\n \n \n Author\ndiff --git a/Documentation/mailmap.txt b/Documentation/mailmap.txt\nnew file mode 100644\nindex 0000000..e25b154\n--- /dev/null\n+++ b/Documentation/mailmap.txt\n@@ -0,0 +1,75 @@\n+If the file `.mailmap` exists at the toplevel of the repository, or at\n+the location pointed to by the mailmap.file configuration option, it\n+is used to map author and committer names and email addresses to\n+canonical real names and email addresses.\n+\n+In the simple form, each line in the file consists of the canonical\n+real name of an author, whitespace, and an email address used in the\n+commit (enclosed by '<' and '>') to map to the name. Thus, looks like\n+this\n+--\n+\tProper Name <commit@email.xx>\n+--\n+\n+The more complex forms are\n+--\n+\t<proper@email.xx> <commit@email.xx>\n+--\n+which allows mailmap to replace only the email part of a commit, and\n+--\n+\tProper Name <proper@email.xx> <commit@email.xx>\n+--\n+which allows mailmap to replace both the name and the email of a\n+commit matching the specified commit email address, and\n+--\n+\tProper Name <proper@email.xx> Commit Name <commit@email.xx>\n+--\n+which allows mailmap to replace both the name and the email of a\n+commit matching both the specified commit name and email address.\n+\n+Example 1: Your history contains commits by two authors, Jane\n+and Joe, whose names appear in the repository under several forms:\n+\n+------------\n+Joe Developer <joe@example.com>\n+Joe R. Developer <joe@example.com>\n+Jane Doe <jane@example.com>\n+Jane Doe <jane@laptop.(none)>\n+Jane D. <jane@desktop.(none)>\n+------------\n+\n+Now suppose that Joe wants his middle name initial used, and Jane\n+prefers her family name fully spelled out. A proper `.mailmap` file\n+would look like:\n+\n+------------\n+Jane Doe         <jane@desktop.(none)>\n+Joe R. Developer <joe@example.com>\n+------------\n+\n+Note how we don't need an entry for <jane@laptop.(none)>, because the\n+real name of that author is correct already.\n+\n+Example 2: Your repository contains commits from the following\n+authors:\n+\n+------------\n+nick1 <bugs@company.xx>\n+nick2 <bugs@company.xx>\n+nick2 <nick2@company.xx>\n+santa <me@company.xx>\n+claus <me@company.xx>\n+CTO <cto@coompany.xx>\n+------------\n+\n+Then, you might want a `.mailmap` file looking like:\n+------------\n+<cto@company.xx>                       <cto@coompany.xx>\n+Some Dude <some@dude.xx>         nick1 <bugs@company.xx>\n+Other Author <other@author.xx>   nick2 <bugs@company.xx>\n+Other Author <other@author.xx>         <nick2@company.xx>\n+Santa Claus <santa.claus@northpole.xx> <me@company.xx>\n+------------\n+\n+Use hash '#' for comments that are either on their own line, or after\n+the email address.\n\\ No newline at end of file\ndiff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt\nindex 28808b7..159390c 100644\n--- a/Documentation/pretty-formats.txt\n+++ b/Documentation/pretty-formats.txt\n@@ -101,18 +101,18 @@ The placeholders are:\n - '%P': parent hashes\n - '%p': abbreviated parent hashes\n - '%an': author name\n-- '%aN': author name (respecting .mailmap)\n+- '%aN': author name (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])\n - '%ae': author email\n-- '%aE': author email (respecting .mailmap)\n+- '%aE': author email (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])\n - '%ad': author date (format respects --date= option)\n - '%aD': author date, RFC2822 style\n - '%ar': author date, relative\n - '%at': author date, UNIX timestamp\n - '%ai': author date, ISO 8601 format\n - '%cn': committer name\n-- '%cN': committer name (respecting .mailmap)\n+- '%cN': committer name (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])\n - '%ce': committer email\n-- '%cE': committer email (respecting .mailmap)\n+- '%cE': committer email (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])\n - '%cd': committer date\n - '%cD': committer date, RFC2822 style\n - '%cr': committer date, relative\n-- \n1.6.1.2.354.g9a90\n"},{"id":"103755","messageId":"498EF286.4010200@trolltech.com","threadId":"17653","inReplyTo":"cover.1234102794.git.marius@trolltech.com","subject":"Re: [PATCH v5 0/5] Extend mailmap functionality","fromName":"Marius Storm-Olsen","fromEmail":"marius@trolltech.com","sentAt":"2009-02-08T14:56:06Z","receivedAt":"2009-02-08T14:56:06Z","isPatch":true,"sender":{"key":"marius@trolltech.com","avatar":"https://gravatar.com/avatar/a40071d8f651862c6ab10bd7996f0ad84d94f06c0399de9e3fa4f06beb390a71?d=mp&s=160"},"body":"Marius Storm-Olsen said the following on 08.02.2009 15:34:\n>   v5:\n>   ---\n>   * Rename log.mailmap variable to mailmap.file\n>   * Fix mailmap tests using shortlog, by adding HEAD\n>   * Factor out the mailmap documentation, and include it from both\n>     git-shortlog.txt and git-blame.txt, since they both use mailmap\n>     directly. Link to these from pretty-format.txt, for all commands\n>     using the pretty option.\n     * Reworded the mailmap documentation a bit. Hopefully a bit easier\n       to read.\n\n--\n.marius\n"}]}