Message ID | pull.1428.git.git.1673254961028.gitgitgadget@gmail.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | ref-filter: add new atom "signature" atom | expand |
On Mon, Jan 9, 2023 at 10:15 AM nsengaw4c via GitGitGadget <gitgitgadget@gmail.com> wrote: > > From: Nsengiyumva Wilberforce <nsengiyumvawilberforce@gmail.com> This patch should be marked as a V2 in its subject: [PATCH v2] ref-filter: add new atom "signature" atom not sure how to do that using GitGitGadget though. Also "atom" appears twice in the subject, so the following would be even better: [PATCH v2] ref-filter: add new "signature" atom I am not sure how, but GitGitGadget should allow to set the "In-Reply-To:" to the message ID of your previous version of this patch, so that your email with the version 2 of the patch would appear in the same email thread as the email of your previous version of this patch on lore.kernel.org/git: https://lore.kernel.org/git/pull.1452.git.1672102523902.gitgitgadget@gmail.com/ (Please don't resend this patch as a v2, but as a v3, if you make any change.) > This commit duplicates the code for `signature` atom from pretty.c > to ref-filter.c. This feature will help to get rid of current duplicate > implementation of `signature` atom when unifying implemenations by s/implemenations/implementations/ > using ref-filter logic everywhere when ref-filter can do everything > pretty is doing. > > Add "signature" atom with `grade`, `signer`, `key`, > `fingerprint`, `primarykeyfingerprint`, `trustlevel` as arguments. > This code and its documentation are inspired by how the %GG, %G?, > %GS, %GK, %GF, %GP, and %GT pretty formats were implemented. > > Co-authored-by: Hariom Verma <hariom18599@gmail.com> > Co-authored-by: Jaydeep Das <jaydeepjd.8914@gmail.com> > Mentored-by: Christian Couder <chriscool@tuxfamily.org> > Mentored-by: Hariom Verma <hariom18599@gmail.com> > Signed-off-by: Nsengiyumva Wilberforce <nsengiyumvawilberforce@gmail.com> > --- > ref-filter: add new atom "signature" atom > > This commit duplicates the code for signature atom from pretty.c to > ref-filter.c. This feature will help to get rid of current duplicate > implementation of signature atom when unifying implemenations by using s/implemenations/implementations/ > ref-filter logic everywhere when ref-filter can do everything pretty is > doing. > > Add "signature" atom with grade, signer, key, fingerprint, > primarykeyfingerprint, trustlevel as arguments. This code and its > documentation are inspired by how the %GG, %G?, %GS, %GK, %GF, %GP, and > %GT pretty formats were implemented. > > Co-authored-by: Hariom Verma hariom18599@gmail.com Co-authored-by: > Jaydeep Das jaydeepjd.8914@gmail.com Mentored-by: Christian Couder > chriscool@tuxfamily.org Mentored-by: Hariom Verma hariom18599@gmail.com > Signed-off-by: Nsengiyumva Wilberforce nsengiyumvawilberforce@gmail.com Not sure you can do something about it, but the above lines aren't properly wrapped. > Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1428%2Fnsengiyumva-wilberforce%2Fsignature10-v1 > Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1428/nsengiyumva-wilberforce/signature10-v1 > Pull-Request: https://github.com/git/git/pull/1428 > > Documentation/git-for-each-ref.txt | 27 ++++++ > ref-filter.c | 101 +++++++++++++++++++++++ > t/t6300-for-each-ref.sh | 127 +++++++++++++++++++++++++++++ > 3 files changed, 255 insertions(+) > > diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt > index 6da899c6296..9a0be85368b 100644 > --- a/Documentation/git-for-each-ref.txt > +++ b/Documentation/git-for-each-ref.txt > @@ -212,6 +212,33 @@ symref:: > `:lstrip` and `:rstrip` options in the same way as `refname` > above. > > +signature:: > + The GPG signature of a commit. > + > +signature:grade:: > + Show "G" for a good (valid) signature, "B" for a bad > + signature, "U" for a good signature with unknown validity, "X" > + for a good signature that has expired, "Y" for a good > + signature made by an expired key, "R" for a good signature > + made by a revoked key, "E" if the signature cannot be > + checked (e.g. missing key) and "N" for no signature. > + > +signature:signer:: > + The signer of the GPG signature of a commit. > + > +signature:key:: > + The key of the GPG signature of a commit. > + > +signature:fingerprint:: > + The fingerprint of the GPG signature of a commit. > + > +signature:primarykeyfingerprint:: > + The Primary Key fingerprint of the GPG signature of a commit. > + > +signature:trustlevel:: > + The Trust level of the GPG signature of a commit. Possible > + outputs are `ultimate`, `fully`, `marginal`, `never` and `undefined`. > + > worktreepath:: > The absolute path to the worktree in which the ref is checked > out, if it is checked out in any linked worktree. Empty string > diff --git a/ref-filter.c b/ref-filter.c > index a24324123e7..0cba756b186 100644 > --- a/ref-filter.c > +++ b/ref-filter.c > @@ -144,6 +144,7 @@ enum atom_type { > ATOM_BODY, > ATOM_TRAILERS, > ATOM_CONTENTS, > + ATOM_SIGNATURE, > ATOM_RAW, > ATOM_UPSTREAM, > ATOM_PUSH, > @@ -208,6 +209,10 @@ static struct used_atom { > struct email_option { > enum { EO_RAW, EO_TRIM, EO_LOCALPART } option; > } email_option; > + struct { > + enum { S_BARE, S_GRADE, S_SIGNER, S_KEY, > + S_FINGERPRINT, S_PRI_KEY_FP, S_TRUST_LEVEL} option; > + } signature; > struct refname_atom refname; > char *head; > } u; > @@ -394,6 +399,34 @@ static int subject_atom_parser(struct ref_format *format, struct used_atom *atom > return 0; > } > > +static int parse_signature_option(const char *arg) > +{ > + if (!arg) > + return S_BARE; > + else if (!strcmp(arg, "signer")) > + return S_SIGNER; > + else if (!strcmp(arg, "grade")) > + return S_GRADE; > + else if (!strcmp(arg, "key")) > + return S_KEY; > + else if (!strcmp(arg, "fingerprint")) > + return S_FINGERPRINT; > + else if (!strcmp(arg, "primarykeyfingerprint")) > + return S_PRI_KEY_FP; > + else if (!strcmp(arg, "trustlevel")) > + return S_TRUST_LEVEL; > + return -1; > +} > + > +static int signature_atom_parser(struct ref_format *format UNUSED, struct used_atom *atom, > + const char *arg, struct strbuf *err){ > + int opt = parse_signature_option(arg); > + if (opt < 0) > + return err_bad_arg(err, "signature", arg); > + atom->u.signature.option = opt; > + return 0; > +} > + > static int trailers_atom_parser(struct ref_format *format, struct used_atom *atom, > const char *arg, struct strbuf *err) > { > @@ -631,6 +664,7 @@ static struct { > [ATOM_BODY] = { "body", SOURCE_OBJ, FIELD_STR, body_atom_parser }, > [ATOM_TRAILERS] = { "trailers", SOURCE_OBJ, FIELD_STR, trailers_atom_parser }, > [ATOM_CONTENTS] = { "contents", SOURCE_OBJ, FIELD_STR, contents_atom_parser }, > + [ATOM_SIGNATURE] = { "signature", SOURCE_OBJ, FIELD_STR, signature_atom_parser }, > [ATOM_RAW] = { "raw", SOURCE_OBJ, FIELD_STR, raw_atom_parser }, > [ATOM_UPSTREAM] = { "upstream", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser }, > [ATOM_PUSH] = { "push", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser }, > @@ -1362,6 +1396,72 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void > } > } > > +static void grab_signature(struct atom_value *val, int deref, struct object *obj) > +{ > + int i; > + struct commit *commit = (struct commit *) obj; > + struct signature_check sigc = { 0 }; > + > + check_commit_signature(commit, &sigc); > + > + for (i = 0; i < used_atom_cnt; i++) { > + struct used_atom *atom = &used_atom[i]; > + const char *name = atom->name; > + struct atom_value *v = &val[i]; > + > + if (!!deref != (*name == '*')) > + continue; > + if (deref) > + name++; > + > + if (!skip_prefix(name, "signature", &name) || (*name && > + *name != ':')) > + continue; > + if (!*name) > + name = NULL; > + else > + name++; > + if (parse_signature_option(name) < 0) > + continue; > + > + if (atom->u.signature.option == S_BARE) > + v->s = xstrdup(sigc.output ? sigc.output: ""); > + else if (atom->u.signature.option == S_SIGNER) > + v->s = xstrdup(sigc.signer ? sigc.signer : ""); > + else if (atom->u.signature.option == S_GRADE) { > + switch (sigc.result) { > + case 'G': > + switch (sigc.trust_level) { > + case TRUST_UNDEFINED: > + case TRUST_NEVER: > + v->s = xstrfmt("%c", (char)'U'); > + break; > + default: > + v->s = xstrfmt("%c", (char)'G'); > + break; > + } > + break; > + case 'B': > + case 'E': > + case 'N': > + case 'X': > + case 'Y': > + case 'R': > + v->s = xstrfmt("%c", (char)sigc.result); > + } > + } > + else if (atom->u.signature.option == S_KEY) > + v->s = xstrdup(sigc.key ? sigc.key : ""); > + else if (atom->u.signature.option == S_FINGERPRINT) > + v->s = xstrdup(sigc.fingerprint ? sigc.fingerprint : ""); > + else if (atom->u.signature.option == S_PRI_KEY_FP) > + v->s = xstrdup(sigc.primary_key_fingerprint ? sigc.primary_key_fingerprint : ""); > + else if (atom->u.signature.option == S_TRUST_LEVEL) > + v->s = xstrdup(gpg_trust_level_to_str(sigc.trust_level)); > + } > + signature_check_clear(&sigc); > +} > + > static void find_subpos(const char *buf, > const char **sub, size_t *sublen, > const char **body, size_t *bodylen, > @@ -1555,6 +1655,7 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, s > grab_sub_body_contents(val, deref, data); > grab_person("author", val, deref, buf); > grab_person("committer", val, deref, buf); > + grab_signature(val, deref, obj); > break; > case OBJ_TREE: > /* grab_tree_values(val, deref, obj, buf, sz); */ > diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh > index 2ae1fc721b1..a8efe6f58ec 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 > +GNUPGHOME_NOT_USED=$GNUPGHOME > . "$TEST_DIRECTORY"/lib-gpg.sh > . "$TEST_DIRECTORY"/lib-terminal.sh > > @@ -1464,4 +1465,130 @@ sig_crlf="$(printf "%s" "$sig" | append_cr; echo dummy)" > sig_crlf=${sig_crlf%dummy} > test_atom refs/tags/fake-sig-crlf contents:signature "$sig_crlf" > > +GRADE_FORMAT="%(signature:grade)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)" > +TRUSTLEVEL_FORMAT="%(signature:trustlevel)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)" > + > +test_expect_success GPG 'test bare signature atom' ' > + git checkout -b signed && > + echo 1 >file && git add file && > + test_tick && git commit -S -m initial && > + git verify-commit signed 2>out && > + head -3 out >expected && > + tail -1 out >>expected && > + echo >>expected && > + git for-each-ref refs/heads/signed --format="%(signature)" >actual && > + test_cmp actual expected > +' (I already commented about this test in a previous email related to how it fails on GitHub CI.)
> <gitgitgadget@gmail.com> wrote: > > > > From: Nsengiyumva Wilberforce <nsengiyumvawilberforce@gmail.com> > > This patch should be marked as a V2 in its subject: > > [PATCH v2] ref-filter: add new atom "signature" atom > > not sure how to do that using GitGitGadget though. > > Also "atom" appears twice in the subject, so the following would be even better: > > [PATCH v2] ref-filter: add new "signature" atom > > I am not sure how, but GitGitGadget should allow to set the > "In-Reply-To:" to the message ID of your previous version of this > patch, so that your email with the version 2 of the patch would appear > in the same email thread as the email of your previous version of this > patch on lore.kernel.org/git: > > https://lore.kernel.org/git/pull.1452.git.1672102523902.gitgitgadget@gmail.com/ > > (Please don't resend this patch as a v2, but as a v3, if you make any change.) I do not see any option for changing the ID, I think I need some directions on how to change the patch version On Mon, Jan 9, 2023 at 4:45 AM Christian Couder <christian.couder@gmail.com> wrote: > > On Mon, Jan 9, 2023 at 10:15 AM nsengaw4c via GitGitGadget > <gitgitgadget@gmail.com> wrote: > > > > From: Nsengiyumva Wilberforce <nsengiyumvawilberforce@gmail.com> > > This patch should be marked as a V2 in its subject: > > [PATCH v2] ref-filter: add new atom "signature" atom > > not sure how to do that using GitGitGadget though. > > Also "atom" appears twice in the subject, so the following would be even better: > > [PATCH v2] ref-filter: add new "signature" atom > > I am not sure how, but GitGitGadget should allow to set the > "In-Reply-To:" to the message ID of your previous version of this > patch, so that your email with the version 2 of the patch would appear > in the same email thread as the email of your previous version of this > patch on lore.kernel.org/git: > > https://lore.kernel.org/git/pull.1452.git.1672102523902.gitgitgadget@gmail.com/ > > (Please don't resend this patch as a v2, but as a v3, if you make any change.) > > > This commit duplicates the code for `signature` atom from pretty.c > > to ref-filter.c. This feature will help to get rid of current duplicate > > implementation of `signature` atom when unifying implemenations by > > s/implemenations/implementations/ > > > using ref-filter logic everywhere when ref-filter can do everything > > pretty is doing. > > > > Add "signature" atom with `grade`, `signer`, `key`, > > `fingerprint`, `primarykeyfingerprint`, `trustlevel` as arguments. > > This code and its documentation are inspired by how the %GG, %G?, > > %GS, %GK, %GF, %GP, and %GT pretty formats were implemented. > > > > Co-authored-by: Hariom Verma <hariom18599@gmail.com> > > Co-authored-by: Jaydeep Das <jaydeepjd.8914@gmail.com> > > Mentored-by: Christian Couder <chriscool@tuxfamily.org> > > Mentored-by: Hariom Verma <hariom18599@gmail.com> > > Signed-off-by: Nsengiyumva Wilberforce <nsengiyumvawilberforce@gmail.com> > > --- > > ref-filter: add new atom "signature" atom > > > > This commit duplicates the code for signature atom from pretty.c to > > ref-filter.c. This feature will help to get rid of current duplicate > > implementation of signature atom when unifying implemenations by using > > s/implemenations/implementations/ > > > ref-filter logic everywhere when ref-filter can do everything pretty is > > doing. > > > > Add "signature" atom with grade, signer, key, fingerprint, > > primarykeyfingerprint, trustlevel as arguments. This code and its > > documentation are inspired by how the %GG, %G?, %GS, %GK, %GF, %GP, and > > %GT pretty formats were implemented. > > > > Co-authored-by: Hariom Verma hariom18599@gmail.com Co-authored-by: > > Jaydeep Das jaydeepjd.8914@gmail.com Mentored-by: Christian Couder > > chriscool@tuxfamily.org Mentored-by: Hariom Verma hariom18599@gmail.com > > Signed-off-by: Nsengiyumva Wilberforce nsengiyumvawilberforce@gmail.com > > Not sure you can do something about it, but the above lines aren't > properly wrapped. > > > Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1428%2Fnsengiyumva-wilberforce%2Fsignature10-v1 > > Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1428/nsengiyumva-wilberforce/signature10-v1 > > Pull-Request: https://github.com/git/git/pull/1428 > > > > Documentation/git-for-each-ref.txt | 27 ++++++ > > ref-filter.c | 101 +++++++++++++++++++++++ > > t/t6300-for-each-ref.sh | 127 +++++++++++++++++++++++++++++ > > 3 files changed, 255 insertions(+) > > > > diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt > > index 6da899c6296..9a0be85368b 100644 > > --- a/Documentation/git-for-each-ref.txt > > +++ b/Documentation/git-for-each-ref.txt > > @@ -212,6 +212,33 @@ symref:: > > `:lstrip` and `:rstrip` options in the same way as `refname` > > above. > > > > +signature:: > > + The GPG signature of a commit. > > + > > +signature:grade:: > > + Show "G" for a good (valid) signature, "B" for a bad > > + signature, "U" for a good signature with unknown validity, "X" > > + for a good signature that has expired, "Y" for a good > > + signature made by an expired key, "R" for a good signature > > + made by a revoked key, "E" if the signature cannot be > > + checked (e.g. missing key) and "N" for no signature. > > + > > +signature:signer:: > > + The signer of the GPG signature of a commit. > > + > > +signature:key:: > > + The key of the GPG signature of a commit. > > + > > +signature:fingerprint:: > > + The fingerprint of the GPG signature of a commit. > > + > > +signature:primarykeyfingerprint:: > > + The Primary Key fingerprint of the GPG signature of a commit. > > + > > +signature:trustlevel:: > > + The Trust level of the GPG signature of a commit. Possible > > + outputs are `ultimate`, `fully`, `marginal`, `never` and `undefined`. > > + > > worktreepath:: > > The absolute path to the worktree in which the ref is checked > > out, if it is checked out in any linked worktree. Empty string > > diff --git a/ref-filter.c b/ref-filter.c > > index a24324123e7..0cba756b186 100644 > > --- a/ref-filter.c > > +++ b/ref-filter.c > > @@ -144,6 +144,7 @@ enum atom_type { > > ATOM_BODY, > > ATOM_TRAILERS, > > ATOM_CONTENTS, > > + ATOM_SIGNATURE, > > ATOM_RAW, > > ATOM_UPSTREAM, > > ATOM_PUSH, > > @@ -208,6 +209,10 @@ static struct used_atom { > > struct email_option { > > enum { EO_RAW, EO_TRIM, EO_LOCALPART } option; > > } email_option; > > + struct { > > + enum { S_BARE, S_GRADE, S_SIGNER, S_KEY, > > + S_FINGERPRINT, S_PRI_KEY_FP, S_TRUST_LEVEL} option; > > + } signature; > > struct refname_atom refname; > > char *head; > > } u; > > @@ -394,6 +399,34 @@ static int subject_atom_parser(struct ref_format *format, struct used_atom *atom > > return 0; > > } > > > > +static int parse_signature_option(const char *arg) > > +{ > > + if (!arg) > > + return S_BARE; > > + else if (!strcmp(arg, "signer")) > > + return S_SIGNER; > > + else if (!strcmp(arg, "grade")) > > + return S_GRADE; > > + else if (!strcmp(arg, "key")) > > + return S_KEY; > > + else if (!strcmp(arg, "fingerprint")) > > + return S_FINGERPRINT; > > + else if (!strcmp(arg, "primarykeyfingerprint")) > > + return S_PRI_KEY_FP; > > + else if (!strcmp(arg, "trustlevel")) > > + return S_TRUST_LEVEL; > > + return -1; > > +} > > + > > +static int signature_atom_parser(struct ref_format *format UNUSED, struct used_atom *atom, > > + const char *arg, struct strbuf *err){ > > + int opt = parse_signature_option(arg); > > + if (opt < 0) > > + return err_bad_arg(err, "signature", arg); > > + atom->u.signature.option = opt; > > + return 0; > > +} > > + > > static int trailers_atom_parser(struct ref_format *format, struct used_atom *atom, > > const char *arg, struct strbuf *err) > > { > > @@ -631,6 +664,7 @@ static struct { > > [ATOM_BODY] = { "body", SOURCE_OBJ, FIELD_STR, body_atom_parser }, > > [ATOM_TRAILERS] = { "trailers", SOURCE_OBJ, FIELD_STR, trailers_atom_parser }, > > [ATOM_CONTENTS] = { "contents", SOURCE_OBJ, FIELD_STR, contents_atom_parser }, > > + [ATOM_SIGNATURE] = { "signature", SOURCE_OBJ, FIELD_STR, signature_atom_parser }, > > [ATOM_RAW] = { "raw", SOURCE_OBJ, FIELD_STR, raw_atom_parser }, > > [ATOM_UPSTREAM] = { "upstream", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser }, > > [ATOM_PUSH] = { "push", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser }, > > @@ -1362,6 +1396,72 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void > > } > > } > > > > +static void grab_signature(struct atom_value *val, int deref, struct object *obj) > > +{ > > + int i; > > + struct commit *commit = (struct commit *) obj; > > + struct signature_check sigc = { 0 }; > > + > > + check_commit_signature(commit, &sigc); > > + > > + for (i = 0; i < used_atom_cnt; i++) { > > + struct used_atom *atom = &used_atom[i]; > > + const char *name = atom->name; > > + struct atom_value *v = &val[i]; > > + > > + if (!!deref != (*name == '*')) > > + continue; > > + if (deref) > > + name++; > > + > > + if (!skip_prefix(name, "signature", &name) || (*name && > > + *name != ':')) > > + continue; > > + if (!*name) > > + name = NULL; > > + else > > + name++; > > + if (parse_signature_option(name) < 0) > > + continue; > > + > > + if (atom->u.signature.option == S_BARE) > > + v->s = xstrdup(sigc.output ? sigc.output: ""); > > + else if (atom->u.signature.option == S_SIGNER) > > + v->s = xstrdup(sigc.signer ? sigc.signer : ""); > > + else if (atom->u.signature.option == S_GRADE) { > > + switch (sigc.result) { > > + case 'G': > > + switch (sigc.trust_level) { > > + case TRUST_UNDEFINED: > > + case TRUST_NEVER: > > + v->s = xstrfmt("%c", (char)'U'); > > + break; > > + default: > > + v->s = xstrfmt("%c", (char)'G'); > > + break; > > + } > > + break; > > + case 'B': > > + case 'E': > > + case 'N': > > + case 'X': > > + case 'Y': > > + case 'R': > > + v->s = xstrfmt("%c", (char)sigc.result); > > + } > > + } > > + else if (atom->u.signature.option == S_KEY) > > + v->s = xstrdup(sigc.key ? sigc.key : ""); > > + else if (atom->u.signature.option == S_FINGERPRINT) > > + v->s = xstrdup(sigc.fingerprint ? sigc.fingerprint : ""); > > + else if (atom->u.signature.option == S_PRI_KEY_FP) > > + v->s = xstrdup(sigc.primary_key_fingerprint ? sigc.primary_key_fingerprint : ""); > > + else if (atom->u.signature.option == S_TRUST_LEVEL) > > + v->s = xstrdup(gpg_trust_level_to_str(sigc.trust_level)); > > + } > > + signature_check_clear(&sigc); > > +} > > + > > static void find_subpos(const char *buf, > > const char **sub, size_t *sublen, > > const char **body, size_t *bodylen, > > @@ -1555,6 +1655,7 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, s > > grab_sub_body_contents(val, deref, data); > > grab_person("author", val, deref, buf); > > grab_person("committer", val, deref, buf); > > + grab_signature(val, deref, obj); > > break; > > case OBJ_TREE: > > /* grab_tree_values(val, deref, obj, buf, sz); */ > > diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh > > index 2ae1fc721b1..a8efe6f58ec 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 > > +GNUPGHOME_NOT_USED=$GNUPGHOME > > . "$TEST_DIRECTORY"/lib-gpg.sh > > . "$TEST_DIRECTORY"/lib-terminal.sh > > > > @@ -1464,4 +1465,130 @@ sig_crlf="$(printf "%s" "$sig" | append_cr; echo dummy)" > > sig_crlf=${sig_crlf%dummy} > > test_atom refs/tags/fake-sig-crlf contents:signature "$sig_crlf" > > > > +GRADE_FORMAT="%(signature:grade)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)" > > +TRUSTLEVEL_FORMAT="%(signature:trustlevel)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)" > > + > > +test_expect_success GPG 'test bare signature atom' ' > > + git checkout -b signed && > > + echo 1 >file && git add file && > > + test_tick && git commit -S -m initial && > > + git verify-commit signed 2>out && > > + head -3 out >expected && > > + tail -1 out >>expected && > > + echo >>expected && > > + git for-each-ref refs/heads/signed --format="%(signature)" >actual && > > + test_cmp actual expected > > +' > > (I already commented about this test in a previous email related to > how it fails on GitHub CI.)
diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 6da899c6296..9a0be85368b 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -212,6 +212,33 @@ symref:: `:lstrip` and `:rstrip` options in the same way as `refname` above. +signature:: + The GPG signature of a commit. + +signature:grade:: + Show "G" for a good (valid) signature, "B" for a bad + signature, "U" for a good signature with unknown validity, "X" + for a good signature that has expired, "Y" for a good + signature made by an expired key, "R" for a good signature + made by a revoked key, "E" if the signature cannot be + checked (e.g. missing key) and "N" for no signature. + +signature:signer:: + The signer of the GPG signature of a commit. + +signature:key:: + The key of the GPG signature of a commit. + +signature:fingerprint:: + The fingerprint of the GPG signature of a commit. + +signature:primarykeyfingerprint:: + The Primary Key fingerprint of the GPG signature of a commit. + +signature:trustlevel:: + The Trust level of the GPG signature of a commit. Possible + outputs are `ultimate`, `fully`, `marginal`, `never` and `undefined`. + worktreepath:: The absolute path to the worktree in which the ref is checked out, if it is checked out in any linked worktree. Empty string diff --git a/ref-filter.c b/ref-filter.c index a24324123e7..0cba756b186 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -144,6 +144,7 @@ enum atom_type { ATOM_BODY, ATOM_TRAILERS, ATOM_CONTENTS, + ATOM_SIGNATURE, ATOM_RAW, ATOM_UPSTREAM, ATOM_PUSH, @@ -208,6 +209,10 @@ static struct used_atom { struct email_option { enum { EO_RAW, EO_TRIM, EO_LOCALPART } option; } email_option; + struct { + enum { S_BARE, S_GRADE, S_SIGNER, S_KEY, + S_FINGERPRINT, S_PRI_KEY_FP, S_TRUST_LEVEL} option; + } signature; struct refname_atom refname; char *head; } u; @@ -394,6 +399,34 @@ static int subject_atom_parser(struct ref_format *format, struct used_atom *atom return 0; } +static int parse_signature_option(const char *arg) +{ + if (!arg) + return S_BARE; + else if (!strcmp(arg, "signer")) + return S_SIGNER; + else if (!strcmp(arg, "grade")) + return S_GRADE; + else if (!strcmp(arg, "key")) + return S_KEY; + else if (!strcmp(arg, "fingerprint")) + return S_FINGERPRINT; + else if (!strcmp(arg, "primarykeyfingerprint")) + return S_PRI_KEY_FP; + else if (!strcmp(arg, "trustlevel")) + return S_TRUST_LEVEL; + return -1; +} + +static int signature_atom_parser(struct ref_format *format UNUSED, struct used_atom *atom, + const char *arg, struct strbuf *err){ + int opt = parse_signature_option(arg); + if (opt < 0) + return err_bad_arg(err, "signature", arg); + atom->u.signature.option = opt; + return 0; +} + static int trailers_atom_parser(struct ref_format *format, struct used_atom *atom, const char *arg, struct strbuf *err) { @@ -631,6 +664,7 @@ static struct { [ATOM_BODY] = { "body", SOURCE_OBJ, FIELD_STR, body_atom_parser }, [ATOM_TRAILERS] = { "trailers", SOURCE_OBJ, FIELD_STR, trailers_atom_parser }, [ATOM_CONTENTS] = { "contents", SOURCE_OBJ, FIELD_STR, contents_atom_parser }, + [ATOM_SIGNATURE] = { "signature", SOURCE_OBJ, FIELD_STR, signature_atom_parser }, [ATOM_RAW] = { "raw", SOURCE_OBJ, FIELD_STR, raw_atom_parser }, [ATOM_UPSTREAM] = { "upstream", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser }, [ATOM_PUSH] = { "push", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser }, @@ -1362,6 +1396,72 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void } } +static void grab_signature(struct atom_value *val, int deref, struct object *obj) +{ + int i; + struct commit *commit = (struct commit *) obj; + struct signature_check sigc = { 0 }; + + check_commit_signature(commit, &sigc); + + for (i = 0; i < used_atom_cnt; i++) { + struct used_atom *atom = &used_atom[i]; + const char *name = atom->name; + struct atom_value *v = &val[i]; + + if (!!deref != (*name == '*')) + continue; + if (deref) + name++; + + if (!skip_prefix(name, "signature", &name) || (*name && + *name != ':')) + continue; + if (!*name) + name = NULL; + else + name++; + if (parse_signature_option(name) < 0) + continue; + + if (atom->u.signature.option == S_BARE) + v->s = xstrdup(sigc.output ? sigc.output: ""); + else if (atom->u.signature.option == S_SIGNER) + v->s = xstrdup(sigc.signer ? sigc.signer : ""); + else if (atom->u.signature.option == S_GRADE) { + switch (sigc.result) { + case 'G': + switch (sigc.trust_level) { + case TRUST_UNDEFINED: + case TRUST_NEVER: + v->s = xstrfmt("%c", (char)'U'); + break; + default: + v->s = xstrfmt("%c", (char)'G'); + break; + } + break; + case 'B': + case 'E': + case 'N': + case 'X': + case 'Y': + case 'R': + v->s = xstrfmt("%c", (char)sigc.result); + } + } + else if (atom->u.signature.option == S_KEY) + v->s = xstrdup(sigc.key ? sigc.key : ""); + else if (atom->u.signature.option == S_FINGERPRINT) + v->s = xstrdup(sigc.fingerprint ? sigc.fingerprint : ""); + else if (atom->u.signature.option == S_PRI_KEY_FP) + v->s = xstrdup(sigc.primary_key_fingerprint ? sigc.primary_key_fingerprint : ""); + else if (atom->u.signature.option == S_TRUST_LEVEL) + v->s = xstrdup(gpg_trust_level_to_str(sigc.trust_level)); + } + signature_check_clear(&sigc); +} + static void find_subpos(const char *buf, const char **sub, size_t *sublen, const char **body, size_t *bodylen, @@ -1555,6 +1655,7 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, s grab_sub_body_contents(val, deref, data); grab_person("author", val, deref, buf); grab_person("committer", val, deref, buf); + grab_signature(val, deref, obj); break; case OBJ_TREE: /* grab_tree_values(val, deref, obj, buf, sz); */ diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 2ae1fc721b1..a8efe6f58ec 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 +GNUPGHOME_NOT_USED=$GNUPGHOME . "$TEST_DIRECTORY"/lib-gpg.sh . "$TEST_DIRECTORY"/lib-terminal.sh @@ -1464,4 +1465,130 @@ sig_crlf="$(printf "%s" "$sig" | append_cr; echo dummy)" sig_crlf=${sig_crlf%dummy} test_atom refs/tags/fake-sig-crlf contents:signature "$sig_crlf" +GRADE_FORMAT="%(signature:grade)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)" +TRUSTLEVEL_FORMAT="%(signature:trustlevel)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)" + +test_expect_success GPG 'test bare signature atom' ' + git checkout -b signed && + echo 1 >file && git add file && + test_tick && git commit -S -m initial && + git verify-commit signed 2>out && + head -3 out >expected && + tail -1 out >>expected && + echo >>expected && + git for-each-ref refs/heads/signed --format="%(signature)" >actual && + test_cmp actual expected +' + +test_expect_success GPG 'show good signature with custom format' ' + echo 2 >file && git add file && + test_tick && git commit -S -m initial && + git verify-commit signed 2>out && + cat >expect <<-\EOF && + G + 13B6F51ECDDE430D + C O Mitter <committer@example.com> + 73D758744BE721698EC54E8713B6F51ECDDE430D + 73D758744BE721698EC54E8713B6F51ECDDE430D + EOF + git for-each-ref refs/heads/signed --format="$GRADE_FORMAT" >actual && + test_cmp expect actual +' + +test_expect_success GPG 'test signature atom with grade option and bad signature' ' + git config commit.gpgsign true && + echo 3 >file && test_tick && git commit -a -m "third" --no-gpg-sign && + git tag third-unsigned && + + test_tick && git rebase -f HEAD^^ && git tag second-signed HEAD^ && + git tag third-signed && + + git cat-file commit third-signed >raw && + sed -e "s/^third/3rd forged/" raw >forged1 && + FORGED1=$(git hash-object -w -t commit forged1) && + git update-ref refs/tags/third-signed "$FORGED1" && + test_must_fail git verify-commit "$FORGED1" && + + cat >expect <<-\EOF && + B + 13B6F51ECDDE430D + C O Mitter <committer@example.com> + + + EOF + git for-each-ref refs/tags/third-signed --format="$GRADE_FORMAT" >actual && + test_cmp expect actual +' + +test_expect_success GPG 'show untrusted signature with custom format' ' + echo 4 >file && test_tick && git commit -a -m fourth -SB7227189 && + git tag signed-fourth && + cat >expect <<-\EOF && + U + 65A0EEA02E30CAD7 + Eris Discordia <discord@example.net> + F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7 + D4BE22311AD3131E5EDA29A461092E85B7227189 + EOF + git for-each-ref refs/tags/signed-fourth --format="$GRADE_FORMAT" >actual && + test_cmp expect actual +' + +test_expect_success GPG 'show untrusted signature with undefined trust level' ' + echo 5 >file && test_tick && git commit -a -m fifth -SB7227189 && + git tag fifth-signed && + cat >expect <<-\EOF && + undefined + 65A0EEA02E30CAD7 + Eris Discordia <discord@example.net> + F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7 + D4BE22311AD3131E5EDA29A461092E85B7227189 + EOF + git for-each-ref refs/tags/fifth-signed --format="$TRUSTLEVEL_FORMAT" >actual && + test_cmp expect actual +' + +test_expect_success GPG 'show untrusted signature with ultimate trust level' ' + echo 7 >file && test_tick && git commit -a -m "seventh" --no-gpg-sign && + git tag seventh-unsigned && + + test_tick && git rebase -f HEAD^^ && git tag sixth-signed HEAD^ && + git tag seventh-signed && + cat >expect <<-\EOF && + ultimate + 13B6F51ECDDE430D + C O Mitter <committer@example.com> + 73D758744BE721698EC54E8713B6F51ECDDE430D + 73D758744BE721698EC54E8713B6F51ECDDE430D + EOF + git for-each-ref refs/tags/seventh-signed --format="$TRUSTLEVEL_FORMAT" >actual && + test_cmp expect actual +' + +test_expect_success GPG 'show unknown signature with custom format' ' + cat >expect <<-\EOF && + E + 65A0EEA02E30CAD7 + + + + EOF + GNUPGHOME="$GNUPGHOME_NOT_USED" git for-each-ref refs/tags/fifth-signed --format="$GRADE_FORMAT" >actual && + test_cmp expect actual +' + +test_expect_success GPG 'show lack of signature with custom format' ' + echo 8 >file && test_tick && git commit -a -m "eigth unsigned" --no-gpg-sign && + git tag eigth-unsigned && + cat >expect <<-\EOF && + N + + + + + EOF + git for-each-ref refs/tags/eigth-unsigned --format="$GRADE_FORMAT" >actual && + test_cmp expect actual +' + test_done