From 37d3e85912ea13cb1d59022e2bc2ea8cf8bf4876 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 7 Sep 2011 13:42:39 -0400 Subject: [PATCH 1/5] t7004: factor out gpg setup Other test scripts may want to look at or verify signed tags, and the setup is non-trivial. Let's factor this out into lib-gpg.sh for other tests to use. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- t/lib-gpg.sh | 26 ++++++++++++++++++++++++++ t/{t7004 => lib-gpg}/pubring.gpg | Bin t/{t7004 => lib-gpg}/random_seed | Bin t/{t7004 => lib-gpg}/secring.gpg | Bin t/{t7004 => lib-gpg}/trustdb.gpg | Bin t/t7004-tag.sh | 29 +---------------------------- 6 files changed, 27 insertions(+), 28 deletions(-) create mode 100755 t/lib-gpg.sh rename t/{t7004 => lib-gpg}/pubring.gpg (100%) rename t/{t7004 => lib-gpg}/random_seed (100%) rename t/{t7004 => lib-gpg}/secring.gpg (100%) rename t/{t7004 => lib-gpg}/trustdb.gpg (100%) diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh new file mode 100755 index 0000000000..28463fb6f4 --- /dev/null +++ b/t/lib-gpg.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +gpg_version=`gpg --version 2>&1` +if test $? = 127; then + say "You do not seem to have gpg installed" +else + # As said here: http://www.gnupg.org/documentation/faqs.html#q6.19 + # the gpg version 1.0.6 didn't parse trust packets correctly, so for + # that version, creation of signed tags using the generated key fails. + case "$gpg_version" in + 'gpg (GnuPG) 1.0.6'*) + say "Your version of gpg (1.0.6) is too buggy for testing" + ;; + *) + # key generation info: gpg --homedir t/lib-gpg --gen-key + # Type DSA and Elgamal, size 2048 bits, no expiration date. + # Name and email: C O Mitter + # No password given, to enable non-interactive operation. + cp -R "$TEST_DIRECTORY"/lib-gpg ./gpghome + chmod 0700 gpghome + GNUPGHOME="$(pwd)/gpghome" + export GNUPGHOME + test_set_prereq GPG + ;; + esac +fi diff --git a/t/t7004/pubring.gpg b/t/lib-gpg/pubring.gpg similarity index 100% rename from t/t7004/pubring.gpg rename to t/lib-gpg/pubring.gpg diff --git a/t/t7004/random_seed b/t/lib-gpg/random_seed similarity index 100% rename from t/t7004/random_seed rename to t/lib-gpg/random_seed diff --git a/t/t7004/secring.gpg b/t/lib-gpg/secring.gpg similarity index 100% rename from t/t7004/secring.gpg rename to t/lib-gpg/secring.gpg diff --git a/t/t7004/trustdb.gpg b/t/lib-gpg/trustdb.gpg similarity index 100% rename from t/t7004/trustdb.gpg rename to t/lib-gpg/trustdb.gpg diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 097ce2bc83..e93ac73829 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -8,6 +8,7 @@ test_description='git tag Tests for operations with tags.' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-gpg.sh # creating and listing lightweight tags: @@ -585,24 +586,6 @@ test_expect_success \ test_cmp expect actual ' -# subsequent tests require gpg; check if it is available -gpg --version >/dev/null 2>/dev/null -if [ $? -eq 127 ]; then - say "# gpg not found - skipping tag signing and verification tests" -else - # As said here: http://www.gnupg.org/documentation/faqs.html#q6.19 - # the gpg version 1.0.6 didn't parse trust packets correctly, so for - # that version, creation of signed tags using the generated key fails. - case "$(gpg --version)" in - 'gpg (GnuPG) 1.0.6'*) - say "Skipping signed tag tests, because a bug in 1.0.6 version" - ;; - *) - test_set_prereq GPG - ;; - esac -fi - # trying to verify annotated non-signed tags: test_expect_success GPG \ @@ -625,16 +608,6 @@ test_expect_success GPG \ # creating and verifying signed tags: -# key generation info: gpg --homedir t/t7004 --gen-key -# Type DSA and Elgamal, size 2048 bits, no expiration date. -# Name and email: C O Mitter -# No password given, to enable non-interactive operation. - -cp -R "$TEST_DIRECTORY"/t7004 ./gpghome -chmod 0700 gpghome -GNUPGHOME="$(pwd)/gpghome" -export GNUPGHOME - get_tag_header signed-tag $commit commit $time >expect echo 'A signed tag message' >>expect echo '-----BEGIN PGP SIGNATURE-----' >>expect From 7140c22c8e98042053d5ff63c8514f5cdf1cb6ed Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 7 Sep 2011 13:43:28 -0400 Subject: [PATCH 2/5] t6300: add more body-parsing tests The current tests don't actually check parsing commit and tag messages that have both a subject and a body (they just have single-line messages). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- t/t6300-for-each-ref.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 7dc8a510c7..6fa4d52740 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -37,6 +37,7 @@ test_atom() { case "$1" in head) ref=refs/heads/master ;; tag) ref=refs/tags/testtag ;; + *) ref=$1 ;; esac printf '%s\n' "$3" >expected test_expect_${4:-success} "basic atom: $1 $2" " @@ -359,4 +360,23 @@ test_expect_success 'an unusual tag with an incomplete line' ' ' +test_expect_success 'create tag with subject and body content' ' + cat >>msg <<-\EOF && + the subject line + + first body line + second body line + EOF + git tag -F msg subject-body +' +test_atom refs/tags/subject-body subject 'the subject line' +test_atom refs/tags/subject-body body 'first body line +second body line +' +test_atom refs/tags/subject-body contents 'the subject line + +first body line +second body line +' + test_done From 7ec0f31eec66b854a2ca856538723dea5f1c0ab7 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 7 Sep 2011 13:44:07 -0400 Subject: [PATCH 3/5] for-each-ref: refactor subject and body placeholder parsing The find_subpos function was a little hard to use, as well as to read. It would sometimes write into the subject and body pointers, and sometimes not. The body pointer sometimes could be compared to subject, and sometimes not. When actually duplicating the subject, the caller was forced to figure out again how long the subject is (which is not too big a deal when the subject is a single line, but hard to extend). The refactoring makes the function more straightforward, both to read and to use. We will always put something into the subject and body pointers, and we return explicit lengths for them, too. This lays the groundwork both for more complex subject parsing (e.g., multiline), as well as splitting the body into subparts (like the text versus the signature). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/for-each-ref.c | 54 +++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 89e75c6894..bcea0276f3 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -458,38 +458,42 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru } } -static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body) +static void find_subpos(const char *buf, unsigned long sz, + const char **sub, unsigned long *sublen, + const char **body, unsigned long *bodylen) { - while (*buf) { - const char *eol = strchr(buf, '\n'); - if (!eol) - return; - if (eol[1] == '\n') { - buf = eol + 1; - break; /* found end of header */ - } - buf = eol + 1; + const char *eol; + /* skip past header until we hit empty line */ + while (*buf && *buf != '\n') { + eol = strchrnul(buf, '\n'); + if (*eol) + eol++; + buf = eol; } + /* skip any empty lines */ while (*buf == '\n') buf++; - if (!*buf) - return; - *sub = buf; /* first non-empty line */ - buf = strchr(buf, '\n'); - if (!buf) { - *body = ""; - return; /* no body */ - } + + /* subject is first non-empty line */ + *sub = buf; + /* subject goes to end of line */ + eol = strchrnul(buf, '\n'); + *sublen = eol - buf; + buf = eol; + + /* skip any empty lines */ while (*buf == '\n') - buf++; /* skip blank between subject and body */ + buf++; *body = buf; + *bodylen = strlen(buf); } /* See grab_values */ static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) { int i; - const char *subpos = NULL, *bodypos = NULL; + const char *subpos = NULL, *bodypos; + unsigned long sublen, bodylen; for (i = 0; i < used_atom_cnt; i++) { const char *name = used_atom[i]; @@ -503,14 +507,14 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj strcmp(name, "contents")) continue; if (!subpos) - find_subpos(buf, sz, &subpos, &bodypos); - if (!subpos) - return; + find_subpos(buf, sz, + &subpos, &sublen, + &bodypos, &bodylen); if (!strcmp(name, "subject")) - v->s = copy_line(subpos); + v->s = xmemdupz(subpos, sublen); else if (!strcmp(name, "body")) - v->s = xstrdup(bodypos); + v->s = xmemdupz(bodypos, bodylen); else if (!strcmp(name, "contents")) v->s = xstrdup(subpos); } From 7f6e275bc003e04fca63ae1058bb665078f72d7e Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 7 Sep 2011 13:44:56 -0400 Subject: [PATCH 4/5] for-each-ref: handle multiline subjects like --pretty Generally the format of a git tag or commit message is: subject body body body body body body However, we occasionally see multiline subjects like: subject with multiple lines body body body body body body The rest of git treats these multiline subjects as something to be concatenated and shown as a single line (e.g., "git log --pretty=format:%s" will do so since f53bd74). For consistency, for-each-ref should do the same with its "%(subject)". Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/for-each-ref.c | 29 ++++++++++++++++++++++++----- t/t6300-for-each-ref.sh | 21 +++++++++++++++++++++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index bcea0276f3..ea2112b388 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -361,6 +361,18 @@ static const char *copy_email(const char *buf) return xmemdupz(email, eoemail + 1 - email); } +static char *copy_subject(const char *buf, unsigned long len) +{ + char *r = xmemdupz(buf, len); + int i; + + for (i = 0; i < len; i++) + if (r[i] == '\n') + r[i] = ' '; + + return r; +} + static void grab_date(const char *buf, struct atom_value *v, const char *atomname) { const char *eoemail = strstr(buf, "> "); @@ -476,10 +488,17 @@ static void find_subpos(const char *buf, unsigned long sz, /* subject is first non-empty line */ *sub = buf; - /* subject goes to end of line */ - eol = strchrnul(buf, '\n'); - *sublen = eol - buf; - buf = eol; + /* subject goes to first empty line */ + while (*buf && *buf != '\n') { + eol = strchrnul(buf, '\n'); + if (*eol) + eol++; + buf = eol; + } + *sublen = buf - *sub; + /* drop trailing newline, if present */ + if (*sublen && (*sub)[*sublen - 1] == '\n') + *sublen -= 1; /* skip any empty lines */ while (*buf == '\n') @@ -512,7 +531,7 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj &bodypos, &bodylen); if (!strcmp(name, "subject")) - v->s = xmemdupz(subpos, sublen); + v->s = copy_subject(subpos, sublen); else if (!strcmp(name, "body")) v->s = xmemdupz(bodypos, bodylen); else if (!strcmp(name, "contents")) diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 6fa4d52740..0c9ff96dbe 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -379,4 +379,25 @@ first body line second body line ' +test_expect_success 'create tag with multiline subject' ' + cat >msg <<-\EOF && + first subject line + second subject line + + first body line + second body line + EOF + git tag -F msg multiline +' +test_atom refs/tags/multiline subject 'first subject line second subject line' +test_atom refs/tags/multiline body 'first body line +second body line +' +test_atom refs/tags/multiline contents 'first subject line +second subject line + +first body line +second body line +' + test_done From e2b239722a5806521016835f86e6c29c630a0e60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Wed, 7 Sep 2011 13:46:08 -0400 Subject: [PATCH 5/5] for-each-ref: add split message parts to %(contents:*). The %(body) placeholder returns the whole body of a tag or commit, including the signature. However, callers may want to get just the body without signature, or just the signature. Rather than change the meaning of %(body), which might break some scripts, this patch introduces a new set of placeholders which break down the %(contents) placeholder into its constituent parts. [jk: initial patch by mg, rebased on top of my refactoring and with tests by me] Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/git-for-each-ref.txt | 7 ++-- builtin/for-each-ref.c | 32 +++++++++++++--- t/lib-gpg.sh | 8 ++++ t/t6300-for-each-ref.sh | 60 +++++++++++++++++++++++++++++- 4 files changed, 96 insertions(+), 11 deletions(-) diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 152e695c81..c872b883ba 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -101,9 +101,10 @@ Fields that have name-email-date tuple as its value (`author`, `committer`, and `tagger`) can be suffixed with `name`, `email`, and `date` to extract the named component. -The first line of the message in a commit and tag object is -`subject`, the remaining lines are `body`. The whole message -is `contents`. +The complete message in a commit and tag object is `contents`. +Its first line is `contents:subject`, the remaining lines +are `contents:body` and the optional GPG signature +is `contents:signature`. For sorting purposes, fields with numeric values sort in numeric order (`objectsize`, `authordate`, `committerdate`, `taggerdate`). diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index ea2112b388..d90e5d2b29 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -69,6 +69,9 @@ static struct { { "subject" }, { "body" }, { "contents" }, + { "contents:subject" }, + { "contents:body" }, + { "contents:signature" }, { "upstream" }, { "symref" }, { "flag" }, @@ -472,7 +475,9 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru static void find_subpos(const char *buf, unsigned long sz, const char **sub, unsigned long *sublen, - const char **body, unsigned long *bodylen) + const char **body, unsigned long *bodylen, + unsigned long *nonsiglen, + const char **sig, unsigned long *siglen) { const char *eol; /* skip past header until we hit empty line */ @@ -486,10 +491,14 @@ static void find_subpos(const char *buf, unsigned long sz, while (*buf == '\n') buf++; + /* parse signature first; we might not even have a subject line */ + *sig = buf + parse_signature(buf, strlen(buf)); + *siglen = strlen(*sig); + /* subject is first non-empty line */ *sub = buf; /* subject goes to first empty line */ - while (*buf && *buf != '\n') { + while (buf < *sig && *buf && *buf != '\n') { eol = strchrnul(buf, '\n'); if (*eol) eol++; @@ -505,14 +514,15 @@ static void find_subpos(const char *buf, unsigned long sz, buf++; *body = buf; *bodylen = strlen(buf); + *nonsiglen = *sig - buf; } /* See grab_values */ static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) { int i; - const char *subpos = NULL, *bodypos; - unsigned long sublen, bodylen; + const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL; + unsigned long sublen = 0, bodylen = 0, nonsiglen = 0, siglen = 0; for (i = 0; i < used_atom_cnt; i++) { const char *name = used_atom[i]; @@ -523,17 +533,27 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj name++; if (strcmp(name, "subject") && strcmp(name, "body") && - strcmp(name, "contents")) + strcmp(name, "contents") && + strcmp(name, "contents:subject") && + strcmp(name, "contents:body") && + strcmp(name, "contents:signature")) continue; if (!subpos) find_subpos(buf, sz, &subpos, &sublen, - &bodypos, &bodylen); + &bodypos, &bodylen, &nonsiglen, + &sigpos, &siglen); if (!strcmp(name, "subject")) v->s = copy_subject(subpos, sublen); + else if (!strcmp(name, "contents:subject")) + v->s = copy_subject(subpos, sublen); else if (!strcmp(name, "body")) v->s = xmemdupz(bodypos, bodylen); + else if (!strcmp(name, "contents:body")) + v->s = xmemdupz(bodypos, nonsiglen); + else if (!strcmp(name, "contents:signature")) + v->s = xmemdupz(sigpos, siglen); else if (!strcmp(name, "contents")) v->s = xstrdup(subpos); } diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh index 28463fb6f4..05824fa8e4 100755 --- a/t/lib-gpg.sh +++ b/t/lib-gpg.sh @@ -24,3 +24,11 @@ else ;; esac fi + +sanitize_pgp() { + perl -ne ' + /^-----END PGP/ and $in_pgp = 0; + print unless $in_pgp; + /^-----BEGIN PGP/ and $in_pgp = 1; + ' +} diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 0c9ff96dbe..172178490a 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -6,6 +6,7 @@ test_description='for-each-ref test' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-gpg.sh # Mon Jul 3 15:18:43 2006 +0000 datestamp=1151939923 @@ -40,9 +41,10 @@ test_atom() { *) ref=$1 ;; esac printf '%s\n' "$3" >expected - test_expect_${4:-success} "basic atom: $1 $2" " + test_expect_${4:-success} $PREREQ "basic atom: $1 $2" " git for-each-ref --format='%($2)' $ref >actual && - test_cmp expected actual + sanitize_pgp actual.clean && + test_cmp expected actual.clean " } @@ -72,7 +74,10 @@ test_atom head taggerdate '' test_atom head creator 'C O Mitter 1151939923 +0200' test_atom head creatordate 'Mon Jul 3 17:18:43 2006 +0200' test_atom head subject 'Initial' +test_atom head contents:subject 'Initial' test_atom head body '' +test_atom head contents:body '' +test_atom head contents:signature '' test_atom head contents 'Initial ' @@ -102,7 +107,10 @@ test_atom tag taggerdate 'Mon Jul 3 17:18:45 2006 +0200' test_atom tag creator 'C O Mitter 1151939925 +0200' test_atom tag creatordate 'Mon Jul 3 17:18:45 2006 +0200' test_atom tag subject 'Tagging at 1151939927' +test_atom tag contents:subject 'Tagging at 1151939927' test_atom tag body '' +test_atom tag contents:body '' +test_atom tag contents:signature '' test_atom tag contents 'Tagging at 1151939927 ' @@ -390,9 +398,14 @@ test_expect_success 'create tag with multiline subject' ' git tag -F msg multiline ' test_atom refs/tags/multiline subject 'first subject line second subject line' +test_atom refs/tags/multiline contents:subject 'first subject line second subject line' test_atom refs/tags/multiline body 'first body line second body line ' +test_atom refs/tags/multiline contents:body 'first body line +second body line +' +test_atom refs/tags/multiline contents:signature '' test_atom refs/tags/multiline contents 'first subject line second subject line @@ -400,4 +413,47 @@ first body line second body line ' +test_expect_success GPG 'create signed tags' ' + git tag -s -m "" signed-empty && + git tag -s -m "subject line" signed-short && + cat >msg <<-\EOF && + subject line + + body contents + EOF + git tag -s -F msg signed-long +' + +sig='-----BEGIN PGP SIGNATURE----- +-----END PGP SIGNATURE----- +' + +PREREQ=GPG +test_atom refs/tags/signed-empty subject '' +test_atom refs/tags/signed-empty contents:subject '' +test_atom refs/tags/signed-empty body "$sig" +test_atom refs/tags/signed-empty contents:body '' +test_atom refs/tags/signed-empty contents:signature "$sig" +test_atom refs/tags/signed-empty contents "$sig" + +test_atom refs/tags/signed-short subject 'subject line' +test_atom refs/tags/signed-short contents:subject 'subject line' +test_atom refs/tags/signed-short body "$sig" +test_atom refs/tags/signed-short contents:body '' +test_atom refs/tags/signed-short contents:signature "$sig" +test_atom refs/tags/signed-short contents "subject line +$sig" + +test_atom refs/tags/signed-long subject 'subject line' +test_atom refs/tags/signed-long contents:subject 'subject line' +test_atom refs/tags/signed-long body "body contents +$sig" +test_atom refs/tags/signed-long contents:body 'body contents +' +test_atom refs/tags/signed-long contents:signature "$sig" +test_atom refs/tags/signed-long contents "subject line + +body contents +$sig" + test_done