ref-filter: add mailmap support

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@gmail.com> Mentored-by: Hariom Verma <hariom18599@gmail.com> Signed-off-by: Kousik Sanagavarapu <five231003@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Kousik Sanagavarapu committed Sep 25, 2023 at 23:13 UTC a3d2e83a17d6ab9c50d14cfdff9f70a69de5c4c4
3 files changed +206 -37
Documentation/git-for-each-ref.txt
+5 -1
@@ -303,7 +303,11 @@ Fields that have name-email-date tuple as its value (`author`,
303 and `date` to extract the named component. For email fields (`authoremail`,
304 `committeremail` and `taggeremail`), `:trim` can be appended to get the email
305 without angle brackets, and `:localpart` to get the part before the `@` symbol
306 -out of the trimmed email.
306 +out of the trimmed email. In addition to these, the `:mailmap` option and the
307 +corresponding `:mailmap,trim` and `:mailmap,localpart` can be used (order does
308 +not matter) to get values of the name and email according to the .mailmap file
309 +or according to the file set in the mailmap.file or mailmap.blob configuration
310 +variable (see linkgit:gitmailmap[5]).
311
312 The raw data in an object is `raw`.
313
ref-filter.c
+117 -35
@@ -13,6 +13,8 @@
13 #include "oid-array.h"
14 #include "repository.h"
15 #include "commit.h"
16 +#include "mailmap.h"
17 +#include "ident.h"
18 #include "remote.h"
19 #include "color.h"
20 #include "tag.h"
@@ -215,8 +217,16 @@ static struct used_atom {
217 struct {
218 enum { O_SIZE, O_SIZE_DISK } option;
219 } objectsize;
218 - struct email_option {
219 - enum { EO_RAW, EO_TRIM, EO_LOCALPART } option;
220 + struct {
221 + enum { N_RAW, N_MAILMAP } option;
222 + } name_option;
223 + struct {
224 + enum {
225 + EO_RAW = 0,
226 + EO_TRIM = 1<<0,
227 + EO_LOCALPART = 1<<1,
228 + EO_MAILMAP = 1<<2,
229 + } option;
230 } email_option;
231 struct {
232 enum { S_BARE, S_GRADE, S_SIGNER, S_KEY,
@@ -720,21 +730,55 @@ static int oid_atom_parser(struct ref_format *format UNUSED,
730 return 0;
731 }
732
723 -static int person_email_atom_parser(struct ref_format *format UNUSED,
724 - struct used_atom *atom,
725 - const char *arg, struct strbuf *err)
733 +static int person_name_atom_parser(struct ref_format *format UNUSED,
734 + struct used_atom *atom,
735 + const char *arg, struct strbuf *err)
736 {
737 if (!arg)
728 - atom->u.email_option.option = EO_RAW;
729 - else if (!strcmp(arg, "trim"))
730 - atom->u.email_option.option = EO_TRIM;
731 - else if (!strcmp(arg, "localpart"))
732 - atom->u.email_option.option = EO_LOCALPART;
738 + atom->u.name_option.option = N_RAW;
739 + else if (!strcmp(arg, "mailmap"))
740 + atom->u.name_option.option = N_MAILMAP;
741 else
742 return err_bad_arg(err, atom->name, arg);
743 return 0;
744 }
745
746 +static int email_atom_option_parser(struct used_atom *atom,
747 + const char **arg, struct strbuf *err)
748 +{
749 + if (!*arg)
750 + return EO_RAW;
751 + if (skip_prefix(*arg, "trim", arg))
752 + return EO_TRIM;
753 + if (skip_prefix(*arg, "localpart", arg))
754 + return EO_LOCALPART;
755 + if (skip_prefix(*arg, "mailmap", arg))
756 + return EO_MAILMAP;
757 + return -1;
758 +}
759 +
760 +static int person_email_atom_parser(struct ref_format *format UNUSED,
761 + struct used_atom *atom,
762 + const char *arg, struct strbuf *err)
763 +{
764 + for (;;) {
765 + int opt = email_atom_option_parser(atom, &arg, err);
766 + const char *bad_arg = arg;
767 +
768 + if (opt < 0)
769 + return err_bad_arg(err, atom->name, bad_arg);
770 + atom->u.email_option.option |= opt;
771 +
772 + if (!arg || !*arg)
773 + break;
774 + if (*arg == ',')
775 + arg++;
776 + else
777 + return err_bad_arg(err, atom->name, bad_arg);
778 + }
779 + return 0;
780 +}
781 +
782 static int refname_atom_parser(struct ref_format *format UNUSED,
783 struct used_atom *atom,
784 const char *arg, struct strbuf *err)
@@ -877,15 +921,15 @@ static struct {
921 [ATOM_TYPE] = { "type", SOURCE_OBJ },
922 [ATOM_TAG] = { "tag", SOURCE_OBJ },
923 [ATOM_AUTHOR] = { "author", SOURCE_OBJ },
880 - [ATOM_AUTHORNAME] = { "authorname", SOURCE_OBJ },
924 + [ATOM_AUTHORNAME] = { "authorname", SOURCE_OBJ, FIELD_STR, person_name_atom_parser },
925 [ATOM_AUTHOREMAIL] = { "authoremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser },
926 [ATOM_AUTHORDATE] = { "authordate", SOURCE_OBJ, FIELD_TIME },
927 [ATOM_COMMITTER] = { "committer", SOURCE_OBJ },
884 - [ATOM_COMMITTERNAME] = { "committername", SOURCE_OBJ },
928 + [ATOM_COMMITTERNAME] = { "committername", SOURCE_OBJ, FIELD_STR, person_name_atom_parser },
929 [ATOM_COMMITTEREMAIL] = { "committeremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser },
930 [ATOM_COMMITTERDATE] = { "committerdate", SOURCE_OBJ, FIELD_TIME },
931 [ATOM_TAGGER] = { "tagger", SOURCE_OBJ },
888 - [ATOM_TAGGERNAME] = { "taggername", SOURCE_OBJ },
932 + [ATOM_TAGGERNAME] = { "taggername", SOURCE_OBJ, FIELD_STR, person_name_atom_parser },
933 [ATOM_TAGGEREMAIL] = { "taggeremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser },
934 [ATOM_TAGGERDATE] = { "taggerdate", SOURCE_OBJ, FIELD_TIME },
935 [ATOM_CREATOR] = { "creator", SOURCE_OBJ },
@@ -1486,32 +1530,49 @@ static const char *copy_name(const char *buf)
1530 return xstrdup("");
1531 }
1532
1533 +static const char *find_end_of_email(const char *email, int opt)
1534 +{
1535 + const char *eoemail;
1536 +
1537 + if (opt & EO_LOCALPART) {
1538 + eoemail = strchr(email, '@');
1539 + if (eoemail)
1540 + return eoemail;
1541 + return strchr(email, '>');
1542 + }
1543 +
1544 + if (opt & EO_TRIM)
1545 + return strchr(email, '>');
1546 +
1547 + /*
1548 + * The option here is either the raw email option or the raw
1549 + * mailmap option (that is EO_RAW or EO_MAILMAP). In such cases,
1550 + * we directly grab the whole email including the closing
1551 + * angle brackets.
1552 + *
1553 + * If EO_MAILMAP was set with any other option (that is either
1554 + * EO_TRIM or EO_LOCALPART), we already grab the end of email
1555 + * above.
1556 + */
1557 + eoemail = strchr(email, '>');
1558 + if (eoemail)
1559 + eoemail++;
1560 + return eoemail;
1561 +}
1562 +
1563 static const char *copy_email(const char *buf, struct used_atom *atom)
1564 {
1565 const char *email = strchr(buf, '<');
1566 const char *eoemail;
1567 + int opt = atom->u.email_option.option;
1568 +
1569 if (!email)
1570 return xstrdup("");
1495 - switch (atom->u.email_option.option) {
1496 - case EO_RAW:
1497 - eoemail = strchr(email, '>');
1498 - if (eoemail)
1499 - eoemail++;
1500 - break;
1501 - case EO_TRIM:
1502 - email++;
1503 - eoemail = strchr(email, '>');
1504 - break;
1505 - case EO_LOCALPART:
1571 +
1572 + if (opt & (EO_LOCALPART | EO_TRIM))
1573 email++;
1507 - eoemail = strchr(email, '@');
1508 - if (!eoemail)
1509 - eoemail = strchr(email, '>');
1510 - break;
1511 - default:
1512 - BUG("unknown email option");
1513 - }
1574
1575 + eoemail = find_end_of_email(email, opt);
1576 if (!eoemail)
1577 return xstrdup("");
1578 return xmemdupz(email, eoemail - email);
@@ -1572,16 +1633,23 @@ static void grab_date(const char *buf, struct atom_value *v, const char *atomnam
1633 v->value = 0;
1634 }
1635
1636 +static struct string_list mailmap = STRING_LIST_INIT_NODUP;
1637 +
1638 /* See grab_values */
1639 static void grab_person(const char *who, struct atom_value *val, int deref, void *buf)
1640 {
1641 int i;
1642 int wholen = strlen(who);
1643 const char *wholine = NULL;
1644 + const char *headers[] = { "author ", "committer ",
1645 + "tagger ", NULL };
1646
1647 for (i = 0; i < used_atom_cnt; i++) {
1583 - const char *name = used_atom[i].name;
1648 + struct used_atom *atom = &used_atom[i];
1649 + const char *name = atom->name;
1650 struct atom_value *v = &val[i];
1651 + struct strbuf mailmap_buf = STRBUF_INIT;
1652 +
1653 if (!!deref != (*name == '*'))
1654 continue;
1655 if (deref)
@@ -1589,22 +1657,36 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void
1657 if (strncmp(who, name, wholen))
1658 continue;
1659 if (name[wholen] != 0 &&
1592 - strcmp(name + wholen, "name") &&
1660 + !starts_with(name + wholen, "name") &&
1661 !starts_with(name + wholen, "email") &&
1662 !starts_with(name + wholen, "date"))
1663 continue;
1596 - if (!wholine)
1664 +
1665 + if ((starts_with(name + wholen, "name") &&
1666 + (atom->u.name_option.option == N_MAILMAP)) ||
1667 + (starts_with(name + wholen, "email") &&
1668 + (atom->u.email_option.option & EO_MAILMAP))) {
1669 + if (!mailmap.items)
1670 + read_mailmap(&mailmap);
1671 + strbuf_addstr(&mailmap_buf, buf);
1672 + apply_mailmap_to_header(&mailmap_buf, headers, &mailmap);
1673 + wholine = find_wholine(who, wholen, mailmap_buf.buf);
1674 + } else {
1675 wholine = find_wholine(who, wholen, buf);
1676 + }
1677 +
1678 if (!wholine)
1679 return; /* no point looking for it */
1680 if (name[wholen] == 0)
1681 v->s = copy_line(wholine);
1602 - else if (!strcmp(name + wholen, "name"))
1682 + else if (starts_with(name + wholen, "name"))
1683 v->s = copy_name(wholine);
1684 else if (starts_with(name + wholen, "email"))
1685 v->s = copy_email(wholine, &used_atom[i]);
1686 else if (starts_with(name + wholen, "date"))
1687 grab_date(wholine, v, name);
1688 +
1689 + strbuf_release(&mailmap_buf);
1690 }
1691
1692 /*
t/t6300-for-each-ref.sh
+84 -1
@@ -25,6 +25,13 @@ test_expect_success setup '
25 disklen sha1:138
26 disklen sha256:154
27 EOF
28 +
29 + # setup .mailmap
30 + cat >.mailmap <<-EOF &&
31 + A Thor <athor@example.com> A U Thor <author@example.com>
32 + C Mitter <cmitter@example.com> C O Mitter <committer@example.com>
33 + EOF
34 +
35 setdate_and_increment &&
36 echo "Using $datestamp" > one &&
37 git add one &&
@@ -145,15 +152,31 @@ test_atom head '*objectname' ''
152 test_atom head '*objecttype' ''
153 test_atom head author 'A U Thor <author@example.com> 1151968724 +0200'
154 test_atom head authorname 'A U Thor'
155 +test_atom head authorname:mailmap 'A Thor'
156 test_atom head authoremail '<author@example.com>'
157 test_atom head authoremail:trim 'author@example.com'
158 test_atom head authoremail:localpart 'author'
159 +test_atom head authoremail:trim,localpart 'author'
160 +test_atom head authoremail:mailmap '<athor@example.com>'
161 +test_atom head authoremail:mailmap,trim 'athor@example.com'
162 +test_atom head authoremail:trim,mailmap 'athor@example.com'
163 +test_atom head authoremail:mailmap,localpart 'athor'
164 +test_atom head authoremail:localpart,mailmap 'athor'
165 +test_atom head authoremail:mailmap,trim,localpart,mailmap,trim 'athor'
166 test_atom head authordate 'Tue Jul 4 01:18:44 2006 +0200'
167 test_atom head committer 'C O Mitter <committer@example.com> 1151968723 +0200'
168 test_atom head committername 'C O Mitter'
169 +test_atom head committername:mailmap 'C Mitter'
170 test_atom head committeremail '<committer@example.com>'
171 test_atom head committeremail:trim 'committer@example.com'
172 test_atom head committeremail:localpart 'committer'
173 +test_atom head committeremail:localpart,trim 'committer'
174 +test_atom head committeremail:mailmap '<cmitter@example.com>'
175 +test_atom head committeremail:mailmap,trim 'cmitter@example.com'
176 +test_atom head committeremail:trim,mailmap 'cmitter@example.com'
177 +test_atom head committeremail:mailmap,localpart 'cmitter'
178 +test_atom head committeremail:localpart,mailmap 'cmitter'
179 +test_atom head committeremail:trim,mailmap,trim,trim,localpart 'cmitter'
180 test_atom head committerdate 'Tue Jul 4 01:18:43 2006 +0200'
181 test_atom head tag ''
182 test_atom head tagger ''
@@ -203,22 +226,46 @@ test_atom tag '*objectname' $(git rev-parse refs/tags/testtag^{})
226 test_atom tag '*objecttype' 'commit'
227 test_atom tag author ''
228 test_atom tag authorname ''
229 +test_atom tag authorname:mailmap ''
230 test_atom tag authoremail ''
231 test_atom tag authoremail:trim ''
232 test_atom tag authoremail:localpart ''
233 +test_atom tag authoremail:trim,localpart ''
234 +test_atom tag authoremail:mailmap ''
235 +test_atom tag authoremail:mailmap,trim ''
236 +test_atom tag authoremail:trim,mailmap ''
237 +test_atom tag authoremail:mailmap,localpart ''
238 +test_atom tag authoremail:localpart,mailmap ''
239 +test_atom tag authoremail:mailmap,trim,localpart,mailmap,trim ''
240 test_atom tag authordate ''
241 test_atom tag committer ''
242 test_atom tag committername ''
243 +test_atom tag committername:mailmap ''
244 test_atom tag committeremail ''
245 test_atom tag committeremail:trim ''
246 test_atom tag committeremail:localpart ''
247 +test_atom tag committeremail:localpart,trim ''
248 +test_atom tag committeremail:mailmap ''
249 +test_atom tag committeremail:mailmap,trim ''
250 +test_atom tag committeremail:trim,mailmap ''
251 +test_atom tag committeremail:mailmap,localpart ''
252 +test_atom tag committeremail:localpart,mailmap ''
253 +test_atom tag committeremail:trim,mailmap,trim,trim,localpart ''
254 test_atom tag committerdate ''
255 test_atom tag tag 'testtag'
256 test_atom tag tagger 'C O Mitter <committer@example.com> 1151968725 +0200'
257 test_atom tag taggername 'C O Mitter'
258 +test_atom tag taggername:mailmap 'C Mitter'
259 test_atom tag taggeremail '<committer@example.com>'
260 test_atom tag taggeremail:trim 'committer@example.com'
261 test_atom tag taggeremail:localpart 'committer'
262 +test_atom tag taggeremail:trim,localpart 'committer'
263 +test_atom tag taggeremail:mailmap '<cmitter@example.com>'
264 +test_atom tag taggeremail:mailmap,trim 'cmitter@example.com'
265 +test_atom tag taggeremail:trim,mailmap 'cmitter@example.com'
266 +test_atom tag taggeremail:mailmap,localpart 'cmitter'
267 +test_atom tag taggeremail:localpart,mailmap 'cmitter'
268 +test_atom tag taggeremail:trim,mailmap,trim,localpart,localpart 'cmitter'
269 test_atom tag taggerdate 'Tue Jul 4 01:18:45 2006 +0200'
270 test_atom tag creator 'C O Mitter <committer@example.com> 1151968725 +0200'
271 test_atom tag creatordate 'Tue Jul 4 01:18:45 2006 +0200'
@@ -292,8 +339,44 @@ test_bad_atom () {
339 test_bad_atom head 'authoremail:foo' \
340 'fatal: unrecognized %(authoremail) argument: foo'
341
342 +test_bad_atom head 'authoremail:mailmap,trim,bar' \
343 + 'fatal: unrecognized %(authoremail) argument: bar'
344 +
345 +test_bad_atom head 'authoremail:trim,' \
346 + 'fatal: unrecognized %(authoremail) argument: '
347 +
348 +test_bad_atom head 'authoremail:mailmaptrim' \
349 + 'fatal: unrecognized %(authoremail) argument: trim'
350 +
351 +test_bad_atom head 'committeremail: ' \
352 + 'fatal: unrecognized %(committeremail) argument: '
353 +
354 +test_bad_atom head 'committeremail: trim,foo' \
355 + 'fatal: unrecognized %(committeremail) argument: trim,foo'
356 +
357 +test_bad_atom head 'committeremail:mailmap,localpart ' \
358 + 'fatal: unrecognized %(committeremail) argument: '
359 +
360 +test_bad_atom head 'committeremail:trim_localpart' \
361 + 'fatal: unrecognized %(committeremail) argument: _localpart'
362 +
363 +test_bad_atom head 'committeremail:localpart,,,trim' \
364 + 'fatal: unrecognized %(committeremail) argument: ,,trim'
365 +
366 +test_bad_atom tag 'taggeremail:mailmap,trim, foo ' \
367 + 'fatal: unrecognized %(taggeremail) argument: foo '
368 +
369 +test_bad_atom tag 'taggeremail:trim,localpart,' \
370 + 'fatal: unrecognized %(taggeremail) argument: '
371 +
372 +test_bad_atom tag 'taggeremail:mailmap;localpart trim' \
373 + 'fatal: unrecognized %(taggeremail) argument: ;localpart trim'
374 +
375 test_bad_atom tag 'taggeremail:localpart trim' \
296 - 'fatal: unrecognized %(taggeremail) argument: localpart trim'
376 + 'fatal: unrecognized %(taggeremail) argument: trim'
377 +
378 +test_bad_atom tag 'taggeremail:mailmap,mailmap,trim,qux,localpart,trim' \
379 + 'fatal: unrecognized %(taggeremail) argument: qux,localpart,trim'
380
381 test_date () {
382 f=$1 &&