* Stop a git commit by a specific author using pre-commit hook @ 2012-03-09 1:15 Adrian Cornish 2012-03-10 19:41 ` Neal Kreitzinger 0 siblings, 1 reply; 13+ messages in thread From: Adrian Cornish @ 2012-03-09 1:15 UTC (permalink / raw) To: git Hi All, My plan is to use git to keep track of changes in /etc but when committing I want to have the person making the change specify themselves as author by adding the --author option on the commandline. So I would like to stop accidental commits as root. I tried creating this pre-commit hook but it is not working - git var is still returning root even if I specify the author on commit line. TIA Adrian AUTHOR=`git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/\1/p'` if [ "$AUTHOR" == "root <root@localhost>" ]; then echo "Please commit under your own user name instead of \"$AUTHOR\":" echo 'git commit --author="Adrian"' echo "or if your name is not already in logs use full ident" echo 'git commit --author="Adrian Cornish <a@localhost>"' exit 1 fi exit 0 ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Stop a git commit by a specific author using pre-commit hook 2012-03-09 1:15 Stop a git commit by a specific author using pre-commit hook Adrian Cornish @ 2012-03-10 19:41 ` Neal Kreitzinger 2012-03-10 21:54 ` Junio C Hamano 0 siblings, 1 reply; 13+ messages in thread From: Neal Kreitzinger @ 2012-03-10 19:41 UTC (permalink / raw) To: Adrian Cornish; +Cc: git On 3/8/2012 7:15 PM, Adrian Cornish wrote: > My plan is to use git to keep track of changes in /etc but when > committing I want to have the person making the change specify > themselves as author by adding the --author option on the commandline. > > So I would like to stop accidental commits as root. > > I tried creating this pre-commit hook but it is not working - git var > is still returning root even if I specify the author on commit line. > > AUTHOR=`git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/\1/p'` > if [ "$AUTHOR" == "root<root@localhost>" ]; > then > echo "Please commit under your own user name instead of \"$AUTHOR\":" > echo 'git commit --author="Adrian"' > echo "or if your name is not already in logs use full ident" > echo 'git commit --author="Adrian Cornish<a@localhost>"' > exit 1 > fi > exit 0 We use whoami in our pre-commit hook to see who the user is that is doing the commit. I think you could also use GIT_COMMITTER_NAME or linux $USER environment variables. Either way, the --author seems like an unnecessary and unreliable way to get the username. You're relying on the user to remember or obey that step. By doing --author, you also seem to be implying that the git commit author value may not be the same as the user doing the commit so that would also ruin the original commit author data audit trail. I haven't used these particular git environment variables, but I suspect they are dependent upon the user having their gitconfig filled out properly, so the linux whoami and $USER are probably more reliable. If people can su to root then $USER will not work because it will still be set to their original user name (before they did su to root). Therefore, "whoami" seems like your best solution. v/r, neal ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Stop a git commit by a specific author using pre-commit hook 2012-03-10 19:41 ` Neal Kreitzinger @ 2012-03-10 21:54 ` Junio C Hamano 2012-03-10 23:03 ` Neal Kreitzinger 0 siblings, 1 reply; 13+ messages in thread From: Junio C Hamano @ 2012-03-10 21:54 UTC (permalink / raw) To: Neal Kreitzinger; +Cc: Adrian Cornish, git Neal Kreitzinger <nkreitzinger@gmail.com> writes: > On 3/8/2012 7:15 PM, Adrian Cornish wrote: >> My plan is to use git to keep track of changes in /etc but when >> committing I want to have the person making the change specify >> themselves as author by adding the --author option on the commandline. > >> So I would like to stop accidental commits as root. > ... > We use whoami in our pre-commit hook to see who the user is that is > doing the commit. I think you could also use GIT_COMMITTER_NAME or > linux $USER environment variables. Either way, the --author seems > like an unnecessary and unreliable way to get the username. > ... If people can su to root > then $USER will not work because it will still be set to their > original user name (before they did su to root). Therefore, "whoami" > seems like your best solution. When people want to raise a red flag against a commit made by root, they are coming from two different schools. One is "do not run 'git' or any development tool for that matter while being root". It is a good discipline to follow in general to limit what you do with escalated privilege to the minimum. The other is "record who actually did the work, not 'root' that people cannot later track down who it actually was". People from this school do not mind running development tools as root. And your advice is a good one for the former, but not very relevant for the latter. And I think Adrian is asking for the latter. ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Stop a git commit by a specific author using pre-commit hook 2012-03-10 21:54 ` Junio C Hamano @ 2012-03-10 23:03 ` Neal Kreitzinger 2012-03-11 11:05 ` Junio C Hamano 0 siblings, 1 reply; 13+ messages in thread From: Neal Kreitzinger @ 2012-03-10 23:03 UTC (permalink / raw) To: Junio C Hamano; +Cc: Adrian Cornish, git On 3/10/2012 3:54 PM, Junio C Hamano wrote: > Neal Kreitzinger<nkreitzinger@gmail.com> writes: > >> On 3/8/2012 7:15 PM, Adrian Cornish wrote: >>> My plan is to use git to keep track of changes in /etc but when >>> committing I want to have the person making the change specify >>> themselves as author by adding the --author option on the commandline. >>> So I would like to stop accidental commits as root. >> ... >> We use whoami in our pre-commit hook to see who the user is that is >> doing the commit. I think you could also use GIT_COMMITTER_NAME or >> linux $USER environment variables. Either way, the --author seems >> like an unnecessary and unreliable way to get the username. >> ... If people can su to root >> then $USER will not work because it will still be set to their >> original user name (before they did su to root). Therefore, "whoami" >> seems like your best solution. > When people want to raise a red flag against a commit made by root, > they are coming from two different schools. > > One is "do not run 'git' or any development tool for that matter > while being root". It is a good discipline to follow in general to > limit what you do with escalated privilege to the minimum. > > The other is "record who actually did the work, not 'root' that > people cannot later track down who it actually was". People from > this school do not mind running development tools as root. > > And your advice is a good one for the former, but not very relevant > for the latter. > > And I think Adrian is asking for the latter. > Now I see. In that case, at the point pre-commit is run git has not overriden GIT_AUTHOR_IDENT with your --author value, yet. I don't know if that is a bug or not. The prepare-commit-msg hook is the same way. However, by the time the commit-msg hook runs git has placed your --author override into GIT_AUTHOR_IDENT so if you check it there it will work and you can abort the commit. Of course, at that point the user has already typed their commit message and may lose it. You could create a git alias like "git root-commit" that prompts them to enter their authorname and then runs git-commit --author with that user provided value. Pre-commit hook could detect root user and error out telling them to run git-root-commit instead. v/r, neal ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Stop a git commit by a specific author using pre-commit hook 2012-03-10 23:03 ` Neal Kreitzinger @ 2012-03-11 11:05 ` Junio C Hamano 2012-03-11 11:08 ` [PATCH 1/(3/4)] test: does pre-commit-hook learn authorship? Junio C Hamano 2012-03-11 11:09 ` [PATCH 2/(3/4)] ident.c: add split_ident_line() to parse formatted ident line Junio C Hamano 0 siblings, 2 replies; 13+ messages in thread From: Junio C Hamano @ 2012-03-11 11:05 UTC (permalink / raw) To: Neal Kreitzinger; +Cc: Adrian Cornish, git Neal Kreitzinger <nkreitzinger@gmail.com> writes: > ... at the point pre-commit is run git has not > overriden GIT_AUTHOR_IDENT with your --author value, yet. I don't > know if that is a bug or not. The prepare-commit-msg hook is the same > way. I'd call it a bug, as the command line --author should have the same effect as GIT_AUTHOR_NAME and friends observable by the outside world. There are two slightly different approaches to solve this, that share the first two preparatory commits. I'll send out the preparatory ones first and then show both of these approaches. ^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 1/(3/4)] test: does pre-commit-hook learn authorship? 2012-03-11 11:05 ` Junio C Hamano @ 2012-03-11 11:08 ` Junio C Hamano 2012-03-11 20:30 ` Johannes Sixt 2012-03-11 11:09 ` [PATCH 2/(3/4)] ident.c: add split_ident_line() to parse formatted ident line Junio C Hamano 1 sibling, 1 reply; 13+ messages in thread From: Junio C Hamano @ 2012-03-11 11:08 UTC (permalink / raw) To: git; +Cc: Neal Kreitzinger, Adrian Cornish When "--author" option is used to lie the authorship to "git commit" command, hooks should learn the author name and email just like when GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL environment variables are used to lie the authorship. Test this. Signed-off-by: Junio C Hamano <gitster@pobox.com> --- * This is the first one of two patches that are shread by two approaches. What it tests should be obvious---the current code fails with the last piece that use --author. t/t7503-pre-commit-hook.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/t/t7503-pre-commit-hook.sh b/t/t7503-pre-commit-hook.sh index ee7f0cd..fc6de5b 100755 --- a/t/t7503-pre-commit-hook.sh +++ b/t/t7503-pre-commit-hook.sh @@ -118,4 +118,22 @@ test_expect_success 'with failing hook requiring GIT_PREFIX' ' git checkout -- file ' +test_expect_failure 'check the author in hook' ' + cat >"$HOOK" <<-\EOF && + test "$GIT_AUTHOR_NAME" = "New Author" && + test "$GIT_AUTHOR_EMAIL" = "newauthor@example.com" + EOF + test_must_fail git commit --allow-empty -m "by a.u.thor" && + ( + GIT_AUTHOR_NAME="New Author" && + GIT_AUTHOR_EMAIL="newauthor@example.com" && + export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL && + git commit --allow-empty -m "by new.author via env" && + git show -s + ) && + git commit --author="New Author <newauthor@example.com>" \ + --allow-empty -m "by new.author via command line" && + git show -s +' + test_done -- 1.7.10.rc0.33.g8866af ^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH 1/(3/4)] test: does pre-commit-hook learn authorship? 2012-03-11 11:08 ` [PATCH 1/(3/4)] test: does pre-commit-hook learn authorship? Junio C Hamano @ 2012-03-11 20:30 ` Johannes Sixt 2012-03-11 21:09 ` Junio C Hamano 0 siblings, 1 reply; 13+ messages in thread From: Johannes Sixt @ 2012-03-11 20:30 UTC (permalink / raw) To: Junio C Hamano; +Cc: git, Neal Kreitzinger, Adrian Cornish Am 11.03.2012 12:08, schrieb Junio C Hamano: > +test_expect_failure 'check the author in hook' ' > + cat >"$HOOK" <<-\EOF && > + test "$GIT_AUTHOR_NAME" = "New Author" && > + test "$GIT_AUTHOR_EMAIL" = "newauthor@example.com" > + EOF Please insert #!/bin/sh as the first line of the script. We'll need it on Windows. -- Hannes ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 1/(3/4)] test: does pre-commit-hook learn authorship? 2012-03-11 20:30 ` Johannes Sixt @ 2012-03-11 21:09 ` Junio C Hamano 0 siblings, 0 replies; 13+ messages in thread From: Junio C Hamano @ 2012-03-11 21:09 UTC (permalink / raw) To: Johannes Sixt; +Cc: git, Neal Kreitzinger, Adrian Cornish Thanks; I should have just used write_script() for this. ^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 2/(3/4)] ident.c: add split_ident_line() to parse formatted ident line 2012-03-11 11:05 ` Junio C Hamano 2012-03-11 11:08 ` [PATCH 1/(3/4)] test: does pre-commit-hook learn authorship? Junio C Hamano @ 2012-03-11 11:09 ` Junio C Hamano 2012-03-11 11:11 ` [PATCH 3/3] commit: pass author/committer info to hooks Junio C Hamano ` (2 more replies) 1 sibling, 3 replies; 13+ messages in thread From: Junio C Hamano @ 2012-03-11 11:09 UTC (permalink / raw) To: git; +Cc: Neal Kreitzinger, Adrian Cornish The commit formatting logic format_person_part() in pretty.c implements the logic to split an author/committer ident line into its parts, intermixed with logic to compute its output using these piece it computes. Separate the former out to a helper function split_ident_line() so that other codepath can use the same logic, and rewrite the function using the helper function. Signed-off-by: Junio C Hamano <gitster@pobox.com> --- * The second shared change between the two approaches. cache.h | 16 +++++++++++++++ ident.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ pretty.c | 64 ++++++++++++++++++---------------------------------------- 3 files changed, 104 insertions(+), 44 deletions(-) diff --git a/cache.h b/cache.h index 3a8e125..7c42eca 100644 --- a/cache.h +++ b/cache.h @@ -928,6 +928,22 @@ extern const char *fmt_name(const char *name, const char *email); extern const char *git_editor(void); extern const char *git_pager(int stdout_is_tty); +struct ident_split { + const char *name_begin; + const char *name_end; + const char *mail_begin; + const char *mail_end; + const char *date_begin; + const char *date_end; + const char *tz_begin; + const char *tz_end; +}; +/* + * Signals an success with 0, but time part of the result may be NULL + * if the input lacks timestamp and zone + */ +extern int split_ident_line(struct ident_split *, const char *, int); + struct checkout { const char *base_dir; int base_dir_len; diff --git a/ident.c b/ident.c index f619619..87c697c 100644 --- a/ident.c +++ b/ident.c @@ -220,6 +220,74 @@ static int copy(char *buf, size_t size, int offset, const char *src) return offset; } +/* + * Reverse of fmt_ident(); given an ident line, split the fields + * to allow the caller to parse it. + * Signal a success by returning 0, but date/tz fields of the result + * can still be NULL if the input line only has the name/email part + * (e.g. reading from a reflog entry). + */ +int split_ident_line(struct ident_split *split, const char *line, int len) +{ + const char *cp; + size_t span; + int status = -1; + + memset(split, 0, sizeof(*split)); + + split->name_begin = line; + for (cp = line; *cp && cp < line + len; cp++) + if (*cp == '<') { + split->mail_begin = cp + 1; + break; + } + if (!split->mail_begin) + return status; + + for (cp = split->mail_begin - 2; line < cp; cp--) + if (!isspace(*cp)) { + split->name_end = cp + 1; + break; + } + if (!split->name_end) + return status; + + for (cp = split->mail_begin; cp < line + len; cp++) + if (*cp == '>') { + split->mail_end = cp; + break; + } + if (!split->mail_end) + return status; + + for (cp = split->mail_end + 1; cp < line + len && isspace(*cp); cp++) + ; + if (line + len <= cp) + goto person_only; + split->date_begin = cp; + span = strspn(cp, "0123456789"); + if (!span) + goto person_only; + split->date_end = split->date_begin + span; + for (cp = split->date_end; cp < line + len && isspace(*cp); cp++) + ; + if (line + len <= cp || (*cp != '+' && *cp != '-')) + goto person_only; + split->tz_begin = cp; + span = strspn(cp + 1, "0123456789"); + if (!span) + goto person_only; + split->tz_end = split->tz_begin + 1 + span; + return 0; + +person_only: + split->date_begin = NULL; + split->date_end = NULL; + split->tz_begin = NULL; + split->tz_end = NULL; + return 0; +} + static const char *env_hint = "\n" "*** Please tell me who you are.\n" diff --git a/pretty.c b/pretty.c index 8688b8f..f2dee30 100644 --- a/pretty.c +++ b/pretty.c @@ -531,41 +531,24 @@ static size_t format_person_part(struct strbuf *sb, char part, { /* currently all placeholders have same length */ const int placeholder_len = 2; - int start, end, tz = 0; + int tz; unsigned long date = 0; - char *ep; - const char *name_start, *name_end, *mail_start, *mail_end, *msg_end = msg+len; char person_name[1024]; char person_mail[1024]; + struct ident_split s; + const char *name_start, *name_end, *mail_start, *mail_end; - /* advance 'end' to point to email start delimiter */ - for (end = 0; end < len && msg[end] != '<'; end++) - ; /* do nothing */ - - /* - * When end points at the '<' that we found, it should have - * matching '>' later, which means 'end' must be strictly - * below len - 1. - */ - if (end >= len - 2) + if (split_ident_line(&s, msg, len) < 0) goto skip; - /* Seek for both name and email part */ - name_start = msg; - name_end = msg+end; - while (name_end > name_start && isspace(*(name_end-1))) - name_end--; - mail_start = msg+end+1; - mail_end = mail_start; - while (mail_end < msg_end && *mail_end != '>') - mail_end++; - if (mail_end == msg_end) - goto skip; - end = mail_end-msg; + name_start = s.name_begin; + name_end = s.name_end; + mail_start = s.mail_begin; + mail_end = s.mail_end; if (part == 'N' || part == 'E') { /* mailmap lookup */ - strlcpy(person_name, name_start, name_end-name_start+1); - strlcpy(person_mail, mail_start, mail_end-mail_start+1); + strlcpy(person_name, name_start, name_end - name_start + 1); + strlcpy(person_mail, mail_start, mail_end - mail_start + 1); mailmap_name(person_mail, sizeof(person_mail), person_name, sizeof(person_name)); name_start = person_name; name_end = name_start + strlen(person_name); @@ -581,28 +564,20 @@ static size_t format_person_part(struct strbuf *sb, char part, return placeholder_len; } - /* advance 'start' to point to date start delimiter */ - for (start = end + 1; start < len && isspace(msg[start]); start++) - ; /* do nothing */ - if (start >= len) - goto skip; - date = strtoul(msg + start, &ep, 10); - if (msg + start == ep) + if (!s.date_begin) goto skip; + date = strtoul(s.date_begin, NULL, 10); + if (part == 't') { /* date, UNIX timestamp */ - strbuf_add(sb, msg + start, ep - (msg + start)); + strbuf_add(sb, s.date_begin, s.date_end - s.date_begin); return placeholder_len; } /* parse tz */ - for (start = ep - msg + 1; start < len && isspace(msg[start]); start++) - ; /* do nothing */ - if (start + 1 < len) { - tz = strtoul(msg + start + 1, NULL, 10); - if (msg[start] == '-') - tz = -tz; - } + tz = strtoul(s.tz_begin + 1, NULL, 10); + if (*s.tz_begin == '-') + tz = -tz; switch (part) { case 'd': /* date */ @@ -621,8 +596,9 @@ static size_t format_person_part(struct strbuf *sb, char part, skip: /* - * bogus commit, 'sb' cannot be updated, but we still need to - * compute a valid return value. + * reading from either a bogus commit, or a reflog entry with + * %gn, %ge, etc.; 'sb' cannot be updated, but we still need + * to compute a valid return value. */ if (part == 'n' || part == 'e' || part == 't' || part == 'd' || part == 'D' || part == 'r' || part == 'i') -- 1.7.10.rc0.33.g8866af ^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 3/3] commit: pass author/committer info to hooks 2012-03-11 11:09 ` [PATCH 2/(3/4)] ident.c: add split_ident_line() to parse formatted ident line Junio C Hamano @ 2012-03-11 11:11 ` Junio C Hamano 2012-03-12 11:29 ` Jeff King 2012-03-11 11:15 ` [PATCH 3/4] run_hook(): enhance the interface to pass arbitrary environment Junio C Hamano 2012-03-11 11:16 ` [PATCH 4/4] commit: pass author/committer info to hooks Junio C Hamano 2 siblings, 1 reply; 13+ messages in thread From: Junio C Hamano @ 2012-03-11 11:11 UTC (permalink / raw) To: git; +Cc: Neal Kreitzinger, Adrian Cornish When lying the author name via GIT_AUTHOR_NAME environment variable to "git commit", the hooks run by the command saw it and could act on the name that will be recorded in the final commit. When the user uses the "--author" option from the command line, the command should give the same information to the hook, and back when "git command" was a scripted Porcelain, it did set the environment variable and hooks can learn the author name from it. However, when the command was reimplemented in C, the rewritten code was not very faithful to the original, and hooks stopped getting the authorship information given with "--author". Fix this by exporting the necessary environment variables. Signed-off-by: Junio C Hamano <gitster@pobox.com> --- * This is the last patch of the simpler of the two approaches, that builds on top of the two common preparatory patches. It uses setenv() to directly affect the execution environment of "git commit" process, which is closer to the original scripted Porcelain implementation. builtin/commit.c | 22 +++++++++++++++++++--- t/t7503-pre-commit-hook.sh | 2 +- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/builtin/commit.c b/builtin/commit.c index eae5a29..57a60f9 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -533,9 +533,20 @@ static int is_a_merge(const struct commit *current_head) static const char sign_off_header[] = "Signed-off-by: "; +static void export_one(const char *var, const char *s, const char *e, int hack) +{ + struct strbuf buf = STRBUF_INIT; + if (hack) + strbuf_addch(&buf, hack); + strbuf_addf(&buf, "%.*s", (int)(e - s), s); + setenv(var, buf.buf, 1); + strbuf_release(&buf); +} + static void determine_author_info(struct strbuf *author_ident) { char *name, *email, *date; + struct ident_split author; name = getenv("GIT_AUTHOR_NAME"); email = getenv("GIT_AUTHOR_EMAIL"); @@ -585,6 +596,11 @@ static void determine_author_info(struct strbuf *author_ident) date = force_date; strbuf_addstr(author_ident, fmt_ident(name, email, date, IDENT_ERROR_ON_NO_NAME)); + if (!split_ident_line(&author, author_ident->buf, author_ident->len)) { + export_one("GIT_AUTHOR_NAME", author.name_begin, author.name_end, 0); + export_one("GIT_AUTHOR_EMAIL", author.mail_begin, author.mail_end, 0); + export_one("GIT_AUTHOR_DATE", author.date_begin, author.tz_end, '@'); + } } static int ends_rfc2822_footer(struct strbuf *sb) @@ -652,6 +668,9 @@ static int prepare_to_commit(const char *index_file, const char *prefix, int ident_shown = 0; int clean_message_contents = (cleanup_mode != CLEANUP_NONE); + /* This checks and barfs if author is badly specified */ + determine_author_info(author_ident); + if (!no_verify && run_hook(index_file, "pre-commit", NULL)) return 0; @@ -771,9 +790,6 @@ static int prepare_to_commit(const char *index_file, const char *prefix, strbuf_release(&sb); - /* This checks and barfs if author is badly specified */ - determine_author_info(author_ident); - /* This checks if committer ident is explicitly given */ strbuf_addstr(&committer_ident, git_committer_info(0)); if (use_editor && include_status) { diff --git a/t/t7503-pre-commit-hook.sh b/t/t7503-pre-commit-hook.sh index fc6de5b..9301a0c 100755 --- a/t/t7503-pre-commit-hook.sh +++ b/t/t7503-pre-commit-hook.sh @@ -118,7 +118,7 @@ test_expect_success 'with failing hook requiring GIT_PREFIX' ' git checkout -- file ' -test_expect_failure 'check the author in hook' ' +test_expect_success 'check the author in hook' ' cat >"$HOOK" <<-\EOF && test "$GIT_AUTHOR_NAME" = "New Author" && test "$GIT_AUTHOR_EMAIL" = "newauthor@example.com" -- 1.7.10.rc0.33.g8866af ^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH 3/3] commit: pass author/committer info to hooks 2012-03-11 11:11 ` [PATCH 3/3] commit: pass author/committer info to hooks Junio C Hamano @ 2012-03-12 11:29 ` Jeff King 0 siblings, 0 replies; 13+ messages in thread From: Jeff King @ 2012-03-12 11:29 UTC (permalink / raw) To: Junio C Hamano; +Cc: git, Neal Kreitzinger, Adrian Cornish On Sun, Mar 11, 2012 at 04:11:38AM -0700, Junio C Hamano wrote: > However, when the command was reimplemented in C, the rewritten code > was not very faithful to the original, and hooks stopped getting the > authorship information given with "--author". Fix this by exporting > the necessary environment variables. > [...] > * This is the last patch of the simpler of the two approaches, that > builds on top of the two common preparatory patches. It uses setenv() > to directly affect the execution environment of "git commit" process, > which is closer to the original scripted Porcelain implementation. FWIW, I like this approach better. It's simpler, and I think it is perfectly sane to define "--author" as "act as if the contents were in GIT_AUTHOR_*"[1]. We do the same thing already with "git --git-dir", "git --work-tree", and other variables, and it keeps the code simple by making the environment variable the definitive source. -Peff [1] Your approach is slightly more complex, in that it handles "-c" (which is a good thing). But I think the principle is the same. ^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 3/4] run_hook(): enhance the interface to pass arbitrary environment 2012-03-11 11:09 ` [PATCH 2/(3/4)] ident.c: add split_ident_line() to parse formatted ident line Junio C Hamano 2012-03-11 11:11 ` [PATCH 3/3] commit: pass author/committer info to hooks Junio C Hamano @ 2012-03-11 11:15 ` Junio C Hamano 2012-03-11 11:16 ` [PATCH 4/4] commit: pass author/committer info to hooks Junio C Hamano 2 siblings, 0 replies; 13+ messages in thread From: Junio C Hamano @ 2012-03-11 11:15 UTC (permalink / raw) To: git; +Cc: Neal Kreitzinger, Adrian Cornish Originally "run_hook()" that used to live in builtin/commit.c had a perfectly appropriate API and name for a private function to spawn a hook from "git commit" command. The only necessary tweak in the context was to optionally specify which file to use as the index file. But then we stupidly moved this private API to run-command.c without making the interface suitable for general consumption, and there is no way to tweak environment variables other than GIT_INDEX_FILE when running a hook. Correct this mistake by adding run_hook_e() that takes an array of environment variables. Signed-off-by: Junio C Hamano <gitster@pobox.com> --- * This is the third patch of the more complex of the two approaches. Instead of the simpler "setenv()" one, the final one tries to limit the environment change only to the hook scripts, without affecting the "git commit" process itself. It probably should not make much difference in the short run, but later when we want to distinguish where the authorship came from, it may be easier to tell with this approach as it does not contaminate the environment of "git commit" itself with what we obtained from "--author" option. Also this is independently a useful refactoring. run-command.c | 49 ++++++++++++++++++++++++++++++++++++------------- run-command.h | 1 + 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/run-command.c b/run-command.c index 1db8abf..a51d8e5 100644 --- a/run-command.c +++ b/run-command.c @@ -672,36 +672,59 @@ int finish_async(struct async *async) #endif } -int run_hook(const char *index_file, const char *name, ...) +static int run_hook_le(const char *const *env, + const char *name, + va_list args) { struct child_process hook; struct argv_array argv = ARGV_ARRAY_INIT; - const char *p, *env[2]; - char index[PATH_MAX]; - va_list args; + const char *p; int ret; if (access(git_path("hooks/%s", name), X_OK) < 0) return 0; - va_start(args, name); argv_array_push(&argv, git_path("hooks/%s", name)); while ((p = va_arg(args, const char *))) argv_array_push(&argv, p); - va_end(args); memset(&hook, 0, sizeof(hook)); hook.argv = argv.argv; hook.no_stdin = 1; hook.stdout_to_stderr = 1; - if (index_file) { - snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file); - env[0] = index; - env[1] = NULL; - hook.env = env; - } - + hook.env = env; ret = run_command(&hook); argv_array_clear(&argv); return ret; } + +int run_hook(const char *index_file, const char *name, ...) +{ + const char *const *env = NULL, *env_buf[2]; + struct strbuf index_buf = STRBUF_INIT; + int status; + va_list args; + + va_start(args, name); + if (index_file) { + strbuf_addf(&index_buf, "GIT_INDEX_FILE=%s", index_file); + env_buf[0] = index_buf.buf; + env_buf[1] = NULL; + env = env_buf; + } + status = run_hook_le(env, name, args); + va_end(args); + strbuf_release(&index_buf); + return status; +} + +int run_hook_e(const char *const *env, const char *name, ...) +{ + int status; + va_list args; + + va_start(args, name); + status = run_hook_le(env, name, args); + va_end(args); + return status; +} diff --git a/run-command.h b/run-command.h index 44f7d2b..87207b9 100644 --- a/run-command.h +++ b/run-command.h @@ -47,6 +47,7 @@ int finish_command(struct child_process *); int run_command(struct child_process *); extern int run_hook(const char *index_file, const char *name, ...); +extern int run_hook_e(const char *const *, const char *name, ...); #define RUN_COMMAND_NO_STDIN 1 #define RUN_GIT_CMD 2 /*If this is to be git sub-command */ -- 1.7.10.rc0.33.g8866af ^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 4/4] commit: pass author/committer info to hooks 2012-03-11 11:09 ` [PATCH 2/(3/4)] ident.c: add split_ident_line() to parse formatted ident line Junio C Hamano 2012-03-11 11:11 ` [PATCH 3/3] commit: pass author/committer info to hooks Junio C Hamano 2012-03-11 11:15 ` [PATCH 3/4] run_hook(): enhance the interface to pass arbitrary environment Junio C Hamano @ 2012-03-11 11:16 ` Junio C Hamano 2 siblings, 0 replies; 13+ messages in thread From: Junio C Hamano @ 2012-03-11 11:16 UTC (permalink / raw) To: git; +Cc: Neal Kreitzinger, Adrian Cornish When lying the author name via GIT_AUTHOR_NAME environment variable to "git commit", the hooks run by the command saw it and could act on the name that will be recorded in the final commit. When the user uses the "--author" option from the command line, the command should give the same information to the hook, and back when "git command" was a scripted Porcelain, it did set the environment variable and hooks can learn the author name from it. However, when the command was reimplemented in C, the rewritten code was not very faithful to the original, and hooks stopped getting the authorship information given with "--author". Fix this by exporting the necessary environment variables. Signed-off-by: Junio C Hamano <gitster@pobox.com> --- * And this is the final patch in the more complex of the two approaches. builtin/commit.c | 132 ++++++++++++++++++++++++++++++-------------- t/t7503-pre-commit-hook.sh | 2 +- 2 files changed, 93 insertions(+), 41 deletions(-) diff --git a/builtin/commit.c b/builtin/commit.c index eae5a29..4a47e39 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -27,6 +27,7 @@ #include "quote.h" #include "submodule.h" #include "gpg-interface.h" +#include "argv-array.h" static const char * const builtin_commit_usage[] = { "git commit [options] [--] <filepattern>...", @@ -637,13 +638,63 @@ static char *cut_ident_timestamp_part(char *string) return ket; } -static int prepare_to_commit(const char *index_file, const char *prefix, +struct commit_context { + const char *index_file; + struct strbuf *author_ident; + struct strbuf *committer_ident; + struct argv_array env; +}; + +static void setup_commit_context(struct commit_context *ctx, + const char *index_file, + struct strbuf *author_ident, + struct strbuf *committer_ident) +{ + struct ident_split s; + struct argv_array *env = &ctx->env; + + ctx->index_file = index_file; + ctx->author_ident = author_ident; + ctx->committer_ident = committer_ident; + argv_array_init(env); + if (index_file) + argv_array_pushf(env, "GIT_INDEX_FILE=%s", index_file); + + if (!split_ident_line(&s, author_ident->buf, author_ident->len)) { + argv_array_pushf(env, "GIT_AUTHOR_NAME=%.*s", + (int)(s.name_end - s.name_begin), + s.name_begin); + argv_array_pushf(env, "GIT_AUTHOR_EMAIL=%.*s", + (int)(s.mail_end - s.mail_begin), + s.mail_begin); + argv_array_pushf(env, "GIT_AUTHOR_DATE=@%.*s %.*s", + (int)(s.date_end - s.date_begin), + s.date_begin, + (int)(s.tz_end - s.tz_begin), + s.tz_begin); + } + + if (!split_ident_line(&s, committer_ident->buf, committer_ident->len)) { + argv_array_pushf(env, "GIT_COMMITTER_NAME=%.*s", + (int)(s.name_end - s.name_begin), + s.name_begin); + argv_array_pushf(env, "GIT_COMMITTER_EMAIL=%.*s", + (int)(s.mail_end - s.mail_begin), + s.mail_begin); + argv_array_pushf(env, "GIT_COMMITTER_DATE=@%.*s %.*s", + (int)(s.date_end - s.date_begin), + s.date_begin, + (int)(s.tz_end - s.tz_begin), + s.tz_begin); + } +} + +static int prepare_to_commit(struct commit_context *ctx, + const char *prefix, struct commit *current_head, - struct wt_status *s, - struct strbuf *author_ident) + struct wt_status *s) { struct stat statbuf; - struct strbuf committer_ident = STRBUF_INIT; int commitable, saved_color_setting; struct strbuf sb = STRBUF_INIT; char *buffer; @@ -652,7 +703,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, int ident_shown = 0; int clean_message_contents = (cleanup_mode != CLEANUP_NONE); - if (!no_verify && run_hook(index_file, "pre-commit", NULL)) + if (!no_verify && run_hook_e(ctx->env.argv, "pre-commit", NULL)) return 0; if (squash_message) { @@ -771,11 +822,6 @@ static int prepare_to_commit(const char *index_file, const char *prefix, strbuf_release(&sb); - /* This checks and barfs if author is badly specified */ - determine_author_info(author_ident); - - /* This checks if committer ident is explicitly given */ - strbuf_addstr(&committer_ident, git_committer_info(0)); if (use_editor && include_status) { char *ai_tmp, *ci_tmp; if (whence != FROM_COMMIT) @@ -809,28 +855,28 @@ static int prepare_to_commit(const char *index_file, const char *prefix, status_printf_ln(s, GIT_COLOR_NORMAL, "%s", only_include_assumed); - ai_tmp = cut_ident_timestamp_part(author_ident->buf); - ci_tmp = cut_ident_timestamp_part(committer_ident.buf); - if (strcmp(author_ident->buf, committer_ident.buf)) + ai_tmp = cut_ident_timestamp_part(ctx->author_ident->buf); + ci_tmp = cut_ident_timestamp_part(ctx->committer_ident->buf); + if (strcmp(ctx->author_ident->buf, ctx->committer_ident->buf)) status_printf_ln(s, GIT_COLOR_NORMAL, - _("%s" - "Author: %s"), - ident_shown++ ? "" : "\n", - author_ident->buf); + _("%s" + "Author: %s"), + ident_shown++ ? "" : "\n", + ctx->author_ident->buf); if (!user_ident_sufficiently_given()) status_printf_ln(s, GIT_COLOR_NORMAL, - _("%s" - "Committer: %s"), - ident_shown++ ? "" : "\n", - committer_ident.buf); + _("%s" + "Committer: %s"), + ident_shown++ ? "" : "\n", + ctx->committer_ident->buf); if (ident_shown) status_printf_ln(s, GIT_COLOR_NORMAL, ""); saved_color_setting = s->use_color; s->use_color = 0; - commitable = run_status(s->fp, index_file, prefix, 1, s); + commitable = run_status(s->fp, ctx->index_file, prefix, 1, s); s->use_color = saved_color_setting; *ai_tmp = ' '; @@ -850,8 +896,6 @@ static int prepare_to_commit(const char *index_file, const char *prefix, else commitable = index_differs_from(parent, 0); } - strbuf_release(&committer_ident); - fclose(s->fp); /* @@ -861,7 +905,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, */ if (!commitable && whence != FROM_MERGE && !allow_empty && !(amend && is_a_merge(current_head))) { - run_status(stdout, index_file, prefix, 0, s); + run_status(stdout, ctx->index_file, prefix, 0, s); if (amend) fputs(_(empty_amend_advice), stderr); else if (whence == FROM_CHERRY_PICK) @@ -875,22 +919,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix, * the editor and after we invoke run_status above. */ discard_cache(); - read_cache_from(index_file); + read_cache_from(ctx->index_file); if (update_main_cache_tree(0)) { error(_("Error building trees")); return 0; } - if (run_hook(index_file, "prepare-commit-msg", - git_path(commit_editmsg), hook_arg1, hook_arg2, NULL)) + if (run_hook_e(ctx->env.argv, "prepare-commit-msg", + git_path(commit_editmsg), hook_arg1, hook_arg2, NULL)) return 0; if (use_editor) { - char index[PATH_MAX]; - const char *env[2] = { NULL }; - env[0] = index; - snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file); - if (launch_editor(git_path(commit_editmsg), NULL, env)) { + if (launch_editor(git_path(commit_editmsg), NULL, ctx->env.argv)) { fprintf(stderr, _("Please supply the message using either -m or -F option.\n")); exit(1); @@ -898,7 +938,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, } if (!no_verify && - run_hook(index_file, "commit-msg", git_path(commit_editmsg), NULL)) { + run_hook_e(ctx->env.argv, "commit-msg", git_path(commit_editmsg), NULL)) { return 0; } @@ -1389,6 +1429,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) { struct strbuf sb = STRBUF_INIT; struct strbuf author_ident = STRBUF_INIT; + struct strbuf committer_ident = STRBUF_INIT; const char *index_file, *reflog_msg; char *nl, *p; unsigned char sha1[20]; @@ -1399,6 +1440,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) struct wt_status s; struct commit *current_head = NULL; struct commit_extra_header *extra = NULL; + struct commit_context commit_ctx; if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_commit_usage, builtin_commit_options); @@ -1420,10 +1462,16 @@ int cmd_commit(int argc, const char **argv, const char *prefix) return dry_run_commit(argc, argv, prefix, current_head, &s); index_file = prepare_index(argc, argv, prefix, current_head, 0); - /* Set up everything for writing the commit object. This includes - running hooks, writing the trees, and interacting with the user. */ - if (!prepare_to_commit(index_file, prefix, - current_head, &s, &author_ident)) { + /* + * Set up everything for writing the commit object. This includes + * running hooks, writing the trees, and interacting with the user. + */ + determine_author_info(&author_ident); + strbuf_addstr(&committer_ident, git_committer_info(0)); + setup_commit_context(&commit_ctx, + index_file, &author_ident, &committer_ident); + + if (!prepare_to_commit(&commit_ctx, prefix, current_head, &s)) { rollback_index_files(); return 1; } @@ -1513,7 +1561,6 @@ int cmd_commit(int argc, const char **argv, const char *prefix) rollback_index_files(); die(_("failed to write commit object")); } - strbuf_release(&author_ident); free_commit_extra_headers(extra); ref_lock = lock_any_ref_for_update("HEAD", @@ -1552,7 +1599,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix) "not exceeded, and then \"git reset HEAD\" to recover.")); rerere(0); - run_hook(get_index_file(), "post-commit", NULL); + + argv_array_clear(&commit_ctx.env); + setup_commit_context(&commit_ctx, + get_index_file(), + &author_ident, &committer_ident); + run_hook_e(commit_ctx.env.argv, "post-commit", NULL); if (amend && !no_post_rewrite) { struct notes_rewrite_cfg *cfg; cfg = init_copy_notes_for_rewrite("amend"); diff --git a/t/t7503-pre-commit-hook.sh b/t/t7503-pre-commit-hook.sh index fc6de5b..9301a0c 100755 --- a/t/t7503-pre-commit-hook.sh +++ b/t/t7503-pre-commit-hook.sh @@ -118,7 +118,7 @@ test_expect_success 'with failing hook requiring GIT_PREFIX' ' git checkout -- file ' -test_expect_failure 'check the author in hook' ' +test_expect_success 'check the author in hook' ' cat >"$HOOK" <<-\EOF && test "$GIT_AUTHOR_NAME" = "New Author" && test "$GIT_AUTHOR_EMAIL" = "newauthor@example.com" -- 1.7.10.rc0.33.g8866af ^ permalink raw reply related [flat|nested] 13+ messages in thread
end of thread, other threads:[~2012-03-12 11:29 UTC | newest] Thread overview: 13+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2012-03-09 1:15 Stop a git commit by a specific author using pre-commit hook Adrian Cornish 2012-03-10 19:41 ` Neal Kreitzinger 2012-03-10 21:54 ` Junio C Hamano 2012-03-10 23:03 ` Neal Kreitzinger 2012-03-11 11:05 ` Junio C Hamano 2012-03-11 11:08 ` [PATCH 1/(3/4)] test: does pre-commit-hook learn authorship? Junio C Hamano 2012-03-11 20:30 ` Johannes Sixt 2012-03-11 21:09 ` Junio C Hamano 2012-03-11 11:09 ` [PATCH 2/(3/4)] ident.c: add split_ident_line() to parse formatted ident line Junio C Hamano 2012-03-11 11:11 ` [PATCH 3/3] commit: pass author/committer info to hooks Junio C Hamano 2012-03-12 11:29 ` Jeff King 2012-03-11 11:15 ` [PATCH 3/4] run_hook(): enhance the interface to pass arbitrary environment Junio C Hamano 2012-03-11 11:16 ` [PATCH 4/4] commit: pass author/committer info to hooks Junio C Hamano
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).