From 7c8ce308d383ce6888f69e39f0d32322600c2cc2 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 12 Dec 2012 05:59:45 -0500 Subject: [PATCH 1/5] mailmap: refactor mailmap parsing for non-file sources The read_single_mailmap function opens a mailmap file and parses each line. In preparation for having non-file mailmaps, let's pull out the line-parsing logic into its own function (read_mailmap_line), and rename the file-parsing function to match (read_mailmap_file). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- mailmap.c | 74 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/mailmap.c b/mailmap.c index ea4b471ede..89bc318de4 100644 --- a/mailmap.c +++ b/mailmap.c @@ -129,44 +129,50 @@ static char *parse_name_and_email(char *buffer, char **name, return (*right == '\0' ? NULL : right); } -static int read_single_mailmap(struct string_list *map, const char *filename, char **repo_abbrev) +static void read_mailmap_line(struct string_list *map, char *buffer, + char **repo_abbrev) +{ + char *name1 = NULL, *email1 = NULL, *name2 = NULL, *email2 = NULL; + if (buffer[0] == '#') { + static const char abbrev[] = "# repo-abbrev:"; + int abblen = sizeof(abbrev) - 1; + int len = strlen(buffer); + + if (!repo_abbrev) + return; + + if (len && buffer[len - 1] == '\n') + buffer[--len] = 0; + if (!strncmp(buffer, abbrev, abblen)) { + char *cp; + + if (repo_abbrev) + free(*repo_abbrev); + *repo_abbrev = xmalloc(len); + + for (cp = buffer + abblen; isspace(*cp); cp++) + ; /* nothing */ + strcpy(*repo_abbrev, cp); + } + return; + } + if ((name2 = parse_name_and_email(buffer, &name1, &email1, 0)) != NULL) + parse_name_and_email(name2, &name2, &email2, 1); + + if (email1) + add_mapping(map, name1, email1, name2, email2); +} + +static int read_mailmap_file(struct string_list *map, const char *filename, + char **repo_abbrev) { char buffer[1024]; FILE *f = (filename == NULL ? NULL : fopen(filename, "r")); if (f == NULL) return 1; - while (fgets(buffer, sizeof(buffer), f) != NULL) { - char *name1 = NULL, *email1 = NULL, *name2 = NULL, *email2 = NULL; - if (buffer[0] == '#') { - static const char abbrev[] = "# repo-abbrev:"; - int abblen = sizeof(abbrev) - 1; - int len = strlen(buffer); - - if (!repo_abbrev) - continue; - - if (len && buffer[len - 1] == '\n') - buffer[--len] = 0; - if (!strncmp(buffer, abbrev, abblen)) { - char *cp; - - if (repo_abbrev) - free(*repo_abbrev); - *repo_abbrev = xmalloc(len); - - for (cp = buffer + abblen; isspace(*cp); cp++) - ; /* nothing */ - strcpy(*repo_abbrev, cp); - } - continue; - } - if ((name2 = parse_name_and_email(buffer, &name1, &email1, 0)) != NULL) - parse_name_and_email(name2, &name2, &email2, 1); - - if (email1) - add_mapping(map, name1, email1, name2, email2); - } + while (fgets(buffer, sizeof(buffer), f) != NULL) + read_mailmap_line(map, buffer, repo_abbrev); fclose(f); return 0; } @@ -175,8 +181,8 @@ int read_mailmap(struct string_list *map, char **repo_abbrev) { map->strdup_strings = 1; /* each failure returns 1, so >1 means both calls failed */ - return read_single_mailmap(map, ".mailmap", repo_abbrev) + - read_single_mailmap(map, git_mailmap_file, repo_abbrev) > 1; + return read_mailmap_file(map, ".mailmap", repo_abbrev) + + read_mailmap_file(map, git_mailmap_file, repo_abbrev) > 1; } void clear_mailmap(struct string_list *map) From 086109006f695166daf2934417a20681b0c94ab8 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 12 Dec 2012 06:04:04 -0500 Subject: [PATCH 2/5] mailmap: support reading mailmap from blobs In a bare repository, there isn't a simple way to respect an in-tree mailmap without extracting it to a temporary file. This patch provides a config variable, similar to mailmap.file, which reads the mailmap from a blob in the repository. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/config.txt | 6 ++++ cache.h | 1 + config.c | 2 ++ mailmap.c | 49 +++++++++++++++++++++++++-- t/t4203-mailmap.sh | 73 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 129 insertions(+), 2 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index bf8f911e1f..376007797c 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1517,6 +1517,12 @@ mailmap.file:: subdirectory, or somewhere outside of the repository itself. See linkgit:git-shortlog[1] and linkgit:git-blame[1]. +mailmap.blob:: + Like `mailmap.file`, but consider the value as a reference to a + blob in the repository (e.g., `HEAD:.mailmap`). If both + `mailmap.file` and `mailmap.blob` are given, both are parsed, + with entries from `mailmap.file` taking precedence. + man.viewer:: Specify the programs that may be used to display help in the 'man' format. See linkgit:git-help[1]. diff --git a/cache.h b/cache.h index 18fdd18f36..a65f6d141f 100644 --- a/cache.h +++ b/cache.h @@ -1155,6 +1155,7 @@ extern int author_ident_sufficiently_given(void); extern const char *git_commit_encoding; extern const char *git_log_output_encoding; extern const char *git_mailmap_file; +extern const char *git_mailmap_blob; /* IO helper functions */ extern void maybe_flush_or_die(FILE *, const char *); diff --git a/config.c b/config.c index fb3f8681ee..97364c03fc 100644 --- a/config.c +++ b/config.c @@ -839,6 +839,8 @@ static int git_default_mailmap_config(const char *var, const char *value) { if (!strcmp(var, "mailmap.file")) return git_config_string(&git_mailmap_file, var, value); + if (!strcmp(var, "mailmap.blob")) + return git_config_string(&git_mailmap_blob, var, value); /* Add other config variables here and to Documentation/config.txt. */ return 0; diff --git a/mailmap.c b/mailmap.c index 89bc318de4..2f9c69157d 100644 --- a/mailmap.c +++ b/mailmap.c @@ -10,6 +10,7 @@ static inline void debug_mm(const char *format, ...) {} #endif const char *git_mailmap_file; +const char *git_mailmap_blob; struct mailmap_info { char *name; @@ -177,12 +178,56 @@ static int read_mailmap_file(struct string_list *map, const char *filename, return 0; } +static void read_mailmap_buf(struct string_list *map, + const char *buf, unsigned long len, + char **repo_abbrev) +{ + while (len) { + const char *end = strchrnul(buf, '\n'); + unsigned long linelen = end - buf + 1; + char *line = xmemdupz(buf, linelen); + + read_mailmap_line(map, line, repo_abbrev); + + free(line); + buf += linelen; + len -= linelen; + } +} + +static int read_mailmap_blob(struct string_list *map, + const char *name, + char **repo_abbrev) +{ + unsigned char sha1[20]; + char *buf; + unsigned long size; + enum object_type type; + + if (!name) + return 1; + if (get_sha1(name, sha1) < 0) + return 1; + + buf = read_sha1_file(sha1, &type, &size); + if (!buf) + return 1; + if (type != OBJ_BLOB) + return 1; + + read_mailmap_buf(map, buf, size, repo_abbrev); + + free(buf); + return 0; +} + int read_mailmap(struct string_list *map, char **repo_abbrev) { map->strdup_strings = 1; - /* each failure returns 1, so >1 means both calls failed */ + /* each failure returns 1, so >2 means all calls failed */ return read_mailmap_file(map, ".mailmap", repo_abbrev) + - read_mailmap_file(map, git_mailmap_file, repo_abbrev) > 1; + read_mailmap_blob(map, git_mailmap_blob, repo_abbrev) + + read_mailmap_file(map, git_mailmap_file, repo_abbrev) > 2; } void clear_mailmap(struct string_list *map) diff --git a/t/t4203-mailmap.sh b/t/t4203-mailmap.sh index 1f182f612c..e7ea40ceb6 100755 --- a/t/t4203-mailmap.sh +++ b/t/t4203-mailmap.sh @@ -149,6 +149,79 @@ test_expect_success 'No mailmap files, but configured' ' test_cmp expect actual ' +test_expect_success 'setup mailmap blob tests' ' + git checkout -b map && + test_when_finished "git checkout master" && + cat >just-bugs <<-\EOF && + Blob Guy + EOF + cat >both <<-\EOF && + Blob Guy + Blob Guy + EOF + git add just-bugs both && + git commit -m "my mailmaps" && + echo "Repo Guy " >.mailmap && + echo "Internal Guy " >internal.map +' + +test_expect_success 'mailmap.blob set' ' + cat >expect <<-\EOF && + Blob Guy (1): + second + + Repo Guy (1): + initial + + EOF + git -c mailmap.blob=map:just-bugs shortlog HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'mailmap.blob overrides .mailmap' ' + cat >expect <<-\EOF && + Blob Guy (2): + initial + second + + EOF + git -c mailmap.blob=map:both shortlog HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'mailmap.file overrides mailmap.blob' ' + cat >expect <<-\EOF && + Blob Guy (1): + second + + Internal Guy (1): + initial + + EOF + git \ + -c mailmap.blob=map:both \ + -c mailmap.file=internal.map \ + shortlog HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'mailmap.blob can be missing' ' + cat >expect <<-\EOF && + Repo Guy (1): + initial + + nick1 (1): + second + + EOF + git -c mailmap.blob=map:nonexistent shortlog HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'cleanup after mailmap.blob tests' ' + rm -f .mailmap +' + # Extended mailmap configurations should give us the following output for shortlog cat >expect <<\EOF A U Thor (1): From 938a60d64fc91fd7d646ed33fad527b724d1e534 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 12 Dec 2012 06:18:02 -0500 Subject: [PATCH 3/5] mailmap: clean up read_mailmap error handling The error handling for the read_mailmap function is odd. It returns 1 on error, rather than -1. And it treats a non-existent mailmap as an error, even though there is no reason that one needs to exist. Unless some other mailmap source loads successfully, in which case the original error is completely masked. This does not cause any bugs, however, because no caller bothers to check the return value, anyway. Let's make this a little more robust to real errors and less surprising for future callers that do check the error code: 1. Return -1 on errors. 2. Treat a missing entry (e.g., no mailmap.file given), ENOENT, or a non-existent blob (for mailmap.blob) as "no error". 3. Complain loudly when a real error (e.g., a transient I/O error, no permission to open the mailmap file, missing or corrupted blob object, etc) occurs. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- mailmap.c | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/mailmap.c b/mailmap.c index 2f9c69157d..5ffe48a604 100644 --- a/mailmap.c +++ b/mailmap.c @@ -168,10 +168,19 @@ static int read_mailmap_file(struct string_list *map, const char *filename, char **repo_abbrev) { char buffer[1024]; - FILE *f = (filename == NULL ? NULL : fopen(filename, "r")); + FILE *f; + + if (!filename) + return 0; + + f = fopen(filename, "r"); + if (!f) { + if (errno == ENOENT) + return 0; + return error("unable to open mailmap at %s: %s", + filename, strerror(errno)); + } - if (f == NULL) - return 1; while (fgets(buffer, sizeof(buffer), f) != NULL) read_mailmap_line(map, buffer, repo_abbrev); fclose(f); @@ -205,15 +214,15 @@ static int read_mailmap_blob(struct string_list *map, enum object_type type; if (!name) - return 1; + return 0; if (get_sha1(name, sha1) < 0) - return 1; + return 0; buf = read_sha1_file(sha1, &type, &size); if (!buf) - return 1; + return error("unable to read mailmap object at %s", name); if (type != OBJ_BLOB) - return 1; + return error("mailmap is not a blob: %s", name); read_mailmap_buf(map, buf, size, repo_abbrev); @@ -223,11 +232,12 @@ static int read_mailmap_blob(struct string_list *map, int read_mailmap(struct string_list *map, char **repo_abbrev) { + int err = 0; map->strdup_strings = 1; - /* each failure returns 1, so >2 means all calls failed */ - return read_mailmap_file(map, ".mailmap", repo_abbrev) + - read_mailmap_blob(map, git_mailmap_blob, repo_abbrev) + - read_mailmap_file(map, git_mailmap_file, repo_abbrev) > 2; + err |= read_mailmap_file(map, ".mailmap", repo_abbrev); + err |= read_mailmap_blob(map, git_mailmap_blob, repo_abbrev); + err |= read_mailmap_file(map, git_mailmap_file, repo_abbrev); + return err; } void clear_mailmap(struct string_list *map) From d5422b0c0ba9948166115f1b178c34ee65fc3057 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 13 Dec 2012 08:08:12 -0500 Subject: [PATCH 4/5] mailmap: fix some documentation loose-ends for mailmap.blob Anywhere we mention mailmap.file, it is probably worth mentioning mailmap.blob, as well. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/git-log.txt | 2 +- Documentation/mailmap.txt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index 585dac40ba..08a185db7f 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -167,7 +167,7 @@ log.showroot:: `git log -p` output would be shown without a diff attached. The default is `true`. -mailmap.file:: +mailmap.*:: See linkgit:git-shortlog[1]. notes.displayRef:: diff --git a/Documentation/mailmap.txt b/Documentation/mailmap.txt index 288f04e70c..bb349c2db3 100644 --- a/Documentation/mailmap.txt +++ b/Documentation/mailmap.txt @@ -1,5 +1,6 @@ If the file `.mailmap` exists at the toplevel of the repository, or at -the location pointed to by the mailmap.file configuration option, it +the location pointed to by the mailmap.file or mailmap.blob +configuration options, it is used to map author and committer names and email addresses to canonical real names and email addresses. From 8c473cecfd8835c2bdf34b323e1b2de620099c04 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 13 Dec 2012 08:04:47 -0500 Subject: [PATCH 5/5] mailmap: default mailmap.blob in bare repositories The motivation for mailmap.blob is to let users of bare repositories use the mailmap feature, as they would not have a checkout containing the .mailmap file. We can make it even easier for them by just looking in HEAD:.mailmap by default. We can't know for sure that this is where they would keep a mailmap, of course, but it is the best guess (and it matches the non-bare behavior, which reads from HEAD:.mailmap in the working tree). If it's missing, git will silently ignore the setting. We do not do the same magic in the non-bare case, because: 1. In the common case, HEAD:.mailmap will be the same as the .mailmap in the working tree, which is a no-op. 2. In the uncommon case, the user has modified .mailmap but not yet committed it, and would expect the working tree version to take precedence. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/config.txt | 8 +++++--- mailmap.c | 5 +++++ t/t4203-mailmap.sh | 25 +++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 376007797c..1a3c554dbf 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1519,9 +1519,11 @@ mailmap.file:: mailmap.blob:: Like `mailmap.file`, but consider the value as a reference to a - blob in the repository (e.g., `HEAD:.mailmap`). If both - `mailmap.file` and `mailmap.blob` are given, both are parsed, - with entries from `mailmap.file` taking precedence. + blob in the repository. If both `mailmap.file` and + `mailmap.blob` are given, both are parsed, with entries from + `mailmap.file` taking precedence. In a bare repository, this + defaults to `HEAD:.mailmap`. In a non-bare repository, it + defaults to empty. man.viewer:: Specify the programs that may be used to display help in the diff --git a/mailmap.c b/mailmap.c index 5ffe48a604..b16542febe 100644 --- a/mailmap.c +++ b/mailmap.c @@ -233,7 +233,12 @@ static int read_mailmap_blob(struct string_list *map, int read_mailmap(struct string_list *map, char **repo_abbrev) { int err = 0; + map->strdup_strings = 1; + + if (!git_mailmap_blob && is_bare_repository()) + git_mailmap_blob = "HEAD:.mailmap"; + err |= read_mailmap_file(map, ".mailmap", repo_abbrev); err |= read_mailmap_blob(map, git_mailmap_blob, repo_abbrev); err |= read_mailmap_file(map, git_mailmap_file, repo_abbrev); diff --git a/t/t4203-mailmap.sh b/t/t4203-mailmap.sh index e7ea40ceb6..aae30d97b1 100755 --- a/t/t4203-mailmap.sh +++ b/t/t4203-mailmap.sh @@ -218,6 +218,31 @@ test_expect_success 'mailmap.blob can be missing' ' test_cmp expect actual ' +test_expect_success 'mailmap.blob defaults to off in non-bare repo' ' + git init non-bare && + ( + cd non-bare && + test_commit one .mailmap "Fake Name " && + echo " 1 Fake Name" >expect && + git shortlog -ns HEAD >actual && + test_cmp expect actual && + rm .mailmap && + echo " 1 A U Thor" >expect && + git shortlog -ns HEAD >actual && + test_cmp expect actual + ) +' + +test_expect_success 'mailmap.blob defaults to HEAD:.mailmap in bare repo' ' + git clone --bare non-bare bare && + ( + cd bare && + echo " 1 Fake Name" >expect && + git shortlog -ns HEAD >actual && + test_cmp expect actual + ) +' + test_expect_success 'cleanup after mailmap.blob tests' ' rm -f .mailmap '