Skip to content

Commit 7d24cdf

Browse files
committed
Merge branch 'nw/for-each-ref-signature' into seen
* nw/for-each-ref-signature: ref-filter: add new "signature" atom
2 parents b9995e8 + a148e4e commit 7d24cdf

File tree

3 files changed

+261
-0
lines changed

3 files changed

+261
-0
lines changed

Documentation/git-for-each-ref.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,33 @@ symref::
217217
`:lstrip` and `:rstrip` options in the same way as `refname`
218218
above.
219219

220+
signature::
221+
The GPG signature of a commit.
222+
223+
signature:grade::
224+
Show "G" for a good (valid) signature, "B" for a bad
225+
signature, "U" for a good signature with unknown validity, "X"
226+
for a good signature that has expired, "Y" for a good
227+
signature made by an expired key, "R" for a good signature
228+
made by a revoked key, "E" if the signature cannot be
229+
checked (e.g. missing key) and "N" for no signature.
230+
231+
signature:signer::
232+
The signer of the GPG signature of a commit.
233+
234+
signature:key::
235+
The key of the GPG signature of a commit.
236+
237+
signature:fingerprint::
238+
The fingerprint of the GPG signature of a commit.
239+
240+
signature:primarykeyfingerprint::
241+
The Primary Key fingerprint of the GPG signature of a commit.
242+
243+
signature:trustlevel::
244+
The Trust level of the GPG signature of a commit. Possible
245+
outputs are `ultimate`, `fully`, `marginal`, `never` and `undefined`.
246+
220247
worktreepath::
221248
The absolute path to the worktree in which the ref is checked
222249
out, if it is checked out in any linked worktree. Empty string

ref-filter.c

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ enum atom_type {
144144
ATOM_BODY,
145145
ATOM_TRAILERS,
146146
ATOM_CONTENTS,
147+
ATOM_SIGNATURE,
147148
ATOM_RAW,
148149
ATOM_UPSTREAM,
149150
ATOM_PUSH,
@@ -209,6 +210,10 @@ static struct used_atom {
209210
struct email_option {
210211
enum { EO_RAW, EO_TRIM, EO_LOCALPART } option;
211212
} email_option;
213+
struct {
214+
enum { S_BARE, S_GRADE, S_SIGNER, S_KEY,
215+
S_FINGERPRINT, S_PRI_KEY_FP, S_TRUST_LEVEL} option;
216+
} signature;
212217
struct refname_atom refname;
213218
char *head;
214219
} u;
@@ -401,6 +406,34 @@ static int subject_atom_parser(struct ref_format *format UNUSED,
401406
return 0;
402407
}
403408

409+
static int parse_signature_option(const char *arg)
410+
{
411+
if (!arg)
412+
return S_BARE;
413+
else if (!strcmp(arg, "signer"))
414+
return S_SIGNER;
415+
else if (!strcmp(arg, "grade"))
416+
return S_GRADE;
417+
else if (!strcmp(arg, "key"))
418+
return S_KEY;
419+
else if (!strcmp(arg, "fingerprint"))
420+
return S_FINGERPRINT;
421+
else if (!strcmp(arg, "primarykeyfingerprint"))
422+
return S_PRI_KEY_FP;
423+
else if (!strcmp(arg, "trustlevel"))
424+
return S_TRUST_LEVEL;
425+
return -1;
426+
}
427+
428+
static int signature_atom_parser(struct ref_format *format UNUSED, struct used_atom *atom,
429+
const char *arg, struct strbuf *err){
430+
int opt = parse_signature_option(arg);
431+
if (opt < 0)
432+
return err_bad_arg(err, "signature", arg);
433+
atom->u.signature.option = opt;
434+
return 0;
435+
}
436+
404437
static int trailers_atom_parser(struct ref_format *format UNUSED,
405438
struct used_atom *atom,
406439
const char *arg, struct strbuf *err)
@@ -657,6 +690,7 @@ static struct {
657690
[ATOM_BODY] = { "body", SOURCE_OBJ, FIELD_STR, body_atom_parser },
658691
[ATOM_TRAILERS] = { "trailers", SOURCE_OBJ, FIELD_STR, trailers_atom_parser },
659692
[ATOM_CONTENTS] = { "contents", SOURCE_OBJ, FIELD_STR, contents_atom_parser },
693+
[ATOM_SIGNATURE] = { "signature", SOURCE_OBJ, FIELD_STR, signature_atom_parser },
660694
[ATOM_RAW] = { "raw", SOURCE_OBJ, FIELD_STR, raw_atom_parser },
661695
[ATOM_UPSTREAM] = { "upstream", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },
662696
[ATOM_PUSH] = { "push", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },
@@ -1392,6 +1426,77 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void
13921426
}
13931427
}
13941428

1429+
static void grab_signature(struct atom_value *val, int deref, struct object *obj)
1430+
{
1431+
int i;
1432+
struct commit *commit = (struct commit *) obj;
1433+
struct signature_check sigc = { 0 };
1434+
int signature_checked = 0;
1435+
1436+
for (i = 0; i < used_atom_cnt; i++) {
1437+
struct used_atom *atom = &used_atom[i];
1438+
const char *name = atom->name;
1439+
struct atom_value *v = &val[i];
1440+
1441+
if (!!deref != (*name == '*'))
1442+
continue;
1443+
if (deref)
1444+
name++;
1445+
1446+
if (!skip_prefix(name, "signature", &name) || (*name &&
1447+
*name != ':'))
1448+
continue;
1449+
if (!*name)
1450+
name = NULL;
1451+
else
1452+
name++;
1453+
if (parse_signature_option(name) < 0)
1454+
continue;
1455+
1456+
if (!signature_checked) {
1457+
check_commit_signature(commit, &sigc);
1458+
signature_checked = 1;
1459+
}
1460+
1461+
if (atom->u.signature.option == S_BARE)
1462+
v->s = xstrdup(sigc.output ? sigc.output: "");
1463+
else if (atom->u.signature.option == S_SIGNER)
1464+
v->s = xstrdup(sigc.signer ? sigc.signer : "");
1465+
else if (atom->u.signature.option == S_GRADE) {
1466+
switch (sigc.result) {
1467+
case 'G':
1468+
switch (sigc.trust_level) {
1469+
case TRUST_UNDEFINED:
1470+
case TRUST_NEVER:
1471+
v->s = xstrfmt("%c", (char)'U');
1472+
break;
1473+
default:
1474+
v->s = xstrfmt("%c", (char)'G');
1475+
break;
1476+
}
1477+
break;
1478+
case 'B':
1479+
case 'E':
1480+
case 'N':
1481+
case 'X':
1482+
case 'Y':
1483+
case 'R':
1484+
v->s = xstrfmt("%c", (char)sigc.result);
1485+
}
1486+
}
1487+
else if (atom->u.signature.option == S_KEY)
1488+
v->s = xstrdup(sigc.key ? sigc.key : "");
1489+
else if (atom->u.signature.option == S_FINGERPRINT)
1490+
v->s = xstrdup(sigc.fingerprint ? sigc.fingerprint : "");
1491+
else if (atom->u.signature.option == S_PRI_KEY_FP)
1492+
v->s = xstrdup(sigc.primary_key_fingerprint ? sigc.primary_key_fingerprint : "");
1493+
else if (atom->u.signature.option == S_TRUST_LEVEL)
1494+
v->s = xstrdup(gpg_trust_level_to_str(sigc.trust_level));
1495+
}
1496+
if (signature_checked)
1497+
signature_check_clear(&sigc);
1498+
}
1499+
13951500
static void find_subpos(const char *buf,
13961501
const char **sub, size_t *sublen,
13971502
const char **body, size_t *bodylen,
@@ -1585,6 +1690,7 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, s
15851690
grab_sub_body_contents(val, deref, data);
15861691
grab_person("author", val, deref, buf);
15871692
grab_person("committer", val, deref, buf);
1693+
grab_signature(val, deref, obj);
15881694
break;
15891695
case OBJ_TREE:
15901696
/* grab_tree_values(val, deref, obj, buf, sz); */

t/t6300-for-each-ref.sh

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
test_description='for-each-ref test'
77

88
. ./test-lib.sh
9+
GNUPGHOME_NOT_USED=$GNUPGHOME
910
. "$TEST_DIRECTORY"/lib-gpg.sh
1011
. "$TEST_DIRECTORY"/lib-terminal.sh
1112

@@ -1514,4 +1515,131 @@ test_expect_success 'git for-each-ref with non-existing refs' '
15141515
test_must_be_empty actual
15151516
'
15161517

1518+
GRADE_FORMAT="%(signature:grade)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
1519+
TRUSTLEVEL_FORMAT="%(signature:trustlevel)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
1520+
1521+
test_expect_success GPG 'test bare signature atom' '
1522+
git checkout -b signed &&
1523+
echo 1 >file && git add file &&
1524+
test_tick && git commit -S -m initial &&
1525+
git verify-commit signed 2>out_orig &&
1526+
grep -v "checking the trustdb" out_orig >out &&
1527+
head -3 out >expected &&
1528+
tail -1 out >>expected &&
1529+
echo >>expected &&
1530+
git for-each-ref refs/heads/signed --format="%(signature)" >actual &&
1531+
test_cmp expected actual
1532+
'
1533+
1534+
test_expect_success GPG 'show good signature with custom format' '
1535+
echo 2 >file && git add file &&
1536+
test_tick && git commit -S -m initial &&
1537+
git verify-commit signed 2>out &&
1538+
cat >expect <<-\EOF &&
1539+
G
1540+
13B6F51ECDDE430D
1541+
C O Mitter <[email protected]>
1542+
73D758744BE721698EC54E8713B6F51ECDDE430D
1543+
73D758744BE721698EC54E8713B6F51ECDDE430D
1544+
EOF
1545+
git for-each-ref refs/heads/signed --format="$GRADE_FORMAT" >actual &&
1546+
test_cmp expect actual
1547+
'
1548+
1549+
test_expect_success GPG 'test signature atom with grade option and bad signature' '
1550+
git config commit.gpgsign true &&
1551+
echo 3 >file && test_tick && git commit -a -m "third" --no-gpg-sign &&
1552+
git tag third-unsigned &&
1553+
1554+
test_tick && git rebase -f HEAD^^ && git tag second-signed HEAD^ &&
1555+
git tag third-signed &&
1556+
1557+
git cat-file commit third-signed >raw &&
1558+
sed -e "s/^third/3rd forged/" raw >forged1 &&
1559+
FORGED1=$(git hash-object -w -t commit forged1) &&
1560+
git update-ref refs/tags/third-signed "$FORGED1" &&
1561+
test_must_fail git verify-commit "$FORGED1" &&
1562+
1563+
cat >expect <<-\EOF &&
1564+
B
1565+
13B6F51ECDDE430D
1566+
C O Mitter <[email protected]>
1567+
1568+
1569+
EOF
1570+
git for-each-ref refs/tags/third-signed --format="$GRADE_FORMAT" >actual &&
1571+
test_cmp expect actual
1572+
'
1573+
1574+
test_expect_success GPG 'show untrusted signature with custom format' '
1575+
echo 4 >file && test_tick && git commit -a -m fourth -SB7227189 &&
1576+
git tag signed-fourth &&
1577+
cat >expect <<-\EOF &&
1578+
U
1579+
65A0EEA02E30CAD7
1580+
Eris Discordia <[email protected]>
1581+
F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
1582+
D4BE22311AD3131E5EDA29A461092E85B7227189
1583+
EOF
1584+
git for-each-ref refs/tags/signed-fourth --format="$GRADE_FORMAT" >actual &&
1585+
test_cmp expect actual
1586+
'
1587+
1588+
test_expect_success GPG 'show untrusted signature with undefined trust level' '
1589+
echo 5 >file && test_tick && git commit -a -m fifth -SB7227189 &&
1590+
git tag fifth-signed &&
1591+
cat >expect <<-\EOF &&
1592+
undefined
1593+
65A0EEA02E30CAD7
1594+
Eris Discordia <[email protected]>
1595+
F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
1596+
D4BE22311AD3131E5EDA29A461092E85B7227189
1597+
EOF
1598+
git for-each-ref refs/tags/fifth-signed --format="$TRUSTLEVEL_FORMAT" >actual &&
1599+
test_cmp expect actual
1600+
'
1601+
1602+
test_expect_success GPG 'show untrusted signature with ultimate trust level' '
1603+
echo 7 >file && test_tick && git commit -a -m "seventh" --no-gpg-sign &&
1604+
git tag seventh-unsigned &&
1605+
1606+
test_tick && git rebase -f HEAD^^ && git tag sixth-signed HEAD^ &&
1607+
git tag seventh-signed &&
1608+
cat >expect <<-\EOF &&
1609+
ultimate
1610+
13B6F51ECDDE430D
1611+
C O Mitter <[email protected]>
1612+
73D758744BE721698EC54E8713B6F51ECDDE430D
1613+
73D758744BE721698EC54E8713B6F51ECDDE430D
1614+
EOF
1615+
git for-each-ref refs/tags/seventh-signed --format="$TRUSTLEVEL_FORMAT" >actual &&
1616+
test_cmp expect actual
1617+
'
1618+
1619+
test_expect_success GPG 'show unknown signature with custom format' '
1620+
cat >expect <<-\EOF &&
1621+
E
1622+
65A0EEA02E30CAD7
1623+
1624+
1625+
1626+
EOF
1627+
GNUPGHOME="$GNUPGHOME_NOT_USED" git for-each-ref refs/tags/fifth-signed --format="$GRADE_FORMAT" >actual &&
1628+
test_cmp expect actual
1629+
'
1630+
1631+
test_expect_success GPG 'show lack of signature with custom format' '
1632+
echo 8 >file && test_tick && git commit -a -m "eigth unsigned" --no-gpg-sign &&
1633+
git tag eigth-unsigned &&
1634+
cat >expect <<-\EOF &&
1635+
N
1636+
1637+
1638+
1639+
1640+
EOF
1641+
git for-each-ref refs/tags/eigth-unsigned --format="$GRADE_FORMAT" >actual &&
1642+
test_cmp expect actual
1643+
'
1644+
15171645
test_done

0 commit comments

Comments
 (0)