diff options
context:
space:
mode:
authorBryce Kwon <bryce@brycekwon.com>
committerBryce Kwon <bryce@brycekwon.com>
commit
parent
tree
download
Apply the mailmap at HEAD to every ident
-rw-r--r--MANUAL.txt13
-rw-r--r--custom/cgitrc9
-rw-r--r--source/cgit.c6
-rw-r--r--source/cgit.h2
-rw-r--r--source/parsing.c41
-rw-r--r--source/parsing.h1
-rw-r--r--source/shared.c1
-rw-r--r--source/ui-log.c2
-rwxr-xr-xtests/t0208-mailmap.sh86
9 files changed, 153 insertions, 8 deletions
diff --git a/MANUAL.txt b/MANUAL.txt
index fc91125..aa581f0 100644
--- a/MANUAL.txt
+++ b/MANUAL.txt
@@ -235,6 +235,15 @@ enable-log-linecount::
and removed lines for each commit on the repository log page. Default
value: "0".
+enable-mailmap::
+ Flag which, when set to "1", applies the repository's mailmap to every
+ author, committer and tagger shown, so a person who changed name or
+ address appears under one identity in the log, commit, tag, blame and
+ statistics pages, the atom feed and the author search. Only the
+ .mailmap blob at HEAD is read, as git does for a bare repository, and
+ the mailmap.file and mailmap.blob settings are ignored. Default value:
+ "1". See also: "repo.enable-mailmap".
+
enable-plain-email::
Flag which, when set to "0", hides the full author, committer and tagger
email addresses wherever they would be shown beside the name. Default
@@ -614,6 +623,10 @@ repo.enable-log-linecount::
A flag which can be used to override the global setting
"enable-log-linecount". Default value: <enable-log-linecount>.
+repo.enable-mailmap::
+ A flag which can be used to override the global setting
+ "enable-mailmap". Default value: <enable-mailmap>.
+
repo.enable-remote-branches::
A flag which can be used to override the global setting
"enable-remote-branches". Default value: <enable-remote-branches>.
diff --git a/custom/cgitrc b/custom/cgitrc
index 7ba1e13..9684a0b 100644
--- a/custom/cgitrc
+++ b/custom/cgitrc
@@ -201,6 +201,11 @@ enable-log-filecount=0
# Default is 0.
enable-log-linecount=0
+# Apply the .mailmap at HEAD to every author, committer and tagger shown, so a
+# person who changed name or address appears under one identity. Values are 0
+# or 1. Default is 1.
+enable-mailmap=1
+
# Show ages in the log, refs and index columns as an elapsed time such as "3
# days" rather than a calendar date. Values are 0 or 1. Default is 1.
enable-relative-dates=1
@@ -497,6 +502,10 @@ enable-plain-email=1
# global enable-log-linecount value.
#repo.enable-log-linecount=0
+# Per-repo override of enable-mailmap. Values are 0 or 1. Default is the global
+# enable-mailmap value.
+#repo.enable-mailmap=1
+
# Per-repo override of enable-remote-branches. Values are 0 or 1. Default is the
# global enable-remote-branches value.
#repo.enable-remote-branches=0
diff --git a/source/cgit.c b/source/cgit.c
index ae91dd2..36f3b25 100644
--- a/source/cgit.c
+++ b/source/cgit.c
@@ -107,6 +107,7 @@ static void prepare_context(void)
ctx.cfg.local_time = 0;
ctx.cfg.date_mode = date_mode_from_type(DATE_SHORT);
ctx.cfg.enable_header = 1;
+ ctx.cfg.enable_mailmap = 1;
ctx.cfg.enable_plain_email = 1;
ctx.cfg.enable_relative_dates = 1;
ctx.cfg.enable_http_clone = 1;
@@ -239,6 +240,7 @@ static void print_repo(FILE *f, struct cgit_repo *repo)
fprintf(f, "repo.enable-gitmodules-links=%d\n", repo->enable_gitmodules_links);
fprintf(f, "repo.enable-log-filecount=%d\n", repo->enable_log_filecount);
fprintf(f, "repo.enable-log-linecount=%d\n", repo->enable_log_linecount);
+ fprintf(f, "repo.enable-mailmap=%d\n", repo->enable_mailmap);
if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter)
cgit_fprintf_filter(repo->about_filter, f, "repo.about-filter=");
if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter)
@@ -557,6 +559,8 @@ static void apply_config(const char *name, const char *value)
ctx.cfg.enable_log_filecount = atoi(value);
else if (!strcmp(name, "enable-log-linecount"))
ctx.cfg.enable_log_linecount = atoi(value);
+ else if (!strcmp(name, "enable-mailmap"))
+ ctx.cfg.enable_mailmap = atoi(value);
else if (!strcmp(name, "enable-relative-dates"))
ctx.cfg.enable_relative_dates = atoi(value);
else if (!strcmp(name, "enable-remote-branches"))
@@ -1168,6 +1172,8 @@ void cgit_repo_config(struct cgit_repo *repo, const char *name, const char *valu
repo->enable_log_filecount = atoi(value);
else if (!strcmp(name, "enable-log-linecount"))
repo->enable_log_linecount = atoi(value);
+ else if (!strcmp(name, "enable-mailmap"))
+ repo->enable_mailmap = atoi(value);
else if (!strcmp(name, "enable-remote-branches"))
repo->enable_remote_branches = atoi(value);
else if (!strcmp(name, "enable-subject-links"))
diff --git a/source/cgit.h b/source/cgit.h
index d976906..5a9e756 100644
--- a/source/cgit.h
+++ b/source/cgit.h
@@ -88,6 +88,7 @@ struct cgit_repo {
int enable_gitmodules_links;
int enable_log_filecount;
int enable_log_linecount;
+ int enable_mailmap;
int enable_remote_branches;
int enable_subject_links;
int enable_trailers;
@@ -226,6 +227,7 @@ struct cgit_config {
int enable_commit_graph;
int enable_log_filecount;
int enable_log_linecount;
+ int enable_mailmap;
int enable_plain_email;
int enable_relative_dates;
int enable_remote_branches;
diff --git a/source/parsing.c b/source/parsing.c
index c992538..587f44d 100644
--- a/source/parsing.c
+++ b/source/parsing.c
@@ -13,6 +13,8 @@
#include "parsing.h"
#include "shared.h"
+#include <mailmap.h>
+
static char *substr(const char *start, const char *end)
{
size_t len;
@@ -29,22 +31,45 @@ static char *substr(const char *start, const char *end)
return buf;
}
+/*
+ * The repository's mailmap, read once per request and applied to every ident
+ * a page shows. Only the blob at HEAD is read, the way git reads a bare
+ * repository, so a repository cannot point the reader at a file on the server.
+ */
+static struct string_list mailmap = STRING_LIST_INIT_DUP;
+static int mailmap_read;
+
+struct string_list *cgit_mailmap(void)
+{
+ if (!ctx.repo || !ctx.repo->enable_mailmap)
+ return NULL;
+ if (!mailmap_read) {
+ mailmap_read = 1;
+ read_mailmap_blob(the_repository, &mailmap, "HEAD:.mailmap");
+ }
+ return mailmap.nr ? &mailmap : NULL;
+}
+
static void parse_user(const char *line, char **name, char **email, timestamp_t *date, int *tz)
{
struct ident_split ident;
struct strbuf address = STRBUF_INIT;
- ptrdiff_t email_len;
+ struct string_list *map;
+ const char *name_start, *mail_start;
+ size_t name_len, mail_len;
if (!split_ident_line(&ident, line, strchrnul(line, '\n') - line)) {
- *name = substr(ident.name_begin, ident.name_end);
+ name_start = ident.name_begin;
+ name_len = ident.name_end - ident.name_begin;
+ mail_start = ident.mail_begin;
+ mail_len = ident.mail_end - ident.mail_begin;
+ map = cgit_mailmap();
+ if (map)
+ map_user(map, &mail_start, &mail_len, &name_start, &name_len);
+ *name = xmemdupz(name_start, name_len);
- // Assembled rather than formatted. The length is a pointer
- // difference, and the precision of a %.*s conversion has to be
- // an int, which cannot hold one on a 64 bit host.
- email_len = ident.mail_end - ident.mail_begin;
strbuf_addch(&address, '<');
- if (email_len > 0)
- strbuf_add(&address, ident.mail_begin, email_len);
+ strbuf_add(&address, mail_start, mail_len);
strbuf_addch(&address, '>');
*email = strbuf_detach(&address, NULL);
diff --git a/source/parsing.h b/source/parsing.h
index b1dc9f1..d213429 100644
--- a/source/parsing.h
+++ b/source/parsing.h
@@ -14,6 +14,7 @@
// storing them in ctx.qry.
extern void cgit_parse_url(const char *url);
+extern struct string_list *cgit_mailmap(void);
extern struct commitinfo *cgit_parse_commit(struct commit *commit);
extern struct taginfo *cgit_parse_tag(struct tag *tag);
diff --git a/source/shared.c b/source/shared.c
index 59712ce..6ac2353 100644
--- a/source/shared.c
+++ b/source/shared.c
@@ -213,6 +213,7 @@ struct cgit_repo *cgit_add_repo(const char *url)
repo->enable_gitmodules_links = ctx.cfg.enable_gitmodules_links;
repo->enable_log_filecount = ctx.cfg.enable_log_filecount;
repo->enable_log_linecount = ctx.cfg.enable_log_linecount;
+ repo->enable_mailmap = ctx.cfg.enable_mailmap;
repo->enable_remote_branches = ctx.cfg.enable_remote_branches;
repo->enable_subject_links = ctx.cfg.enable_subject_links;
repo->enable_trailers = ctx.cfg.enable_trailers;
diff --git a/source/ui-log.c b/source/ui-log.c
index 8fe58f9..316df75 100644
--- a/source/ui-log.c
+++ b/source/ui-log.c
@@ -492,6 +492,8 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
strvec_push(&rev_argv, path);
repo_init_revisions(the_repository, &rev, NULL);
+ // The author and committer searches then match the mapped idents.
+ rev.mailmap = cgit_mailmap();
rev.abbrev = DEFAULT_ABBREV;
rev.commit_format = CMIT_FMT_DEFAULT;
rev.verbose_header = 1;
diff --git a/tests/t0208-mailmap.sh b/tests/t0208-mailmap.sh
new file mode 100755
index 0000000..ca08de0
--- /dev/null
+++ b/tests/t0208-mailmap.sh
@@ -0,0 +1,86 @@
+#!/bin/sh
+
+# The mailmap. A repository whose commits and tag carry an old ident gets a
+# .mailmap at HEAD renaming it, and the log, commit, tag, atom and search
+# pages are checked for the new name, then the flag is turned off globally and
+# for the one repository.
+
+test_description='Check the mailmap is applied to idents'
+CGIT_TEST_NO_CREATE_REPOS=YesPlease
+. ./setup.sh
+
+test_expect_success 'create a repository with a mailmap at its head' '
+ test_create_repo repos/mapped &&
+ (
+ cd repos/mapped &&
+ echo content >file &&
+ git add file &&
+ GIT_AUTHOR_NAME="Old Name" GIT_AUTHOR_EMAIL=old@example.com \
+ git commit -m "first commit" &&
+ GIT_COMMITTER_NAME="Old Name" GIT_COMMITTER_EMAIL=old@example.com \
+ git tag -a -m "a tag" v1 &&
+ echo "New Name <new@example.com> <old@example.com>" >.mailmap &&
+ git add .mailmap &&
+ git commit -m "add the mailmap"
+ )
+'
+
+test_expect_success 'point cgit at it' '
+ cat >cgitrc <<-EOF
+ virtual-root=/
+ cache-size=0
+ repo.url=mapped
+ repo.path=$PWD/repos/mapped/.git
+ EOF
+'
+
+test_expect_success 'the log shows the mapped author' '
+ cgit_url "mapped/log/" >tmp &&
+ grep ">first commit</a>" tmp &&
+ grep "<td class=.col-author.>New Name</td>" tmp &&
+ ! grep "Old Name" tmp
+'
+
+test_expect_success 'the commit page shows the mapped author and address' '
+ first=$(git -C repos/mapped rev-parse HEAD~1) &&
+ cgit_url "mapped/commit/&id=$first" >tmp &&
+ grep "<th>author</th><td>New Name &lt;new@example.com&gt;</td>" tmp &&
+ ! grep "old@example.com" tmp
+'
+
+test_expect_success 'the tag page shows the mapped tagger' '
+ cgit_url "mapped/tag/&h=v1" >tmp &&
+ grep "New Name &lt;new@example.com&gt;" tmp &&
+ ! grep "Old Name" tmp
+'
+
+test_expect_success 'the atom feed uses the mapped name and address' '
+ cgit_url "mapped/atom/" >tmp &&
+ grep "<name>New Name</name>" tmp &&
+ grep "<email>new@example.com</email>" tmp &&
+ ! grep "Old Name" tmp
+'
+
+test_expect_success 'the author search matches the mapped name' '
+ cgit_query "url=mapped/log/&qt=author&q=New+Name" >tmp &&
+ grep ">first commit</a>" tmp &&
+ cgit_query "url=mapped/log/&qt=author&q=Nobody" >tmp &&
+ ! grep ">first commit</a>" tmp
+'
+
+test_expect_success 'with the flag off the old ident shows' '
+ {
+ echo "enable-mailmap=0" &&
+ cat cgitrc
+ } >cgitrc-off &&
+ CGIT_CONFIG="$PWD/cgitrc-off" QUERY_STRING="url=mapped/log/" cgit >tmp &&
+ grep "<td class=.col-author.>Old Name</td>" tmp
+'
+
+test_expect_success 'the repository flag overrides the global one' '
+ echo "repo.enable-mailmap=0" >>cgitrc &&
+ cgit_url "mapped/log/" >tmp &&
+ grep "<td class=.col-author.>Old Name</td>" tmp
+'
+
+test_done