Skip to content

Commit 5749d4a

Browse files
committed
test: add commit signing
docs: add commit signing recipe test: cleanup unzipped cruft test: additional housekeeping test: additional housekeeping
1 parent 56b7a20 commit 5749d4a

File tree

4 files changed

+166
-33
lines changed

4 files changed

+166
-33
lines changed

docs/recipes/git-commit.rst

+55
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,61 @@ The rest is the same:
5252
>>> tree = index.write_tree()
5353
>>> repo.create_commit(ref, author, committer, message, tree, parents)
5454
55+
56+
----------------------------------------------------------------------
57+
Signing a commit
58+
----------------------------------------------------------------------
59+
60+
Add everything, and commit with a GPG signature:
61+
62+
.. code-block:: bash
63+
64+
$ git add .
65+
$ git commit -S -m "Signed commit"
66+
67+
.. code-block:: python
68+
69+
>>> index = repo.index
70+
>>> index.add_all()
71+
>>> index.write()
72+
>>> author = Signature('Alice Author', '[email protected]')
73+
>>> committer = Signature('Cecil Committer', '[email protected]')
74+
>>> message = "Signed commit"
75+
>>> tree = index.write_tree()
76+
>>> parents = []
77+
>>> commit_string = repo.create_commit_string(
78+
>>> author, committer, message, tree, parents
79+
>>> )
80+
81+
The ``commit_string`` can then be signed by a third party library:
82+
83+
.. code-block:: python
84+
>>> gpg = YourGPGToolHere()
85+
>>> signed_commit = gpg.sign(
86+
>>> commit_string,
87+
>>> passphrase='secret',
88+
>>> detach=True,
89+
>>> )
90+
91+
.. note::
92+
The commit signature should resemble:
93+
94+
.. code-block:: none
95+
>>> -----BEGIN PGP SIGNATURE-----
96+
>>>
97+
>>> < base64 encoded hash here >
98+
>>> -----END PGP SIGNATURE-----
99+
100+
The signed commit can then be added to the branch:
101+
102+
.. code-block:: python
103+
104+
>>> commit = repo.create_commit_with_signature(
105+
>>> commit_string, signed_commit.data.decode('utf-8')
106+
>>> )
107+
>>> repo.head.set_target(commit)
108+
109+
55110
----------------------------------------------------------------------
56111
References
57112
----------------------------------------------------------------------

test/conftest.py

+7
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def emptyrepo(tmp_path):
5353
with utils.TemporaryRepository('emptyrepo.zip', tmp_path) as path:
5454
yield pygit2.Repository(path)
5555

56+
5657
@pytest.fixture
5758
def encodingrepo(tmp_path):
5859
with utils.TemporaryRepository('encoding.zip', tmp_path) as path:
@@ -81,3 +82,9 @@ def testrepo_path(tmp_path):
8182
def testrepopacked(tmp_path):
8283
with utils.TemporaryRepository('testrepopacked.zip', tmp_path) as path:
8384
yield pygit2.Repository(path)
85+
86+
87+
@pytest.fixture
88+
def gpgsigned(tmp_path):
89+
with utils.TemporaryRepository('gpgsigned.zip', tmp_path) as path:
90+
yield pygit2.Repository(path)

test/data/gpgsigned.zip

442 KB
Binary file not shown.

test/test_commit_gpg.py

+104-33
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2010-2021 The pygit2 contributors
1+
# Copyright 2010-2022 The pygit2 contributors
22
#
33
# This file is free software; you can redistribute it and/or modify
44
# it under the terms of the GNU General Public License, version 2,
@@ -23,49 +23,120 @@
2323
# the Free Software Foundation, 51 Franklin Street, Fifth Floor,
2424
# Boston, MA 02110-1301, USA.
2525

26-
import pygit2
27-
import pytest
26+
from pygit2 import GIT_OBJ_COMMIT, Oid, Signature
2827

29-
from . import utils
28+
content = """\
29+
tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904
30+
parent 8496071c1b46c854b31185ea97743be6a8774479
31+
author Ben Burkert <[email protected]> 1358451456 -0800
32+
committer Ben Burkert <[email protected]> 1358451456 -0800
3033
34+
a simple commit which works\
35+
"""
3136

32-
@pytest.fixture
33-
def repo(tmp_path):
34-
with utils.TemporaryRepository('gpgsigned.zip', tmp_path) as path:
35-
yield pygit2.Repository(path)
37+
gpgsig = """\
38+
-----BEGIN PGP SIGNATURE-----
39+
Version: GnuPG v1.4.12 (Darwin)
3640
41+
iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al
42+
o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8
43+
JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq
44+
AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq
45+
SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW
46+
who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok
47+
6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG
48+
cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu
49+
c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9
50+
ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J
51+
7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc
52+
cpxtDQQMGYFpXK/71stq
53+
=ozeK
54+
-----END PGP SIGNATURE-----\
55+
"""
3756

38-
def test_get_gpg_signature_when_signed(repo):
39-
signed_hash = 'a00b212d5455ad8c4c1779f778c7d2a81bb5da23'
40-
expected_signature = (
41-
'-----BEGIN PGP SIGNATURE-----\n\n'
42-
'iQFGBAABCgAwFiEEQZu9JtePgJbDk7VC0+mlK74z13oFAlpzXykSHG1hcmtAbWFy\n'
43-
'a2FkYW1zLm1lAAoJENPppSu+M9d6FRoIAJXeQRRT1V47nnHITiel6426loYkeij7\n'
44-
'66doGNIyll95H92SwH4LAjPyEEByIG1VsA6NztzUoNgnEvAXI0iAz3LyI7N16M4b\n'
45-
'dPDkC72pp8tu280H5Qt5b2V5hmlKKSgtOS5iNhdU/FbWVS8MlHsqzQTZfoTdi6ch\n'
46-
'KWUsjzudVd3F/H/AU+1Jsxt8Iz/oK4T/puUQLnJZKjKlljGP994FA3JIpnZpZmbG\n'
47-
'FybYJEDXnng7uhx3Fz/Mo3KBJoQfAExTtaToY0n0hSjOe6GN9rEsRSMK3mWdysf2\n'
48-
'wOdtYMMcT16hG5tAwnD/myZ4rIIpyZJ/9mjymdUsj6UKf7D+vJuqfsI=\n=IyYy\n'
49-
'-----END PGP SIGNATURE-----'
50-
).encode('ascii')
57+
gpgsig_content = """\
58+
tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904
59+
parent 8496071c1b46c854b31185ea97743be6a8774479
60+
author Ben Burkert <[email protected]> 1358451456 -0800
61+
committer Ben Burkert <[email protected]> 1358451456 -0800
62+
gpgsig -----BEGIN PGP SIGNATURE-----
63+
Version: GnuPG v1.4.12 (Darwin)
64+
65+
iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al
66+
o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8
67+
JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq
68+
AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq
69+
SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW
70+
who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok
71+
6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG
72+
cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu
73+
c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9
74+
ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J
75+
7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc
76+
cpxtDQQMGYFpXK/71stq
77+
=ozeK
78+
-----END PGP SIGNATURE-----
5179
52-
expected_payload = (
53-
'tree c36c20831e43e5984c672a714661870b67ab1d95\nauthor Mark Adams '
54-
'<[email protected]> 1517510299 -0600\ncommitter Mark Adams <ma'
55-
'[email protected]> 1517510441 -0600\n\nMaking a GPG signed commi'
56-
't\n'
57-
).encode('ascii')
80+
a simple commit which works\
81+
"""
82+
# NOTE: ^^^ mind the gap (space must exist after GnuPG header) ^^^
83+
# XXX: seems macos wants the space while linux does not
5884

59-
commit = repo.get(signed_hash)
85+
86+
def test_commit_signing(gpgsigned):
87+
repo = gpgsigned
88+
message = "a simple commit which works"
89+
author = Signature(
90+
name="Ben Burkert",
91+
92+
time=1358451456,
93+
offset=-480,
94+
)
95+
committer = Signature(
96+
name="Ben Burkert",
97+
98+
time=1358451456,
99+
offset=-480,
100+
)
101+
tree = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
102+
parents = ["8496071c1b46c854b31185ea97743be6a8774479"]
103+
104+
# create commit string
105+
commit_string = repo.create_commit_string(
106+
author, committer, message, tree, parents
107+
)
108+
assert commit_string == content
109+
110+
# create/retrieve signed commit
111+
oid = repo.create_commit_with_signature(content, gpgsig)
112+
commit = repo.get(oid)
60113
signature, payload = commit.gpg_signature
61114

62-
assert signature == expected_signature
63-
assert payload == expected_payload
115+
# validate signed commit
116+
assert content == payload.decode("utf-8")
117+
assert gpgsig == signature.decode("utf-8")
118+
assert gpgsig_content == commit.read_raw().decode("utf-8")
119+
120+
# perform sanity checks
121+
assert GIT_OBJ_COMMIT == commit.type
122+
assert "6569fdf71dbd99081891154641869c537784a3ba" == commit.hex
123+
assert commit.message_encoding is None
124+
assert message == commit.message
125+
assert 1358451456 == commit.commit_time
126+
assert committer == commit.committer
127+
assert author == commit.author
128+
assert tree == commit.tree.hex
129+
assert Oid(hex=tree) == commit.tree_id
130+
assert 1 == len(commit.parents)
131+
assert parents[0] == commit.parents[0].hex
132+
assert Oid(hex=parents[0]) == commit.parent_ids[0]
133+
64134

135+
def test_get_gpg_signature_when_unsigned(gpgsigned):
136+
unhash = "5b5b025afb0b4c913b4c338a42934a3863bf3644"
65137

66-
def test_get_gpg_signature_when_unsigned(repo):
67-
unsigned_hash = 'a84938d1d885e80dae24b86b06621cec47ff6edd'
68-
commit = repo.get(unsigned_hash)
138+
repo = gpgsigned
139+
commit = repo.get(unhash)
69140
signature, payload = commit.gpg_signature
70141

71142
assert signature is None

0 commit comments

Comments
 (0)