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`
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

View 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); */

View File

@ -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