Skip to content

Commit 695db86

Browse files
committed
Merge branch 'jc/commit-hook-authorship' into maint
"git commit --author=$name" did not tell the name that was being recorded in the resulting commit to hooks, even though it does do so when the end user overrode the authorship via the "GIT_AUTHOR_NAME" environment variable. * jc/commit-hook-authorship: commit: pass author/committer info to hooks t7503: does pre-commit-hook learn authorship? ident.c: add split_ident_line() to parse formatted ident line
2 parents 7b90ed5 + 7dfe8ad commit 695db86

File tree

5 files changed

+141
-47
lines changed

5 files changed

+141
-47
lines changed

builtin/commit.c

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -533,9 +533,20 @@ static int is_a_merge(const struct commit *current_head)
533533

534534
static const char sign_off_header[] = "Signed-off-by: ";
535535

536+
static void export_one(const char *var, const char *s, const char *e, int hack)
537+
{
538+
struct strbuf buf = STRBUF_INIT;
539+
if (hack)
540+
strbuf_addch(&buf, hack);
541+
strbuf_addf(&buf, "%.*s", (int)(e - s), s);
542+
setenv(var, buf.buf, 1);
543+
strbuf_release(&buf);
544+
}
545+
536546
static void determine_author_info(struct strbuf *author_ident)
537547
{
538548
char *name, *email, *date;
549+
struct ident_split author;
539550

540551
name = getenv("GIT_AUTHOR_NAME");
541552
email = getenv("GIT_AUTHOR_EMAIL");
@@ -585,6 +596,11 @@ static void determine_author_info(struct strbuf *author_ident)
585596
date = force_date;
586597
strbuf_addstr(author_ident, fmt_ident(name, email, date,
587598
IDENT_ERROR_ON_NO_NAME));
599+
if (!split_ident_line(&author, author_ident->buf, author_ident->len)) {
600+
export_one("GIT_AUTHOR_NAME", author.name_begin, author.name_end, 0);
601+
export_one("GIT_AUTHOR_EMAIL", author.mail_begin, author.mail_end, 0);
602+
export_one("GIT_AUTHOR_DATE", author.date_begin, author.tz_end, '@');
603+
}
588604
}
589605

590606
static int ends_rfc2822_footer(struct strbuf *sb)
@@ -652,6 +668,9 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
652668
int ident_shown = 0;
653669
int clean_message_contents = (cleanup_mode != CLEANUP_NONE);
654670

671+
/* This checks and barfs if author is badly specified */
672+
determine_author_info(author_ident);
673+
655674
if (!no_verify && run_hook(index_file, "pre-commit", NULL))
656675
return 0;
657676

@@ -771,9 +790,6 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
771790

772791
strbuf_release(&sb);
773792

774-
/* This checks and barfs if author is badly specified */
775-
determine_author_info(author_ident);
776-
777793
/* This checks if committer ident is explicitly given */
778794
strbuf_addstr(&committer_ident, git_committer_info(0));
779795
if (use_editor && include_status) {

cache.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,22 @@ extern const char *fmt_name(const char *name, const char *email);
928928
extern const char *git_editor(void);
929929
extern const char *git_pager(int stdout_is_tty);
930930

931+
struct ident_split {
932+
const char *name_begin;
933+
const char *name_end;
934+
const char *mail_begin;
935+
const char *mail_end;
936+
const char *date_begin;
937+
const char *date_end;
938+
const char *tz_begin;
939+
const char *tz_end;
940+
};
941+
/*
942+
* Signals an success with 0, but time part of the result may be NULL
943+
* if the input lacks timestamp and zone
944+
*/
945+
extern int split_ident_line(struct ident_split *, const char *, int);
946+
931947
struct checkout {
932948
const char *base_dir;
933949
int base_dir_len;

ident.c

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,74 @@ static int copy(char *buf, size_t size, int offset, const char *src)
220220
return offset;
221221
}
222222

223+
/*
224+
* Reverse of fmt_ident(); given an ident line, split the fields
225+
* to allow the caller to parse it.
226+
* Signal a success by returning 0, but date/tz fields of the result
227+
* can still be NULL if the input line only has the name/email part
228+
* (e.g. reading from a reflog entry).
229+
*/
230+
int split_ident_line(struct ident_split *split, const char *line, int len)
231+
{
232+
const char *cp;
233+
size_t span;
234+
int status = -1;
235+
236+
memset(split, 0, sizeof(*split));
237+
238+
split->name_begin = line;
239+
for (cp = line; *cp && cp < line + len; cp++)
240+
if (*cp == '<') {
241+
split->mail_begin = cp + 1;
242+
break;
243+
}
244+
if (!split->mail_begin)
245+
return status;
246+
247+
for (cp = split->mail_begin - 2; line < cp; cp--)
248+
if (!isspace(*cp)) {
249+
split->name_end = cp + 1;
250+
break;
251+
}
252+
if (!split->name_end)
253+
return status;
254+
255+
for (cp = split->mail_begin; cp < line + len; cp++)
256+
if (*cp == '>') {
257+
split->mail_end = cp;
258+
break;
259+
}
260+
if (!split->mail_end)
261+
return status;
262+
263+
for (cp = split->mail_end + 1; cp < line + len && isspace(*cp); cp++)
264+
;
265+
if (line + len <= cp)
266+
goto person_only;
267+
split->date_begin = cp;
268+
span = strspn(cp, "0123456789");
269+
if (!span)
270+
goto person_only;
271+
split->date_end = split->date_begin + span;
272+
for (cp = split->date_end; cp < line + len && isspace(*cp); cp++)
273+
;
274+
if (line + len <= cp || (*cp != '+' && *cp != '-'))
275+
goto person_only;
276+
split->tz_begin = cp;
277+
span = strspn(cp + 1, "0123456789");
278+
if (!span)
279+
goto person_only;
280+
split->tz_end = split->tz_begin + 1 + span;
281+
return 0;
282+
283+
person_only:
284+
split->date_begin = NULL;
285+
split->date_end = NULL;
286+
split->tz_begin = NULL;
287+
split->tz_end = NULL;
288+
return 0;
289+
}
290+
223291
static const char *env_hint =
224292
"\n"
225293
"*** Please tell me who you are.\n"

pretty.c

Lines changed: 20 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -531,41 +531,24 @@ static size_t format_person_part(struct strbuf *sb, char part,
531531
{
532532
/* currently all placeholders have same length */
533533
const int placeholder_len = 2;
534-
int start, end, tz = 0;
534+
int tz;
535535
unsigned long date = 0;
536-
char *ep;
537-
const char *name_start, *name_end, *mail_start, *mail_end, *msg_end = msg+len;
538536
char person_name[1024];
539537
char person_mail[1024];
538+
struct ident_split s;
539+
const char *name_start, *name_end, *mail_start, *mail_end;
540540

541-
/* advance 'end' to point to email start delimiter */
542-
for (end = 0; end < len && msg[end] != '<'; end++)
543-
; /* do nothing */
544-
545-
/*
546-
* When end points at the '<' that we found, it should have
547-
* matching '>' later, which means 'end' must be strictly
548-
* below len - 1.
549-
*/
550-
if (end >= len - 2)
541+
if (split_ident_line(&s, msg, len) < 0)
551542
goto skip;
552543

553-
/* Seek for both name and email part */
554-
name_start = msg;
555-
name_end = msg+end;
556-
while (name_end > name_start && isspace(*(name_end-1)))
557-
name_end--;
558-
mail_start = msg+end+1;
559-
mail_end = mail_start;
560-
while (mail_end < msg_end && *mail_end != '>')
561-
mail_end++;
562-
if (mail_end == msg_end)
563-
goto skip;
564-
end = mail_end-msg;
544+
name_start = s.name_begin;
545+
name_end = s.name_end;
546+
mail_start = s.mail_begin;
547+
mail_end = s.mail_end;
565548

566549
if (part == 'N' || part == 'E') { /* mailmap lookup */
567-
strlcpy(person_name, name_start, name_end-name_start+1);
568-
strlcpy(person_mail, mail_start, mail_end-mail_start+1);
550+
strlcpy(person_name, name_start, name_end - name_start + 1);
551+
strlcpy(person_mail, mail_start, mail_end - mail_start + 1);
569552
mailmap_name(person_mail, sizeof(person_mail), person_name, sizeof(person_name));
570553
name_start = person_name;
571554
name_end = name_start + strlen(person_name);
@@ -581,28 +564,20 @@ static size_t format_person_part(struct strbuf *sb, char part,
581564
return placeholder_len;
582565
}
583566

584-
/* advance 'start' to point to date start delimiter */
585-
for (start = end + 1; start < len && isspace(msg[start]); start++)
586-
; /* do nothing */
587-
if (start >= len)
588-
goto skip;
589-
date = strtoul(msg + start, &ep, 10);
590-
if (msg + start == ep)
567+
if (!s.date_begin)
591568
goto skip;
592569

570+
date = strtoul(s.date_begin, NULL, 10);
571+
593572
if (part == 't') { /* date, UNIX timestamp */
594-
strbuf_add(sb, msg + start, ep - (msg + start));
573+
strbuf_add(sb, s.date_begin, s.date_end - s.date_begin);
595574
return placeholder_len;
596575
}
597576

598577
/* parse tz */
599-
for (start = ep - msg + 1; start < len && isspace(msg[start]); start++)
600-
; /* do nothing */
601-
if (start + 1 < len) {
602-
tz = strtoul(msg + start + 1, NULL, 10);
603-
if (msg[start] == '-')
604-
tz = -tz;
605-
}
578+
tz = strtoul(s.tz_begin + 1, NULL, 10);
579+
if (*s.tz_begin == '-')
580+
tz = -tz;
606581

607582
switch (part) {
608583
case 'd': /* date */
@@ -621,8 +596,9 @@ static size_t format_person_part(struct strbuf *sb, char part,
621596

622597
skip:
623598
/*
624-
* bogus commit, 'sb' cannot be updated, but we still need to
625-
* compute a valid return value.
599+
* reading from either a bogus commit, or a reflog entry with
600+
* %gn, %ge, etc.; 'sb' cannot be updated, but we still need
601+
* to compute a valid return value.
626602
*/
627603
if (part == 'n' || part == 'e' || part == 't' || part == 'd'
628604
|| part == 'D' || part == 'r' || part == 'i')

t/t7503-pre-commit-hook.sh

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,22 @@ test_expect_success 'with failing hook requiring GIT_PREFIX' '
118118
git checkout -- file
119119
'
120120

121+
test_expect_success 'check the author in hook' '
122+
write_script "$HOOK" <<-\EOF &&
123+
test "$GIT_AUTHOR_NAME" = "New Author" &&
124+
test "$GIT_AUTHOR_EMAIL" = "[email protected]"
125+
EOF
126+
test_must_fail git commit --allow-empty -m "by a.u.thor" &&
127+
(
128+
GIT_AUTHOR_NAME="New Author" &&
129+
GIT_AUTHOR_EMAIL="[email protected]" &&
130+
export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL &&
131+
git commit --allow-empty -m "by new.author via env" &&
132+
git show -s
133+
) &&
134+
git commit --author="New Author <[email protected]>" \
135+
--allow-empty -m "by new.author via command line" &&
136+
git show -s
137+
'
138+
121139
test_done

0 commit comments

Comments
 (0)