Skip to content

Commit

Permalink
Merge branch 'ks/ref-filter-signature' into jch
Browse files Browse the repository at this point in the history
The "git for-each-ref" family of commands learned placeholders
related to GPG signature verification.

* ks/ref-filter-signature:
  ref-filter: add new "signature" atom
  t/lib-gpg: introduce new prereq GPG2
  • Loading branch information
gitster committed Jul 8, 2023
2 parents aa9166b + 26c9c03 commit 060045d
Show file tree
Hide file tree
Showing 5 changed files with 370 additions and 2 deletions.
27 changes: 27 additions & 0 deletions Documentation/git-for-each-ref.txt
Expand Up @@ -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
Expand Down
126 changes: 124 additions & 2 deletions ref-filter.c
Expand Up @@ -150,6 +150,7 @@ enum atom_type {
ATOM_BODY,
ATOM_TRAILERS,
ATOM_CONTENTS,
ATOM_SIGNATURE,
ATOM_RAW,
ATOM_UPSTREAM,
ATOM_PUSH,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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); */
Expand Down
21 changes: 21 additions & 0 deletions t/lib-gpg.sh
Expand Up @@ -51,6 +51,27 @@ test_lazy_prereq GPG '
esac
'

test_lazy_prereq GPG2 '
gpg_version=$(gpg --version 2>&1)
test $? != 127 || exit 1
case "$gpg_version" in
"gpg (GnuPG) "[01].*)
say "This test requires a GPG version >= v2.0.0"
exit 1
;;
*)
(gpgconf --kill all || : ) &&
gpg --homedir "${GNUPGHOME}" --import \
"$TEST_DIRECTORY"/lib-gpg/keyring.gpg &&
gpg --homedir "${GNUPGHOME}" --import-ownertrust \
"$TEST_DIRECTORY"/lib-gpg/ownertrust &&
gpg --homedir "${GNUPGHOME}" </dev/null >/dev/null \
--sign -u committer@example.com
;;
esac
'

test_lazy_prereq GPGSM '
test_have_prereq GPG &&
# Available key info:
Expand Down

0 comments on commit 060045d

Please sign in to comment.