config: allow giving separate author and committer idents

The author.email, author.name, committer.email and committer.name settings are analogous to the GIT_AUTHOR_* and GIT_COMMITTER_* environment variables, but for the git config system. This allows them to be set separately for each repository. Git supports setting different authorship and committer information with environment variables. However, environment variables are set in the shell, so if different authorship and committer information is needed for different repositories an external tool is required. This adds support to git config for author.email, author.name, committer.email and committer.name settings so this information can be set per repository. Also, it generalizes the fmt_ident function so it can handle author vs committer identification. Signed-off-by: William Hubbs <williamh@gentoo.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>

William Hubbs committed Feb 4, 2019 at 12:48 UTC 39ab4d0951ba64edcfae7809740715991b44fa6d
10 files changed +197 -24
Documentation/config/user.txt
+15 -8
@@ -1,12 +1,19 @@
1 -user.email::
2 - Your email address to be recorded in any newly created commits.
3 - Can be overridden by the `GIT_AUTHOR_EMAIL`, `GIT_COMMITTER_EMAIL`, and
4 - `EMAIL` environment variables. See linkgit:git-commit-tree[1].
5 -
1 user.name::
7 - Your full name to be recorded in any newly created commits.
8 - Can be overridden by the `GIT_AUTHOR_NAME` and `GIT_COMMITTER_NAME`
9 - environment variables. See linkgit:git-commit-tree[1].
2 +user.email::
3 +author.name::
4 +author.email::
5 +committer.name::
6 +committer.email::
7 + The `user.name` and `user.email` variables determine what ends
8 + up in the `author` and `committer` field of commit
9 + objects.
10 + If you need the `author` or `committer` to be different, the
11 + `author.name`, `author.email`, `committer.name` or
12 + `committer.email` variables can be set.
13 + Also, all of these can be overridden by the `GIT_AUTHOR_NAME`,
14 + `GIT_AUTHOR_EMAIL`, `GIT_COMMITTER_NAME`,
15 + `GIT_COMMITTER_EMAIL` and `EMAIL` environment variables.
16 + See linkgit:git-commit-tree[1] for more information.
17
18 user.useConfigOnly::
19 Instruct Git to avoid trying to guess defaults for `user.email`
blame.c
+2 -1
@@ -204,7 +204,8 @@ static struct commit *fake_working_tree_commit(struct repository *r,
204
205 origin = make_origin(commit, path);
206
207 - ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0);
207 + ident = fmt_ident("Not Committed Yet", "not.committed.yet",
208 + WANT_BLANK_IDENT, NULL, 0);
209 strbuf_addstr(&msg, "tree 0000000000000000000000000000000000000000\n");
210 for (parent = commit->parents; parent; parent = parent->next)
211 strbuf_addf(&msg, "parent %s\n",
builtin/am.c
+1
@@ -1594,6 +1594,7 @@ static void do_commit(const struct am_state *state)
1594 }
1595
1596 author = fmt_ident(state->author_name, state->author_email,
1597 + WANT_AUTHOR_IDENT,
1598 state->ignore_date ? NULL : state->author_date,
1599 IDENT_STRICT);
1600
builtin/commit.c
+2 -1
@@ -607,7 +607,8 @@ static void determine_author_info(struct strbuf *author_ident)
607 set_ident_var(&date, strbuf_detach(&date_buf, NULL));
608 }
609
610 - strbuf_addstr(author_ident, fmt_ident(name, email, date, IDENT_STRICT));
610 + strbuf_addstr(author_ident, fmt_ident(name, email, WANT_AUTHOR_IDENT, date,
611 + IDENT_STRICT));
612 assert_split_ident(&author, author_ident);
613 export_one("GIT_AUTHOR_NAME", author.name_begin, author.name_end, 0);
614 export_one("GIT_AUTHOR_EMAIL", author.mail_begin, author.mail_end, 0);
cache.h
+11 -2
@@ -1479,10 +1479,19 @@ int date_overflows(timestamp_t date);
1479 #define IDENT_STRICT 1
1480 #define IDENT_NO_DATE 2
1481 #define IDENT_NO_NAME 4
1482 +
1483 +enum want_ident {
1484 + WANT_BLANK_IDENT,
1485 + WANT_AUTHOR_IDENT,
1486 + WANT_COMMITTER_IDENT
1487 +};
1488 +
1489 extern const char *git_author_info(int);
1490 extern const char *git_committer_info(int);
1484 -extern const char *fmt_ident(const char *name, const char *email, const char *date_str, int);
1485 -extern const char *fmt_name(const char *name, const char *email);
1491 +extern const char *fmt_ident(const char *name, const char *email,
1492 + enum want_ident whose_ident,
1493 + const char *date_str, int);
1494 +extern const char *fmt_name(enum want_ident);
1495 extern const char *ident_default_name(void);
1496 extern const char *ident_default_email(void);
1497 extern const char *git_editor(void);
config.c
+3 -1
@@ -1445,7 +1445,9 @@ int git_default_config(const char *var, const char *value, void *cb)
1445 if (starts_with(var, "core."))
1446 return git_default_core_config(var, value, cb);
1447
1448 - if (starts_with(var, "user."))
1448 + if (starts_with(var, "user.") ||
1449 + starts_with(var, "author.") ||
1450 + starts_with(var, "committer."))
1451 return git_ident_config(var, value, cb);
1452
1453 if (starts_with(var, "i18n."))
ident.c
+86 -6
@@ -11,6 +11,10 @@
11 static struct strbuf git_default_name = STRBUF_INIT;
12 static struct strbuf git_default_email = STRBUF_INIT;
13 static struct strbuf git_default_date = STRBUF_INIT;
14 +static struct strbuf git_author_name = STRBUF_INIT;
15 +static struct strbuf git_author_email = STRBUF_INIT;
16 +static struct strbuf git_committer_name = STRBUF_INIT;
17 +static struct strbuf git_committer_email = STRBUF_INIT;
18 static int default_email_is_bogus;
19 static int default_name_is_bogus;
20
@@ -355,13 +359,19 @@ N_("\n"
359 "\n");
360
361 const char *fmt_ident(const char *name, const char *email,
358 - const char *date_str, int flag)
362 + enum want_ident whose_ident, const char *date_str, int flag)
363 {
364 static struct strbuf ident = STRBUF_INIT;
365 int strict = (flag & IDENT_STRICT);
366 int want_date = !(flag & IDENT_NO_DATE);
367 int want_name = !(flag & IDENT_NO_NAME);
368
369 + if (!email) {
370 + if (whose_ident == WANT_AUTHOR_IDENT && git_author_email.len)
371 + email = git_author_email.buf;
372 + else if (whose_ident == WANT_COMMITTER_IDENT && git_committer_email.len)
373 + email = git_committer_email.buf;
374 + }
375 if (!email) {
376 if (strict && ident_use_config_only
377 && !(ident_config_given & IDENT_MAIL_GIVEN)) {
@@ -377,6 +387,13 @@ const char *fmt_ident(const char *name, const char *email,
387
388 if (want_name) {
389 int using_default = 0;
390 + if (!name) {
391 + if (whose_ident == WANT_AUTHOR_IDENT && git_author_name.len)
392 + name = git_author_name.buf;
393 + else if (whose_ident == WANT_COMMITTER_IDENT &&
394 + git_committer_name.len)
395 + name = git_committer_name.buf;
396 + }
397 if (!name) {
398 if (strict && ident_use_config_only
399 && !(ident_config_given & IDENT_NAME_GIVEN)) {
@@ -425,9 +442,25 @@ const char *fmt_ident(const char *name, const char *email,
442 return ident.buf;
443 }
444
428 -const char *fmt_name(const char *name, const char *email)
445 +const char *fmt_name(enum want_ident whose_ident)
446 {
430 - return fmt_ident(name, email, NULL, IDENT_STRICT | IDENT_NO_DATE);
447 + char *name = NULL;
448 + char *email = NULL;
449 +
450 + switch (whose_ident) {
451 + case WANT_BLANK_IDENT:
452 + break;
453 + case WANT_AUTHOR_IDENT:
454 + name = getenv("GIT_AUTHOR_NAME");
455 + email = getenv("GIT_AUTHOR_EMAIL");
456 + break;
457 + case WANT_COMMITTER_IDENT:
458 + name = getenv("GIT_COMMITTER_NAME");
459 + email = getenv("GIT_COMMITTER_EMAIL");
460 + break;
461 + }
462 + return fmt_ident(name, email, whose_ident, NULL,
463 + IDENT_STRICT | IDENT_NO_DATE);
464 }
465
466 const char *git_author_info(int flag)
@@ -438,6 +471,7 @@ const char *git_author_info(int flag)
471 author_ident_explicitly_given |= IDENT_MAIL_GIVEN;
472 return fmt_ident(getenv("GIT_AUTHOR_NAME"),
473 getenv("GIT_AUTHOR_EMAIL"),
474 + WANT_AUTHOR_IDENT,
475 getenv("GIT_AUTHOR_DATE"),
476 flag);
477 }
@@ -450,6 +484,7 @@ const char *git_committer_info(int flag)
484 committer_ident_explicitly_given |= IDENT_MAIL_GIVEN;
485 return fmt_ident(getenv("GIT_COMMITTER_NAME"),
486 getenv("GIT_COMMITTER_EMAIL"),
487 + WANT_COMMITTER_IDENT,
488 getenv("GIT_COMMITTER_DATE"),
489 flag);
490 }
@@ -473,10 +508,45 @@ int author_ident_sufficiently_given(void)
508 return ident_is_sufficient(author_ident_explicitly_given);
509 }
510
476 -int git_ident_config(const char *var, const char *value, void *data)
511 +static int set_ident(const char *var, const char *value)
512 {
478 - if (!strcmp(var, "user.useconfigonly")) {
479 - ident_use_config_only = git_config_bool(var, value);
513 + if (!strcmp(var, "author.name")) {
514 + if (!value)
515 + return config_error_nonbool(var);
516 + strbuf_reset(&git_author_name);
517 + strbuf_addstr(&git_author_name, value);
518 + author_ident_explicitly_given |= IDENT_NAME_GIVEN;
519 + ident_config_given |= IDENT_NAME_GIVEN;
520 + return 0;
521 + }
522 +
523 + if (!strcmp(var, "author.email")) {
524 + if (!value)
525 + return config_error_nonbool(var);
526 + strbuf_reset(&git_author_email);
527 + strbuf_addstr(&git_author_email, value);
528 + author_ident_explicitly_given |= IDENT_MAIL_GIVEN;
529 + ident_config_given |= IDENT_MAIL_GIVEN;
530 + return 0;
531 + }
532 +
533 + if (!strcmp(var, "committer.name")) {
534 + if (!value)
535 + return config_error_nonbool(var);
536 + strbuf_reset(&git_committer_name);
537 + strbuf_addstr(&git_committer_name, value);
538 + committer_ident_explicitly_given |= IDENT_NAME_GIVEN;
539 + ident_config_given |= IDENT_NAME_GIVEN;
540 + return 0;
541 + }
542 +
543 + if (!strcmp(var, "committer.email")) {
544 + if (!value)
545 + return config_error_nonbool(var);
546 + strbuf_reset(&git_committer_email);
547 + strbuf_addstr(&git_committer_email, value);
548 + committer_ident_explicitly_given |= IDENT_MAIL_GIVEN;
549 + ident_config_given |= IDENT_MAIL_GIVEN;
550 return 0;
551 }
552
@@ -505,6 +575,16 @@ int git_ident_config(const char *var, const char *value, void *data)
575 return 0;
576 }
577
578 +int git_ident_config(const char *var, const char *value, void *data)
579 +{
580 + if (!strcmp(var, "user.useconfigonly")) {
581 + ident_use_config_only = git_config_bool(var, value);
582 + return 0;
583 + }
584 +
585 + return set_ident(var, value);
586 +}
587 +
588 static int buf_cmp(const char *a_begin, const char *a_end,
589 const char *b_begin, const char *b_end)
590 {
log-tree.c
+1 -2
@@ -687,8 +687,7 @@ void show_log(struct rev_info *opt)
687 */
688 if (ctx.need_8bit_cte >= 0 && opt->add_signoff)
689 ctx.need_8bit_cte =
690 - has_non_ascii(fmt_name(getenv("GIT_COMMITTER_NAME"),
691 - getenv("GIT_COMMITTER_EMAIL")));
690 + has_non_ascii(fmt_name(WANT_COMMITTER_IDENT));
691 ctx.date_mode = opt->date_mode;
692 ctx.date_mode_explicit = opt->date_mode_explicit;
693 ctx.abbrev = opt->diffopt.abbrev;
sequencer.c
+2 -3
@@ -836,7 +836,7 @@ static const char *read_author_ident(struct strbuf *buf)
836 }
837
838 strbuf_reset(&out);
839 - strbuf_addstr(&out, fmt_ident(name, email, date, 0));
839 + strbuf_addstr(&out, fmt_ident(name, email, WANT_AUTHOR_IDENT, date, 0));
840 strbuf_swap(buf, &out);
841 strbuf_release(&out);
842 free(name);
@@ -4087,8 +4087,7 @@ void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)
4087 int has_footer;
4088
4089 strbuf_addstr(&sob, sign_off_header);
4090 - strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"),
4091 - getenv("GIT_COMMITTER_EMAIL")));
4090 + strbuf_addstr(&sob, fmt_name(WANT_COMMITTER_IDENT));
4091 strbuf_addch(&sob, '\n');
4092
4093 if (!ignore_footer)
t/t7517-per-repo-email.sh
+74
@@ -85,4 +85,78 @@ test_expect_success REBASE_P \
85 test_must_fail git rebase -p master
86 '
87
88 +test_expect_success 'author.name overrides user.name' '
89 + test_config user.name user &&
90 + test_config user.email user@example.com &&
91 + test_config author.name author &&
92 + test_commit author-name-override-user &&
93 + echo author user@example.com > expected-author &&
94 + echo user user@example.com > expected-committer &&
95 + git log --format="%an %ae" -1 > actual-author &&
96 + git log --format="%cn %ce" -1 > actual-committer &&
97 + test_cmp expected-author actual-author &&
98 + test_cmp expected-committer actual-committer
99 +'
100 +
101 +test_expect_success 'author.email overrides user.email' '
102 + test_config user.name user &&
103 + test_config user.email user@example.com &&
104 + test_config author.email author@example.com &&
105 + test_commit author-email-override-user &&
106 + echo user author@example.com > expected-author &&
107 + echo user user@example.com > expected-committer &&
108 + git log --format="%an %ae" -1 > actual-author &&
109 + git log --format="%cn %ce" -1 > actual-committer &&
110 + test_cmp expected-author actual-author &&
111 + test_cmp expected-committer actual-committer
112 +'
113 +
114 +test_expect_success 'committer.name overrides user.name' '
115 + test_config user.name user &&
116 + test_config user.email user@example.com &&
117 + test_config committer.name committer &&
118 + test_commit committer-name-override-user &&
119 + echo user user@example.com > expected-author &&
120 + echo committer user@example.com > expected-committer &&
121 + git log --format="%an %ae" -1 > actual-author &&
122 + git log --format="%cn %ce" -1 > actual-committer &&
123 + test_cmp expected-author actual-author &&
124 + test_cmp expected-committer actual-committer
125 +'
126 +
127 +test_expect_success 'committer.email overrides user.email' '
128 + test_config user.name user &&
129 + test_config user.email user@example.com &&
130 + test_config committer.email committer@example.com &&
131 + test_commit committer-email-override-user &&
132 + echo user user@example.com > expected-author &&
133 + echo user committer@example.com > expected-committer &&
134 + git log --format="%an %ae" -1 > actual-author &&
135 + git log --format="%cn %ce" -1 > actual-committer &&
136 + test_cmp expected-author actual-author &&
137 + test_cmp expected-committer actual-committer
138 +'
139 +
140 +test_expect_success 'author and committer environment variables override config settings' '
141 + test_config user.name user &&
142 + test_config user.email user@example.com &&
143 + test_config author.name author &&
144 + test_config author.email author@example.com &&
145 + test_config committer.name committer &&
146 + test_config committer.email committer@example.com &&
147 + GIT_AUTHOR_NAME=env_author && export GIT_AUTHOR_NAME &&
148 + GIT_AUTHOR_EMAIL=env_author@example.com && export GIT_AUTHOR_EMAIL &&
149 + GIT_COMMITTER_NAME=env_commit && export GIT_COMMITTER_NAME &&
150 + GIT_COMMITTER_EMAIL=env_commit@example.com && export GIT_COMMITTER_EMAIL &&
151 + test_commit env-override-conf &&
152 + echo env_author env_author@example.com > expected-author &&
153 + echo env_commit env_commit@example.com > expected-committer &&
154 + git log --format="%an %ae" -1 > actual-author &&
155 + git log --format="%cn %ce" -1 > actual-committer &&
156 + sane_unset GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL &&
157 + sane_unset GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL &&
158 + test_cmp expected-author actual-author &&
159 + test_cmp expected-committer actual-committer
160 +'
161 +
162 test_done