]> git.ipfire.org Git - thirdparty/git.git/commitdiff
ref-filter: add new "signature" atom
authorKousik Sanagavarapu <five231003@gmail.com>
Sun, 4 Jun 2023 18:22:47 +0000 (23:52 +0530)
committerJunio C Hamano <gitster@pobox.com>
Tue, 6 Jun 2023 00:32:15 +0000 (09:32 +0900)
Duplicate the code for outputting the signature and its other
parameters for commits and tags in ref-filter from pretty. In the
future, this will help in getting rid of the current duplicate
implementations of such logic everywhere, when ref-filter can do
everything that pretty is doing.

The new atom "signature" and its friends are equivalent to the existing
pretty formats as follows:

%(signature) = %GG
%(signature:grade) = %G?
%(siganture:signer) = %GS
%(signature:key) = %GK
%(signature:fingerprint) = %GF
%(signature:primarykeyfingerprint) = %GP
%(signature:trustlevel) = %GT

Co-authored-by: Hariom Verma <hariom18599@gmail.com>
Co-authored-by: Jaydeep Das <jaydeepjd.8914@gmail.com>
Co-authored-by: Nsengiyumva Wilberforce <nsengiyumvawilberforce@gmail.com>
Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Hariom Verma <hariom18599@gmail.com>
Signed-off-by: Kousik Sanagavarapu <five231003@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/git-for-each-ref.txt
ref-filter.c
t/t6300-for-each-ref.sh

index 1e215d4e73451f2d8dc1e70d42d53475952910dd..2e0318770b7761e9de5009879bc29a727f393808 100644 (file)
@@ -221,6 +221,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
index 4991cd4f7a860be1cf508e88668d1fd8af2ff9ae..5c6924b367fa5e6fc38844901b07a9a2199c3725 100644 (file)
@@ -150,6 +150,7 @@ enum atom_type {
        ATOM_BODY,
        ATOM_TRAILERS,
        ATOM_CONTENTS,
+       ATOM_SIGNATURE,
        ATOM_RAW,
        ATOM_UPSTREAM,
        ATOM_PUSH,
@@ -215,6 +216,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;
@@ -407,8 +412,37 @@ static int subject_atom_parser(struct ref_format *format UNUSED,
        return 0;
 }
 
-static int trailers_atom_parser(struct ref_format *format UNUSED,
-                               struct used_atom *atom,
+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)
 {
        atom->u.contents.trailer_opts.no_divider = 1;
@@ -668,6 +702,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 },
@@ -1405,6 +1440,92 @@ 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 };
+       int signature_checked = 0;
+
+       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];
+               int opt;
+
+               if (!!deref != (*name == '*'))
+                       continue;
+               if (deref)
+                       name++;
+
+               if (!skip_prefix(name, "signature", &name) ||
+                   (*name && *name != ':'))
+                       continue;
+               if (!*name)
+                       name = NULL;
+               else
+                       name++;
+
+               opt = parse_signature_option(name);
+               if (opt < 0)
+                       continue;
+
+               if (!signature_checked) {
+                       check_commit_signature(commit, &sigc);
+                       signature_checked = 1;
+               }
+
+               switch (opt) {
+               case S_BARE:
+                       v->s = xstrdup(sigc.output ? sigc.output: "");
+                       break;
+               case S_SIGNER:
+                       v->s = xstrdup(sigc.signer ? sigc.signer : "");
+                       break;
+               case 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);
+                               break;
+                       }
+                       break;
+               case S_KEY:
+                       v->s = xstrdup(sigc.key ? sigc.key : "");
+                       break;
+               case S_FINGERPRINT:
+                       v->s = xstrdup(sigc.fingerprint ?
+                                      sigc.fingerprint : "");
+                       break;
+               case S_PRI_KEY_FP:
+                       v->s = xstrdup(sigc.primary_key_fingerprint ?
+                                      sigc.primary_key_fingerprint : "");
+                       break;
+               case S_TRUST_LEVEL:
+                       v->s = xstrdup(gpg_trust_level_to_str(sigc.trust_level));
+                       break;
+               }
+       }
+
+       if (signature_checked)
+               signature_check_clear(&sigc);
+}
+
 static void find_subpos(const char *buf,
                        const char **sub, size_t *sublen,
                        const char **body, size_t *bodylen,
@@ -1598,6 +1719,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); */
index 5c00607608a847525650db93807d72bb235abd5f..6e6ec852b5d0e86a71a4fa1b4680661988bb8b3e 100755 (executable)
@@ -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
 
@@ -1522,4 +1523,194 @@ test_expect_success 'git for-each-ref with non-existing refs' '
        test_must_be_empty actual
 '
 
+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 'setup for signature atom using gpg' '
+       git checkout -b signed &&
+
+       test_when_finished "test_unconfig commit.gpgSign" &&
+
+       echo "1" >file &&
+       git add file &&
+       test_tick &&
+       git commit -S -m "file: 1" &&
+       git tag first-signed &&
+
+       echo "2" >file &&
+       test_tick &&
+       git commit -a -m "file: 2" &&
+       git tag second-unsigned &&
+
+       git config commit.gpgSign 1 &&
+       echo "3" >file &&
+       test_tick &&
+       git commit -a --no-gpg-sign -m "file: 3" &&
+       git tag third-unsigned &&
+
+       test_tick &&
+       git rebase -f HEAD^^ && git tag second-signed HEAD^ &&
+       git tag third-signed &&
+
+       echo "4" >file &&
+       test_tick &&
+       git commit -a -SB7227189 -m "file: 4" &&
+       git tag fourth-signed &&
+
+       echo "5" >file &&
+       test_tick &&
+       git commit -a --no-gpg-sign -m "file: 5" &&
+       git tag fifth-unsigned &&
+
+       echo "6" >file &&
+       test_tick &&
+       git commit -a --no-gpg-sign -m "file: 6" &&
+
+       test_tick &&
+       git rebase -f HEAD^^ &&
+       git tag fifth-signed HEAD^ &&
+       git tag sixth-signed &&
+
+       echo "7" >file &&
+       test_tick &&
+       git commit -a --no-gpg-sign -m "file: 7" &&
+       git tag seventh-unsigned
+'
+
+test_expect_success GPGSSH 'setup for signature atom using ssh' '
+       test_when_finished "test_unconfig gpg.format user.signingkey" &&
+
+       test_config gpg.format ssh &&
+       test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
+       echo "8" >file &&
+       test_tick &&
+       git commit -a -S -m "file: 8" &&
+       git tag eighth-signed-ssh
+'
+
+test_expect_success GPG2 'bare signature atom' '
+       git verify-commit first-signed 2>out.raw &&
+       grep -Ev "checking the trustdb|PGP trust model" out.raw >out &&
+       head -3 out >expect &&
+       tail -1 out >>expect &&
+       echo  >>expect &&
+       git for-each-ref refs/tags/first-signed \
+               --format="%(signature)" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success GPG 'show good signature with custom format' '
+       git verify-commit first-signed &&
+       cat >expect <<-\EOF &&
+       G
+       13B6F51ECDDE430D
+       C O Mitter <committer@example.com>
+       73D758744BE721698EC54E8713B6F51ECDDE430D
+       73D758744BE721698EC54E8713B6F51ECDDE430D
+       EOF
+       git for-each-ref refs/tags/first-signed \
+               --format="$GRADE_FORMAT" >actual &&
+       test_cmp expect actual
+'
+test_expect_success GPGSSH 'show good signature with custom format
+                           with ssh' '
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
+       cat >expect.tmpl <<-\EOF &&
+       G
+       FINGERPRINT
+       principal with number 1
+       FINGERPRINT
+
+       EOF
+       sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect &&
+       git for-each-ref refs/tags/eighth-signed-ssh \
+               --format="$GRADE_FORMAT" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success GPG 'signature atom with grade option and bad signature' '
+       git cat-file commit third-signed >raw &&
+       sed -e "s/^file: 3/file: 3 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' '
+       cat >expect <<-\EOF &&
+       U
+       65A0EEA02E30CAD7
+       Eris Discordia <discord@example.net>
+       F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
+       D4BE22311AD3131E5EDA29A461092E85B7227189
+       EOF
+       git for-each-ref refs/tags/fourth-signed \
+               --format="$GRADE_FORMAT" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success GPG 'show untrusted signature with undefined trust level' '
+       cat >expect <<-\EOF &&
+       undefined
+       65A0EEA02E30CAD7
+       Eris Discordia <discord@example.net>
+       F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
+       D4BE22311AD3131E5EDA29A461092E85B7227189
+       EOF
+       git for-each-ref refs/tags/fourth-signed \
+               --format="$TRUSTLEVEL_FORMAT" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success GPG 'show untrusted signature with ultimate trust level' '
+       cat >expect <<-\EOF &&
+       ultimate
+       13B6F51ECDDE430D
+       C O Mitter <committer@example.com>
+       73D758744BE721698EC54E8713B6F51ECDDE430D
+       73D758744BE721698EC54E8713B6F51ECDDE430D
+       EOF
+       git for-each-ref refs/tags/sixth-signed \
+               --format="$TRUSTLEVEL_FORMAT" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success GPG 'show unknown signature with custom format' '
+       cat >expect <<-\EOF &&
+       E
+       13B6F51ECDDE430D
+
+
+
+       EOF
+       GNUPGHOME="$GNUPGHOME_NOT_USED" git for-each-ref \
+               refs/tags/sixth-signed --format="$GRADE_FORMAT" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success GPG 'show lack of signature with custom format' '
+       cat >expect <<-\EOF &&
+       N
+
+
+
+
+       EOF
+       git for-each-ref refs/tags/seventh-unsigned \
+               --format="$GRADE_FORMAT" >actual &&
+       test_cmp expect actual
+'
+
 test_done