{"thread":{"id":"17504","subject":"[PATCH v3 1/4] Add log.mailmap as configurational option for mailmap location","startedAt":"2009-02-02T14:32:06Z","lastAt":"2009-02-04T09:24:41Z","messageCount":9,"participants":["Marius Storm-Olsen","Junio C Hamano","Nanako Shiraishi"],"isPatch":true,"patchVersion":3,"patchTotal":4},"messages":[{"id":"102863","messageId":"cover.1233584536.git.marius@trolltech.com","threadId":"17504","inReplyTo":null,"subject":"[PATCH v3 0/4] Extend mailmap functionality","fromName":"Marius Storm-Olsen","fromEmail":"marius@trolltech.com","sentAt":"2009-02-02T14:32:06Z","receivedAt":"2009-02-02T14:32:06Z","isPatch":true,"sender":{"key":"marius@trolltech.com","avatar":"https://gravatar.com/avatar/a40071d8f651862c6ab10bd7996f0ad84d94f06c0399de9e3fa4f06beb390a71?d=mp&s=160"},"body":"  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. So, should you have done commits with faulty\naddress, or if an old email simply isn't valid anymore, you can add\na mapping for that to replace it. So, the old style mapping is\n    Proper Name <commit@email.xx>\n\nwhile this patch series adds support for\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, and test run shows no\nregressions.\n\nMarius Storm-Olsen (4):\n  Add log.mailmap 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\n Documentation/config.txt         |    8 ++\n Documentation/git-shortlog.txt   |   61 ++++++++---\n Documentation/pretty-formats.txt |    2 +\n builtin-blame.c                  |   52 ++++++----\n builtin-shortlog.c               |   25 ++++-\n cache.h                          |    1 +\n config.c                         |   10 ++\n mailmap.c                        |  210 ++++++++++++++++++++++++++++++++++----\n mailmap.h                        |    6 +-\n pretty.c                         |   59 ++++++-----\n string-list.c                    |   43 +++++++-\n string-list.h                    |    9 ++\n t/t4203-mailmap.sh               |  202 ++++++++++++++++++++++++++++++++++++\n 13 files changed, 593 insertions(+), 95 deletions(-)\n create mode 100755 t/t4203-mailmap.sh\n"},{"id":"102862","messageId":"36adb0d2cc4c20c4efb3b03a912b27e67c166855.1233584536.git.marius@trolltech.com","threadId":"17504","inReplyTo":"cover.1233584536.git.marius@trolltech.com","subject":"[PATCH v3 1/4] Add log.mailmap as configurational option for mailmap location","fromName":"Marius Storm-Olsen","fromEmail":"marius@trolltech.com","sentAt":"2009-02-02T14:32:07Z","receivedAt":"2009-02-02T14:32:07Z","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 log.mailmap will override the entries\nin \"./.mailmap\", should they match the same.\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                       |   10 ++++\n mailmap.c                      |   12 ++++-\n mailmap.h                      |    2 +-\n pretty.c                       |    2 +-\n t/t4203-mailmap.sh             |  109 ++++++++++++++++++++++++++++++++++++++++\n 10 files changed, 145 insertions(+), 7 deletions(-)\n create mode 100755 t/t4203-mailmap.sh\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex e2b8775..1334d36 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1012,6 +1012,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+log.mailmap::\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 8f7c0e2..cacbeea 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 aae14ef..19edcf3 100644\n--- a/builtin-blame.c\n+++ b/builtin-blame.c\n@@ -2394,7 +2394,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 45e713e..3eef7ea 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -867,6 +867,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_log_mailmap;\n \n /* IO helper functions */\n extern void maybe_flush_or_die(FILE *, const char *);\ndiff --git a/config.c b/config.c\nindex 790405a..9ebcbbe 100644\n--- a/config.c\n+++ b/config.c\n@@ -565,6 +565,13 @@ static int git_default_branch_config(const char *var, const char *value)\n \treturn 0;\n }\n \n+static int git_default_log_config(const char *var, const char *value)\n+{\n+\tif (!strcmp(var, \"log.mailmap\"))\n+\t\treturn git_config_string(&git_log_mailmap, var, value);\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@@ -579,6 +586,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, \"log.\"))\n+\t\treturn git_default_log_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..5aaee91 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_log_mailmap;\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_log_mailmap, 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 cc460b5..9e03d6a 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -312,7 +312,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..2eded8e\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 >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 >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 'log.mailmap set' '\n+\tmkdir internal_mailmap &&\n+\techo \"Internal Guy <bugs@company.xx>\" > internal_mailmap/.mailmap &&\n+\tgit config log.mailmap internal_mailmap/.mailmap &&\n+\tgit shortlog >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 'log.mailmap override' '\n+\techo \"External Guy <author@example.com>\" >> internal_mailmap/.mailmap &&\n+\tgit config log.mailmap internal_mailmap/.mailmap &&\n+\tgit shortlog >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 'log.mailmap file non-existant' '\n+\trm internal_mailmap/.mailmap &&\n+\trmdir internal_mailmap &&\n+\tgit shortlog >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 >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\n-- \n1.6.1.2.257.g84fd75\n"},{"id":"102865","messageId":"76e643ff7e99985ce46b6bf36ec967124d7fc6aa.1233584536.git.marius@trolltech.com","threadId":"17504","inReplyTo":"36adb0d2cc4c20c4efb3b03a912b27e67c166855.1233584536.git.marius@trolltech.com","subject":"[PATCH v3 2/4] 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-02T14:32:08Z","receivedAt":"2009-02-02T14:32:08Z","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.257.g84fd75\n"},{"id":"102864","messageId":"53facc1773745ae477f15103718e08ea6a5ae58d.1233584536.git.marius@trolltech.com","threadId":"17504","inReplyTo":"76e643ff7e99985ce46b6bf36ec967124d7fc6aa.1233584536.git.marius@trolltech.com","subject":"[PATCH v3 3/4] Add map_user() and clear_mailmap() to mailmap","fromName":"Marius Storm-Olsen","fromEmail":"marius@trolltech.com","sentAt":"2009-02-02T14:32:09Z","receivedAt":"2009-02-02T14:32:09Z","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.\nThe possible mailmap definitions now are:\n  proper_name <commit_email>                             // Old 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 |   58 +++++++++---\n mailmap.c                      |  198 ++++++++++++++++++++++++++++++++++++----\n mailmap.h                      |    4 +\n 3 files changed, 227 insertions(+), 33 deletions(-)\n\ndiff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt\nindex cacbeea..d64dabf 100644\n--- a/Documentation/git-shortlog.txt\n+++ b/Documentation/git-shortlog.txt\n@@ -50,20 +50,29 @@ FILES\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+it is used to map author and committer names and email addresses to\n+canonical real names and email addresses.\n+This can be used to coalesce together commits by the same person where\n+their name and/or email address was spelled differently.\n+\n+In the simple form, each line in the file consists of the canonical real name\n+of an author, whitespace, and an email address used in the commit\n+(enclosed by '<' and '>') to map to the name. Thus, looks like this\n+--\n+\tProper Name <commit@email.xx>\n+--\n+\n+The more complex forms are\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 commit\n+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 commit\n+matching the specified commit name and email address.\n \n So, for example, if your history contains commits by two authors, Jane\n and Joe, whose names appear in the repository under several forms:\n@@ -86,6 +95,27 @@ Jane Doe <jane@desktop.(none)>\n Joe R. Developer <joe@random.com>\n ------------\n \n+Now, suppose your repository contains commits from the following 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+------------\n+\n+Then, you might want a `.mailmap` file looking like:\n+------------\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, either on their own line, or after 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 5aaee91..f593ff0 100644\n--- a/mailmap.c\n+++ b/mailmap.c\n@@ -2,7 +2,87 @@\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+inline void debug_mm(const char *format, ...) {}\n+#endif\n+\n const char *git_log_mailmap;\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+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+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+void add_mapping(struct string_list *map,\n+\t\t char *name1, char *email1, char *name2, char *email2)\n+{\n+\tstruct mailmap_entry *me;\n+\tint index = string_list_find_insert_index(map, email1, 1);\n+\tif (index < 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, email1, map);\n+\t\titem->util = xmalloc(sizeof(struct mailmap_entry));\n+\t\tmemset(item->util, 0, sizeof(struct mailmap_entry));\n+\t}\n+\tme = (struct mailmap_entry *)map->items[index].util;\n+\n+\tif (name2 == NULL) {\n+\t\tdebug_mm(\"mailmap: adding (simple) entry for %s at index %d\\n\", email1, 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\tme->name = xstrdup(name1);\n+\t\tif (email2)\n+\t\t\tme->email = xstrdup(email2);\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\", email1, index);\n+\t\tmi->name = xstrdup(name2);\n+\t\tif (email2)\n+\t\t\tmi->email = xstrdup(email2);\n+\t\tstring_list_insert(name1, &me->namemap)->util = mi;\n+\t}\n+\n+\tdebug_mm(\"mailmap:  '%s' <%s> -> '%s' <%s>\\n\",\n+\t\t name2 ? name1 : \"\", email1,\n+\t\t name2 ? name2 : name1, email2 ? email2 : \"\");\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,8 +91,8 @@ 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\tchar *end_of_name, *left_bracket1, *right_bracket1, *left_bracket2, *right_bracket2;\n+\t\tchar *name1, *email1, *name2, *email2;\n \t\tint i;\n \t\tif (buffer[0] == '#') {\n \t\t\tstatic const char abbrev[] = \"# repo-abbrev:\";\n@@ -37,25 +117,65 @@ 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/* Locate name and email */\n+\t\tif ((left_bracket1 = strchr(buffer, '<')) == NULL)\n \t\t\tcontinue;\n-\t\tif ((right_bracket = strchr(left_bracket + 1, '>')) == NULL)\n+\t\tif ((right_bracket1 = strchr(left_bracket1 + 1, '>')) == NULL)\n \t\t\tcontinue;\n-\t\tif (right_bracket == left_bracket + 1)\n+\t\tif (right_bracket1 == left_bracket1 + 1)\n \t\t\tcontinue;\n-\t\tfor (end_of_name = left_bracket;\n+\t\tfor (end_of_name = left_bracket1;\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\tname1 = xmalloc(end_of_name - buffer + 1);\n+\t\tstrlcpy(name1, buffer, end_of_name - buffer + 1);\n+\t\temail1 = xmalloc(right_bracket1 - left_bracket1);\n+\t\tfor (i = 0; i < right_bracket1 - left_bracket1 - 1; i++)\n+\t\t\temail1[i] = tolower(left_bracket1[i + 1]);\n+\t\temail1[right_bracket1 - left_bracket1 - 1] = '\\0';\n+\n+\t\t/* Locate 2nd name and email. Possible mappings in mailmap file are:\n+\t\t *   proper_name <commit_email>\n+\t\t *   proper_name <proper_email> <commit_email>\n+\t\t *   proper_name <proper_email> commit_name <commit_email>\n+\t\t */\n+\t\tdo {\n+\t\t\temail2 = name2 = 0;\n+\t\t\tright_bracket1 += 1;\n+\t\t\tif ((left_bracket2 = strchr(right_bracket1, '<')) == NULL)\n+\t\t\t\tcontinue;\n+\t\t\tif ((right_bracket2 = strchr(left_bracket2 + 1, '>')) == NULL)\n+\t\t\t\tcontinue;\n+\t\t\tif (right_bracket2 == left_bracket2 + 1)\n+\t\t\t\tcontinue;\n+\t\t\tfor (end_of_name = left_bracket2;\n+\t\t\t     end_of_name != right_bracket1 && isspace(end_of_name[-1]);\n+\t\t\t     end_of_name--)\n+\t\t\t\t; /* keep on looking for name end */\n+\t\t\tfor (;\n+\t\t\t     end_of_name != right_bracket1 && isspace(right_bracket1[0]);\n+\t\t\t     right_bracket1++)\n+\t\t\t\t; /* keep on looking for name start */\n+\t\t\tif (end_of_name != right_bracket1) {\n+\t\t\t\tname2 = xmalloc(end_of_name - right_bracket1 + 1);\n+\t\t\t\tstrlcpy(name2, right_bracket1, end_of_name - right_bracket1 + 1);\n+\t\t\t\tchar *tmp = name1;\n+\t\t\t\tname1 = name2;\n+\t\t\t\tname2 = tmp;\n+\t\t\t}\n+\t\t\temail2 = xmalloc(right_bracket2 - left_bracket2);\n+\t\t\tfor (i = 0; i < right_bracket2 - left_bracket2 - 1; i++)\n+\t\t\t\temail2[i] = tolower(left_bracket2[i + 1]);\n+\t\t\temail2[right_bracket2 - left_bracket2 - 1] = '\\0';\n+\t\t\tchar *tmp = email1;\n+\t\t\temail1 = email2;\n+\t\t\temail2 = tmp;\n+\t\t} while(0);\n+\n+\t\tadd_mapping(map, name1, email1, name2, email2);\n \t}\n \tfclose(f);\n \treturn 0;\n@@ -68,17 +188,31 @@ int read_mailmap(struct string_list *map, char **repo_abbrev)\n \t       read_single_mailmap(map, git_log_mailmap, 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 +222,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->email == NULL && mi->name == NULL) {\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 ? email : mailbuf);\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.257.g84fd75\n"},{"id":"102866","messageId":"42ecc3c2bea8b8113a772e9e83750ab0bdc6b596.1233584536.git.marius@trolltech.com","threadId":"17504","inReplyTo":"53facc1773745ae477f15103718e08ea6a5ae58d.1233584536.git.marius@trolltech.com","subject":"[PATCH v3 4/4] 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-02T14:32:10Z","receivedAt":"2009-02-02T14:32:10Z","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               |   93 ++++++++++++++++++++++++++++++++++++++\n 5 files changed, 173 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 19edcf3..f3be9fa 100644\n--- a/builtin-blame.c\n+++ b/builtin-blame.c\n@@ -1263,11 +1263,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@@ -1278,10 +1279,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@@ -1304,9 +1306,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@@ -1315,20 +1318,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@@ -1337,8 +1343,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@@ -1356,9 +1364,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@@ -1366,9 +1376,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 9e03d6a..29f81c3 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -305,23 +305,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@@ -332,6 +323,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@@ -345,25 +339,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 2eded8e..798348f 100755\n--- a/t/t4203-mailmap.sh\n+++ b/t/t4203-mailmap.sh\n@@ -106,4 +106,97 @@ 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+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+\tmkdir internal_mailmap &&\n+\techo \"Committed <committer@example.com>\" > 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 >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 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+#  e0f60492 (Some Dude    2005-04-07 15:14:13 -0700 2) two\n+#  4db1a3df (Other Author 2005-04-07 15:15:13 -0700 3) three\n+#  e3718380 (Other Author 2005-04-07 15:16:13 -0700 4) four\n+#  25e4bf3d (Santa Claus  2005-04-07 15:17:13 -0700 5) five\n+#  17c39712 (Santa Claus  2005-04-07 15:18:13 -0700 6) six\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.257.g84fd75\n"},{"id":"103072","messageId":"7v3aeu66hv.fsf@gitster.siamese.dyndns.org","threadId":"17504","inReplyTo":"53facc1773745ae477f15103718e08ea6a5ae58d.1233584536.git.marius@trolltech.com","subject":"Re: [PATCH v3 3/4] Add map_user() and clear_mailmap() to mailmap","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-02-04T06:50:04Z","receivedAt":"2009-02-04T06:50:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Marius Storm-Olsen <marius@trolltech.com> writes:\n\n> map_user() allows to lookup and replace both email and\n> name of a user, based on a new style mailmap file.\n> The possible mailmap definitions now are:\n>   proper_name <commit_email>                             // Old style\n>   proper_name <proper_email> <commit_email>              // New style\n>   proper_name <proper_email> commit_name <commit_email>  // New style\n>\n> map_email() operates the same as before, with the\n> exception that it also will to try to match on a name\n> passed in through the name return buffer.\n>\n> clear_mailmap() is needed to now clear the more complex\n> mailmap structure.\n\nUse of ample paragraph breaks would make it much easier to eyes.\n\n    map_user() allows to lookup and replace both email and name of a user,\n    based on a new style mailmap file.\n\n    The possible mailmap definitions now are:\n\n      proper_name <commit_email>                             # Old style\n      proper_name <proper_email> <commit_email>              # New style\n      proper_name <proper_email> commit_name <commit_email>  # New style\n\n    map_email() operates the same as before, with the exception that it also\n    will to try to match on a name passed in through the name return buffer.\n\n    clear_mailmap() is needed to now clear the more complex mailmap structure.\n\nAlso, the example shows how lines would look line in a mailmap file.  I\nwould avoid giving a false impression that the parser should take C++\nstyle comment introducer // by using '#' which is a documented one (I\nsuspect anything that follows the last angle bracket is simply ignored,\nthough).\n\n> @@ -86,6 +95,27 @@ Jane Doe <jane@desktop.(none)>\n>  Joe R. Developer <joe@random.com>\n\nThis context line was updated a few days ago (not a big deal, just in case\nyou didn't know).\n\n> diff --git a/mailmap.c b/mailmap.c\n> index 5aaee91..f593ff0 100644\n> --- a/mailmap.c\n> +++ b/mailmap.c\n> @@ -2,7 +2,87 @@\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> +inline void debug_mm(const char *format, ...) {}\n> +#endif\n\n\"static inline void ...\";\n\n> @@ -37,25 +117,65 @@ static int read_single_mailmap(struct string_list *map, const char *filename, ch\n>...\n> +\t\t/* Locate 2nd name and email. Possible mappings in mailmap file are:\n> +\t\t *   proper_name <commit_email>\n> +\t\t *   proper_name <proper_email> <commit_email>\n> +\t\t *   proper_name <proper_email> commit_name <commit_email>\n> +\t\t */\n\n\t/*\n         * We tend to write a multi line comment block\n         * like this.\n         */\n\n> +\t\tdo {\n> +\t\t\temail2 = name2 = 0;\n> +\t\t\tright_bracket1 += 1;\n> +\t\t\tif ((left_bracket2 = strchr(right_bracket1, '<')) == NULL)\n> +\t\t\t\tcontinue;\n> +\t\t\tif ((right_bracket2 = strchr(left_bracket2 + 1, '>')) == NULL)\n> +\t\t\t\tcontinue;\n> +\t\t\tif (right_bracket2 == left_bracket2 + 1)\n> +\t\t\t\tcontinue;\n> +\t\t\tfor (end_of_name = left_bracket2;\n> +\t\t\t     end_of_name != right_bracket1 && isspace(end_of_name[-1]);\n> +\t\t\t     end_of_name--)\n> +\t\t\t\t; /* keep on looking for name end */\n> +\t\t\tfor (;\n> +\t\t\t     end_of_name != right_bracket1 && isspace(right_bracket1[0]);\n> +\t\t\t     right_bracket1++)\n> +\t\t\t\t; /* keep on looking for name start */\n> +\t\t\tif (end_of_name != right_bracket1) {\n> +\t\t\t\tname2 = xmalloc(end_of_name - right_bracket1 + 1);\n> +\t\t\t\tstrlcpy(name2, right_bracket1, end_of_name - right_bracket1 + 1);\n> +\t\t\t\tchar *tmp = name1;\n> +\t\t\t\tname1 = name2;\n> +\t\t\t\tname2 = tmp;\n> +\t\t\t}\n> +\t\t\temail2 = xmalloc(right_bracket2 - left_bracket2);\n> +\t\t\tfor (i = 0; i < right_bracket2 - left_bracket2 - 1; i++)\n> +\t\t\t\temail2[i] = tolower(left_bracket2[i + 1]);\n> +\t\t\temail2[right_bracket2 - left_bracket2 - 1] = '\\0';\n> +\t\t\tchar *tmp = email1;\n> +\t\t\temail1 = email2;\n> +\t\t\temail2 = tmp;\n> +\t\t} while(0);\n\nYuck.  Is it just me or is this new codeblock especially denser than existing\ncode?  I wonder use of a few smaller helper functions (that the compiler\nmay be able to inline without being told for us) would make this easier to read\nwithout funny-looking \"do { ... if (...) continue; } while (0)\" trick?\n\nTwo \"char *tmp\" in this scope are both decl-after-statement errors.\n"},{"id":"103087","messageId":"498956E9.3050606@trolltech.com","threadId":"17504","inReplyTo":"7v3aeu66hv.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH v3 3/4] Add map_user() and clear_mailmap() to mailmap","fromName":"Marius Storm-Olsen","fromEmail":"marius@trolltech.com","sentAt":"2009-02-04T08:50:49Z","receivedAt":"2009-02-04T08:50:49Z","isPatch":true,"sender":{"key":"marius@trolltech.com","avatar":"https://gravatar.com/avatar/a40071d8f651862c6ab10bd7996f0ad84d94f06c0399de9e3fa4f06beb390a71?d=mp&s=160"},"body":"Junio C Hamano said the following on 04.02.2009 07:50:\n> Marius Storm-Olsen <marius@trolltech.com> writes:\n> Use of ample paragraph breaks would make it much easier to eyes.\n\nOk.\n\n> Also, the example shows how lines would look line in a mailmap\n> file.  I would avoid giving a false impression that the parser\n> should take C++ style comment introducer // by using '#' which is a\n> documented one (I suspect anything that follows the last angle\n> bracket is simply ignored, though).\n\nRight, it is, but I agree with your point.\n\n\n>> @@ -86,6 +95,27 @@ Jane Doe <jane@desktop.(none)>\n>>  Joe R. Developer <joe@random.com>\n> \n> This context line was updated a few days ago (not a big deal, just in case\n> you didn't know).\n\nRight, I saw the patch on the list, but I based the patch series on \nmaster, which I don't think had the update at the time? Anyways, do \nyou prefer the patches based on next instead? \n(Documentation/SubmittingPatches says master, but maybe that has changed)\n\n\n>> +#if DEBUG_MAILMAP\n>> +#define debug_mm(...) fprintf(stderr, __VA_ARGS__)\n>> +#else\n>> +inline void debug_mm(const char *format, ...) {}\n>> +#endif\n> \n> \"static inline void ...\";\n\nSure. (I seriously hope that the compiler optimizes that empty \nfunction call away for me though, without specifying inline :-)\n\n\n>> @@ -37,25 +117,65 @@ static int read_single_mailmap(struct string_list *map, const char *filename, ch\n>> ...\n>> +\t\t/* Locate 2nd name and email. Possible mappings in mailmap file are:\n>> +\t\t *   proper_name <commit_email>\n>> +\t\t *   proper_name <proper_email> <commit_email>\n>> +\t\t *   proper_name <proper_email> commit_name <commit_email>\n>> +\t\t */\n> \n> \t/*\n>          * We tend to write a multi line comment block\n>          * like this.\n>          */\n\nOk.\n\n>> +\t\tdo {\n...\n>> +\t\t\tif ((left_bracket2 = strchr(right_bracket1, '<')) == NULL)\n>> +\t\t\t\tcontinue;\n...\n>> +\t\t} while(0);\n> \n> Yuck.  Is it just me or is this new codeblock especially denser\n> than existing code?  I wonder use of a few smaller helper functions\n> (that the compiler may be able to inline without being told for us)\n> would make this easier to read without funny-looking \"do { ... if\n> (...) continue; } while (0)\" trick?\n\nHeh, It was mostly a copy'n'paste from the previous code block, with \nminor tweaks and variable renaming. I'll factor things out to make it \nan easier read.\n\n> Two \"char *tmp\" in this scope are both decl-after-statement errors.\n\nYikes! I wonder why I never got any compiler notification about those. \nThey should never have been there, sorry.\n\n-- \n.marius [@trolltech.com]\n'if you know what you're doing, it's not research'\n\n"},{"id":"103092","messageId":"20090204181520.6117@nanako3.lavabit.com","threadId":"17504","inReplyTo":"498956E9.3050606@trolltech.com","subject":"Re: [PATCH v3 3/4] Add map_user() and clear_mailmap() to mailmap","fromName":"Nanako Shiraishi","fromEmail":"nanako3@lavabit.com","sentAt":"2009-02-04T09:15:20Z","receivedAt":"2009-02-04T09:15:20Z","isPatch":true,"sender":{"key":"nanako3@lavabit.com","avatar":"https://gravatar.com/avatar/3777b9e201c5883a62b1a6fdf7c53f2d712d1d80989146063ea861e33aad72a8?d=mp&s=160"},"body":"Quoting Marius Storm-Olsen <marius@trolltech.com>\n\n>>> @@ -86,6 +95,27 @@ Jane Doe <jane@desktop.(none)>\n>>>  Joe R. Developer <joe@random.com>\n>>\n>> This context line was updated a few days ago (not a big deal, just in case\n>> you didn't know).\n>\n> Right, I saw the patch on the list, but I based the patch series on\n> master, which I don't think had the update at the time? Anyways, do\n> you prefer the patches based on next instead?\n> (Documentation/SubmittingPatches says master, but maybe that has\n> changed)\n\nAccording to \"What's in git.git (Feb 2009, #01; Sun, 01), the commit has\nbeen in master for the past few days.\n\n-- \nNanako Shiraishi\nhttp://ivory.ap.teacup.com/nanako3/\n"},{"id":"103094","messageId":"49895ED9.90209@trolltech.com","threadId":"17504","inReplyTo":"20090204181520.6117@nanako3.lavabit.com","subject":"Re: [PATCH v3 3/4] Add map_user() and clear_mailmap() to mailmap","fromName":"Marius Storm-Olsen","fromEmail":"marius@trolltech.com","sentAt":"2009-02-04T09:24:41Z","receivedAt":"2009-02-04T09:24:41Z","isPatch":true,"sender":{"key":"marius@trolltech.com","avatar":"https://gravatar.com/avatar/a40071d8f651862c6ab10bd7996f0ad84d94f06c0399de9e3fa4f06beb390a71?d=mp&s=160"},"body":"Nanako Shiraishi said the following on 04.02.2009 10:15:\n>> Right, I saw the patch on the list, but I based the patch series\n>> on master, which I don't think had the update at the time?\n>> Anyways, do you prefer the patches based on next instead? \n>> (Documentation/SubmittingPatches says master, but maybe that has \n>> changed)\n> \n> According to \"What's in git.git (Feb 2009, #01; Sun, 01), the\n> commit has been in master for the past few days.\n\nAh, I see my series was based on master at a34a9dbbced, which was on \nthe 29th of January.\n\n-- \n.marius [@trolltech.com]\n'if you know what you're doing, it's not research'\n\n"}]}