Add mailmap support to ref-filter formats which are similar in pretty. This support is such that the following pretty placeholders are equivalent to the new ref-filter atoms: %aN = authorname:mailmap %cN = committername:mailmap %aE = authoremail:mailmap %aL = authoremail:mailmap,localpart %cE = committeremail:mailmap %cL = committeremail:mailmap,localpart Additionally, mailmap can also be used with ":trim" option for email by doing something like "authoremail:mailmap,trim". The above also applies for the "tagger" atom, that is, "taggername:mailmap", "taggeremail:mailmap", "taggeremail:mailmap,trim" and "taggername:mailmap,localpart". The functionality of ":trim" and ":localpart" remains the same. That is, ":trim" gives the email, but without the angle brackets and ":localpart" gives the part of the email before the '@' character (if such a character is not found then we directly grab everything between the angle brackets). Mentored-by: Christian Couder <christian.couder@xxxxxxxxx> Mentored-by: Hariom Verma <hariom18599@xxxxxxxxx> Signed-off-by: Kousik Sanagavarapu <five231003@xxxxxxxxx> --- Documentation/git-for-each-ref.txt | 6 +- ref-filter.c | 152 ++++++++++++++++++++++------- t/t6300-for-each-ref.sh | 83 ++++++++++++++++ 3 files changed, 205 insertions(+), 36 deletions(-) diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 11b2bc3121..e86d5700dd 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -303,7 +303,11 @@ Fields that have name-email-date tuple as its value (`author`, and `date` to extract the named component. For email fields (`authoremail`, `committeremail` and `taggeremail`), `:trim` can be appended to get the email without angle brackets, and `:localpart` to get the part before the `@` symbol -out of the trimmed email. +out of the trimmed email. In addition to these, the `:mailmap` option and the +corresponding `:mailmap,trim` and `:mailmap,localpart` can be used (order does +not matter) to get values of the name and email according to the .mailmap file +or according to the file set in the mailmap.file or mailmap.blob configuration +variable (see linkgit:gitmailmap[5]). The raw data in an object is `raw`. diff --git a/ref-filter.c b/ref-filter.c index fae9f4b8ed..e4d3510e28 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -13,6 +13,8 @@ #include "oid-array.h" #include "repository.h" #include "commit.h" +#include "mailmap.h" +#include "ident.h" #include "remote.h" #include "color.h" #include "tag.h" @@ -215,8 +217,16 @@ static struct used_atom { struct { enum { O_SIZE, O_SIZE_DISK } option; } objectsize; - struct email_option { - enum { EO_RAW, EO_TRIM, EO_LOCALPART } option; + struct { + enum { N_RAW, N_MAILMAP } option; + } name_option; + struct { + enum { + EO_RAW = 0, + EO_TRIM = 1<<0, + EO_LOCALPART = 1<<1, + EO_MAILMAP = 1<<2, + } option; } email_option; struct { enum { S_BARE, S_GRADE, S_SIGNER, S_KEY, @@ -720,21 +730,55 @@ static int oid_atom_parser(struct ref_format *format UNUSED, return 0; } -static int person_email_atom_parser(struct ref_format *format UNUSED, - struct used_atom *atom, - const char *arg, struct strbuf *err) +static int person_name_atom_parser(struct ref_format *format UNUSED, + struct used_atom *atom, + const char *arg, struct strbuf *err) { if (!arg) - atom->u.email_option.option = EO_RAW; - else if (!strcmp(arg, "trim")) - atom->u.email_option.option = EO_TRIM; - else if (!strcmp(arg, "localpart")) - atom->u.email_option.option = EO_LOCALPART; + atom->u.name_option.option = N_RAW; + else if (!strcmp(arg, "mailmap")) + atom->u.name_option.option = N_MAILMAP; else return err_bad_arg(err, atom->name, arg); return 0; } +static int email_atom_option_parser(struct used_atom *atom, + const char **arg, struct strbuf *err) +{ + if (!*arg) + return EO_RAW; + if (skip_prefix(*arg, "trim", arg)) + return EO_TRIM; + if (skip_prefix(*arg, "localpart", arg)) + return EO_LOCALPART; + if (skip_prefix(*arg, "mailmap", arg)) + return EO_MAILMAP; + return -1; +} + +static int person_email_atom_parser(struct ref_format *format UNUSED, + struct used_atom *atom, + const char *arg, struct strbuf *err) +{ + for (;;) { + int opt = email_atom_option_parser(atom, &arg, err); + const char *bad_arg = arg; + + if (opt < 0) + return err_bad_arg(err, atom->name, bad_arg); + atom->u.email_option.option |= opt; + + if (!arg || !*arg) + break; + if (*arg == ',') + arg++; + else + return err_bad_arg(err, atom->name, bad_arg); + } + return 0; +} + static int refname_atom_parser(struct ref_format *format UNUSED, struct used_atom *atom, const char *arg, struct strbuf *err) @@ -877,15 +921,15 @@ static struct { [ATOM_TYPE] = { "type", SOURCE_OBJ }, [ATOM_TAG] = { "tag", SOURCE_OBJ }, [ATOM_AUTHOR] = { "author", SOURCE_OBJ }, - [ATOM_AUTHORNAME] = { "authorname", SOURCE_OBJ }, + [ATOM_AUTHORNAME] = { "authorname", SOURCE_OBJ, FIELD_STR, person_name_atom_parser }, [ATOM_AUTHOREMAIL] = { "authoremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser }, [ATOM_AUTHORDATE] = { "authordate", SOURCE_OBJ, FIELD_TIME }, [ATOM_COMMITTER] = { "committer", SOURCE_OBJ }, - [ATOM_COMMITTERNAME] = { "committername", SOURCE_OBJ }, + [ATOM_COMMITTERNAME] = { "committername", SOURCE_OBJ, FIELD_STR, person_name_atom_parser }, [ATOM_COMMITTEREMAIL] = { "committeremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser }, [ATOM_COMMITTERDATE] = { "committerdate", SOURCE_OBJ, FIELD_TIME }, [ATOM_TAGGER] = { "tagger", SOURCE_OBJ }, - [ATOM_TAGGERNAME] = { "taggername", SOURCE_OBJ }, + [ATOM_TAGGERNAME] = { "taggername", SOURCE_OBJ, FIELD_STR, person_name_atom_parser }, [ATOM_TAGGEREMAIL] = { "taggeremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser }, [ATOM_TAGGERDATE] = { "taggerdate", SOURCE_OBJ, FIELD_TIME }, [ATOM_CREATOR] = { "creator", SOURCE_OBJ }, @@ -1486,32 +1530,49 @@ static const char *copy_name(const char *buf) return xstrdup(""); } +static const char *find_end_of_email(const char *email, int opt) +{ + const char *eoemail; + + if (opt & EO_LOCALPART) { + eoemail = strchr(email, '@'); + if (eoemail) + return eoemail; + return strchr(email, '>'); + } + + if (opt & EO_TRIM) + return strchr(email, '>'); + + /* + * The option here is either the raw email option or the raw + * mailmap option (that is EO_RAW or EO_MAILMAP). In such cases, + * we directly grab the whole email including the closing + * angle brackets. + * + * If EO_MAILMAP was set with any other option (that is either + * EO_TRIM or EO_LOCALPART), we already grab the end of email + * above. + */ + eoemail = strchr(email, '>'); + if (eoemail) + eoemail++; + return eoemail; +} + static const char *copy_email(const char *buf, struct used_atom *atom) { const char *email = strchr(buf, '<'); const char *eoemail; + int opt = atom->u.email_option.option; + if (!email) return xstrdup(""); - switch (atom->u.email_option.option) { - case EO_RAW: - eoemail = strchr(email, '>'); - if (eoemail) - eoemail++; - break; - case EO_TRIM: - email++; - eoemail = strchr(email, '>'); - break; - case EO_LOCALPART: + + if (opt & (EO_LOCALPART | EO_TRIM)) email++; - eoemail = strchr(email, '@'); - if (!eoemail) - eoemail = strchr(email, '>'); - break; - default: - BUG("unknown email option"); - } + eoemail = find_end_of_email(email, opt); if (!eoemail) return xstrdup(""); return xmemdupz(email, eoemail - email); @@ -1572,16 +1633,23 @@ static void grab_date(const char *buf, struct atom_value *v, const char *atomnam v->value = 0; } +static struct string_list mailmap = STRING_LIST_INIT_NODUP; + /* See grab_values */ static void grab_person(const char *who, struct atom_value *val, int deref, void *buf) { int i; int wholen = strlen(who); const char *wholine = NULL; + const char *headers[] = { "author ", "committer ", + "tagger ", NULL }; for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i].name; + struct used_atom *atom = &used_atom[i]; + const char *name = atom->name; struct atom_value *v = &val[i]; + struct strbuf mailmap_buf = STRBUF_INIT; + if (!!deref != (*name == '*')) continue; if (deref) @@ -1589,22 +1657,36 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void if (strncmp(who, name, wholen)) continue; if (name[wholen] != 0 && - strcmp(name + wholen, "name") && + !starts_with(name + wholen, "name") && !starts_with(name + wholen, "email") && !starts_with(name + wholen, "date")) continue; - if (!wholine) + + if ((starts_with(name + wholen, "name") && + (atom->u.name_option.option == N_MAILMAP)) || + (starts_with(name + wholen, "email") && + (atom->u.email_option.option & EO_MAILMAP))) { + if (!mailmap.items) + read_mailmap(&mailmap); + strbuf_addstr(&mailmap_buf, buf); + apply_mailmap_to_header(&mailmap_buf, headers, &mailmap); + wholine = find_wholine(who, wholen, mailmap_buf.buf); + } else { wholine = find_wholine(who, wholen, buf); + } + if (!wholine) return; /* no point looking for it */ if (name[wholen] == 0) v->s = copy_line(wholine); - else if (!strcmp(name + wholen, "name")) + else if (starts_with(name + wholen, "name")) v->s = copy_name(wholine); else if (starts_with(name + wholen, "email")) v->s = copy_email(wholine, &used_atom[i]); else if (starts_with(name + wholen, "date")) grab_date(wholine, v, name); + + strbuf_release(&mailmap_buf); } /* diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 15b4622f57..1fb464ca50 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -25,6 +25,13 @@ test_expect_success setup ' disklen sha1:138 disklen sha256:154 EOF + + # setup .mailmap + cat >.mailmap <<-EOF && + A Thor <athor@xxxxxxxxxxx> A U Thor <author@xxxxxxxxxxx> + C Mitter <cmitter@xxxxxxxxxxx> C O Mitter <committer@xxxxxxxxxxx> + EOF + setdate_and_increment && echo "Using $datestamp" > one && git add one && @@ -141,15 +148,31 @@ test_atom head '*objectname' '' test_atom head '*objecttype' '' test_atom head author 'A U Thor <author@xxxxxxxxxxx> 1151968724 +0200' test_atom head authorname 'A U Thor' +test_atom head authorname:mailmap 'A Thor' test_atom head authoremail '<author@xxxxxxxxxxx>' test_atom head authoremail:trim 'author@xxxxxxxxxxx' test_atom head authoremail:localpart 'author' +test_atom head authoremail:trim,localpart 'author' +test_atom head authoremail:mailmap '<athor@xxxxxxxxxxx>' +test_atom head authoremail:mailmap,trim 'athor@xxxxxxxxxxx' +test_atom head authoremail:trim,mailmap 'athor@xxxxxxxxxxx' +test_atom head authoremail:mailmap,localpart 'athor' +test_atom head authoremail:localpart,mailmap 'athor' +test_atom head authoremail:mailmap,trim,localpart,mailmap,trim 'athor' test_atom head authordate 'Tue Jul 4 01:18:44 2006 +0200' test_atom head committer 'C O Mitter <committer@xxxxxxxxxxx> 1151968723 +0200' test_atom head committername 'C O Mitter' +test_atom head committername:mailmap 'C Mitter' test_atom head committeremail '<committer@xxxxxxxxxxx>' test_atom head committeremail:trim 'committer@xxxxxxxxxxx' test_atom head committeremail:localpart 'committer' +test_atom head committeremail:localpart,trim 'committer' +test_atom head committeremail:mailmap '<cmitter@xxxxxxxxxxx>' +test_atom head committeremail:mailmap,trim 'cmitter@xxxxxxxxxxx' +test_atom head committeremail:trim,mailmap 'cmitter@xxxxxxxxxxx' +test_atom head committeremail:mailmap,localpart 'cmitter' +test_atom head committeremail:localpart,mailmap 'cmitter' +test_atom head committeremail:trim,mailmap,trim,trim,localpart 'cmitter' test_atom head committerdate 'Tue Jul 4 01:18:43 2006 +0200' test_atom head tag '' test_atom head tagger '' @@ -199,22 +222,46 @@ test_atom tag '*objectname' $(git rev-parse refs/tags/testtag^{}) test_atom tag '*objecttype' 'commit' test_atom tag author '' test_atom tag authorname '' +test_atom tag authorname:mailmap '' test_atom tag authoremail '' test_atom tag authoremail:trim '' test_atom tag authoremail:localpart '' +test_atom tag authoremail:trim,localpart '' +test_atom tag authoremail:mailmap '' +test_atom tag authoremail:mailmap,trim '' +test_atom tag authoremail:trim,mailmap '' +test_atom tag authoremail:mailmap,localpart '' +test_atom tag authoremail:localpart,mailmap '' +test_atom tag authoremail:mailmap,trim,localpart,mailmap,trim '' test_atom tag authordate '' test_atom tag committer '' test_atom tag committername '' +test_atom tag committername:mailmap '' test_atom tag committeremail '' test_atom tag committeremail:trim '' test_atom tag committeremail:localpart '' +test_atom tag committeremail:localpart,trim '' +test_atom tag committeremail:mailmap '' +test_atom tag committeremail:mailmap,trim '' +test_atom tag committeremail:trim,mailmap '' +test_atom tag committeremail:mailmap,localpart '' +test_atom tag committeremail:localpart,mailmap '' +test_atom tag committeremail:trim,mailmap,trim,trim,localpart '' test_atom tag committerdate '' test_atom tag tag 'testtag' test_atom tag tagger 'C O Mitter <committer@xxxxxxxxxxx> 1151968725 +0200' test_atom tag taggername 'C O Mitter' +test_atom tag taggername:mailmap 'C Mitter' test_atom tag taggeremail '<committer@xxxxxxxxxxx>' test_atom tag taggeremail:trim 'committer@xxxxxxxxxxx' test_atom tag taggeremail:localpart 'committer' +test_atom tag taggeremail:trim,localpart 'committer' +test_atom tag taggeremail:mailmap '<cmitter@xxxxxxxxxxx>' +test_atom tag taggeremail:mailmap,trim 'cmitter@xxxxxxxxxxx' +test_atom tag taggeremail:trim,mailmap 'cmitter@xxxxxxxxxxx' +test_atom tag taggeremail:mailmap,localpart 'cmitter' +test_atom tag taggeremail:localpart,mailmap 'cmitter' +test_atom tag taggeremail:trim,mailmap,trim,localpart,localpart 'cmitter' test_atom tag taggerdate 'Tue Jul 4 01:18:45 2006 +0200' test_atom tag creator 'C O Mitter <committer@xxxxxxxxxxx> 1151968725 +0200' test_atom tag creatordate 'Tue Jul 4 01:18:45 2006 +0200' @@ -284,9 +331,45 @@ test_bad_atom() { test_bad_atom head 'authoremail:foo' \ 'fatal: unrecognized %(authoremail) argument: foo' +test_bad_atom head 'authoremail:mailmap,trim,bar' \ + 'fatal: unrecognized %(authoremail) argument: bar' + +test_bad_atom head 'authoremail:trim,' \ + 'fatal: unrecognized %(authoremail) argument: ' + +test_bad_atom head 'authoremail:mailmaptrim' \ + 'fatal: unrecognized %(authoremail) argument: trim' + +test_bad_atom head 'committeremail: ' \ + 'fatal: unrecognized %(committeremail) argument: ' + +test_bad_atom head 'committeremail: trim,foo' \ + 'fatal: unrecognized %(committeremail) argument: trim,foo' + +test_bad_atom head 'committeremail:mailmap,localpart ' \ + 'fatal: unrecognized %(committeremail) argument: ' + +test_bad_atom head 'committeremail:trim_localpart' \ + 'fatal: unrecognized %(committeremail) argument: _localpart' + +test_bad_atom head 'committeremail:localpart,,,trim' \ + 'fatal: unrecognized %(committeremail) argument: ,,trim' + +test_bad_atom tag 'taggeremail:mailmap,trim, foo ' \ + 'fatal: unrecognized %(taggeremail) argument: foo ' + +test_bad_atom tag 'taggeremail:trim,localpart,' \ + 'fatal: unrecognized %(taggeremail) argument: ' + +test_bad_atom tag 'taggeremail:mailmap;localpart trim' \ + 'fatal: unrecognized %(taggeremail) argument: ;localpart trim' + test_bad_atom tag 'taggeremail:localpart trim' \ 'fatal: unrecognized %(taggeremail) argument: trim' +test_bad_atom tag 'taggeremail:mailmap,mailmap,trim,qux,localpart,trim' \ + 'fatal: unrecognized %(taggeremail) argument: qux,localpart,trim' + test_date () { f=$1 && committer_date=$2 && -- 2.42.0.160.g6905eb16ce.dirty