ref-filter: add new "signature" atom

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>
This commit is contained in:
Kousik Sanagavarapu 2023-06-04 23:52:47 +05:30 committed by Junio C Hamano
parent 2f36339fa8
commit 26c9c03f0a
3 changed files with 342 additions and 2 deletions

View File

@ -221,6 +221,33 @@ symref::
`:lstrip` and `:rstrip` options in the same way as `refname` `:lstrip` and `:rstrip` options in the same way as `refname`
above. 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:: worktreepath::
The absolute path to the worktree in which the ref is checked The absolute path to the worktree in which the ref is checked
out, if it is checked out in any linked worktree. Empty string out, if it is checked out in any linked worktree. Empty string

View File

@ -150,6 +150,7 @@ enum atom_type {
ATOM_BODY, ATOM_BODY,
ATOM_TRAILERS, ATOM_TRAILERS,
ATOM_CONTENTS, ATOM_CONTENTS,
ATOM_SIGNATURE,
ATOM_RAW, ATOM_RAW,
ATOM_UPSTREAM, ATOM_UPSTREAM,
ATOM_PUSH, ATOM_PUSH,
@ -215,6 +216,10 @@ static struct used_atom {
struct email_option { struct email_option {
enum { EO_RAW, EO_TRIM, EO_LOCALPART } option; enum { EO_RAW, EO_TRIM, EO_LOCALPART } option;
} email_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; struct refname_atom refname;
char *head; char *head;
} u; } u;
@ -407,9 +412,38 @@ static int subject_atom_parser(struct ref_format *format UNUSED,
return 0; return 0;
} }
static int trailers_atom_parser(struct ref_format *format UNUSED, 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, struct used_atom *atom,
const char *arg, struct strbuf *err) 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; 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_BODY] = { "body", SOURCE_OBJ, FIELD_STR, body_atom_parser },
[ATOM_TRAILERS] = { "trailers", SOURCE_OBJ, FIELD_STR, trailers_atom_parser }, [ATOM_TRAILERS] = { "trailers", SOURCE_OBJ, FIELD_STR, trailers_atom_parser },
[ATOM_CONTENTS] = { "contents", SOURCE_OBJ, FIELD_STR, contents_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_RAW] = { "raw", SOURCE_OBJ, FIELD_STR, raw_atom_parser },
[ATOM_UPSTREAM] = { "upstream", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser }, [ATOM_UPSTREAM] = { "upstream", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },
[ATOM_PUSH] = { "push", 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, static void find_subpos(const char *buf,
const char **sub, size_t *sublen, const char **sub, size_t *sublen,
const char **body, size_t *bodylen, 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_sub_body_contents(val, deref, data);
grab_person("author", val, deref, buf); grab_person("author", val, deref, buf);
grab_person("committer", val, deref, buf); grab_person("committer", val, deref, buf);
grab_signature(val, deref, obj);
break; break;
case OBJ_TREE: case OBJ_TREE:
/* grab_tree_values(val, deref, obj, buf, sz); */ /* grab_tree_values(val, deref, obj, buf, sz); */

View File

@ -6,6 +6,7 @@
test_description='for-each-ref test' test_description='for-each-ref test'
. ./test-lib.sh . ./test-lib.sh
GNUPGHOME_NOT_USED=$GNUPGHOME
. "$TEST_DIRECTORY"/lib-gpg.sh . "$TEST_DIRECTORY"/lib-gpg.sh
. "$TEST_DIRECTORY"/lib-terminal.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 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 test_done