Skip to content

Commit 1b8e748

Browse files
authored
Merge pull request #1055 from nejch/feat/commit-gpg-signature
feat: add support for commit GPG signature
2 parents 82deb7d + da7a809 commit 1b8e748

File tree

7 files changed

+155
-44
lines changed

7 files changed

+155
-44
lines changed

docs/cli.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,18 @@ Create a snippet:
236236
$ gitlab project-snippet create --project-id 2 --title "the title" \
237237
--file-name "the name" --code "the code"
238238
239+
Get a specific project commit by its SHA id:
240+
241+
.. code-block:: console
242+
243+
$ gitlab project-commit get --project-id 2 --id a43290c
244+
245+
Get the GPG signature of a signed commit:
246+
247+
.. code-block:: console
248+
249+
$ gitlab project-commit signature --project-id 2 --id a43290c
250+
239251
Define the status of a commit (as would be done from a CI tool for example):
240252

241253
.. code-block:: console

docs/gl_objects/commits.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ Get the references the commit has been pushed to (branches and tags)::
8282
commit.refs('tag') # only tags
8383
commit.refs('branch') # only branches
8484

85+
Get the GPG signature of the commit (if the commit was signed)::
86+
87+
commit.signature()
88+
8589
List the merge requests related to a commit::
8690

8791
commit.merge_requests()

gitlab/tests/objects/test_commits.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
from httmock import urlmatch, response, with_httmock
2+
3+
from .test_projects import headers, TestProject
4+
5+
6+
@urlmatch(
7+
scheme="http",
8+
netloc="localhost",
9+
path="/api/v4/projects/1/repository/commits/6b2257ea",
10+
method="get",
11+
)
12+
def resp_get_commit(url, request):
13+
"""Mock for commit GET response."""
14+
content = """{
15+
"id": "6b2257eabcec3db1f59dafbd84935e3caea04235",
16+
"short_id": "6b2257ea",
17+
"title": "Initial commit"
18+
}"""
19+
content = content.encode("utf-8")
20+
return response(200, content, headers, None, 5, request)
21+
22+
23+
@urlmatch(
24+
scheme="http", path="/api/v4/projects/1/repository/commits", method="post",
25+
)
26+
def resp_create_commit(url, request):
27+
"""Mock for commit create POST response."""
28+
content = """{
29+
"id": "ed899a2f4b50b4370feeea94676502b42383c746",
30+
"short_id": "ed899a2f",
31+
"title": "Commit message"
32+
}"""
33+
content = content.encode("utf-8")
34+
return response(200, content, headers, None, 5, request)
35+
36+
37+
@urlmatch(
38+
scheme="http", path="/api/v4/projects/1/repository/commits/6b2257ea", method="post",
39+
)
40+
def resp_revert_commit(url, request):
41+
"""Mock for commit revert POST response."""
42+
content = """{
43+
"id": "8b090c1b79a14f2bd9e8a738f717824ff53aebad",
44+
"short_id": "8b090c1b",
45+
"title":"Revert \\"Initial commit\\""
46+
}"""
47+
content = content.encode("utf-8")
48+
return response(200, content, headers, None, 5, request)
49+
50+
51+
@urlmatch(
52+
scheme="http",
53+
netloc="localhost",
54+
path="/api/v4/projects/1/repository/commits/6b2257ea/signature",
55+
method="get",
56+
)
57+
def resp_get_commit_gpg_signature(url, request):
58+
"""Mock for commit GPG signature GET response."""
59+
content = """{
60+
"gpg_key_id": 1,
61+
"gpg_key_primary_keyid": "8254AAB3FBD54AC9",
62+
"gpg_key_user_name": "John Doe",
63+
"gpg_key_user_email": "johndoe@example.com",
64+
"verification_status": "verified",
65+
"gpg_key_subkey_id": null
66+
}"""
67+
content = content.encode("utf-8")
68+
return response(200, content, headers, None, 5, request)
69+
70+
71+
class TestCommit(TestProject):
72+
"""
73+
Base class for commit tests. Inherits from TestProject,
74+
since currently all commit methods are under projects.
75+
"""
76+
77+
@with_httmock(resp_get_commit)
78+
def test_get_commit(self):
79+
commit = self.project.commits.get("6b2257ea")
80+
self.assertEqual(commit.short_id, "6b2257ea")
81+
self.assertEqual(commit.title, "Initial commit")
82+
83+
@with_httmock(resp_create_commit)
84+
def test_create_commit(self):
85+
data = {
86+
"branch": "master",
87+
"commit_message": "Commit message",
88+
"actions": [{"action": "create", "file_path": "README", "content": "",}],
89+
}
90+
commit = self.project.commits.create(data)
91+
self.assertEqual(commit.short_id, "ed899a2f")
92+
self.assertEqual(commit.title, data["commit_message"])
93+
94+
@with_httmock(resp_revert_commit)
95+
def test_revert_commit(self):
96+
commit = self.project.commits.get("6b2257ea", lazy=True)
97+
revert_commit = commit.revert(branch="master")
98+
self.assertEqual(revert_commit["short_id"], "8b090c1b")
99+
self.assertEqual(revert_commit["title"], 'Revert "Initial commit"')
100+
101+
@with_httmock(resp_get_commit_gpg_signature)
102+
def test_get_commit_gpg_signature(self):
103+
commit = self.project.commits.get("6b2257ea", lazy=True)
104+
signature = commit.signature()
105+
self.assertEqual(signature["gpg_key_primary_keyid"], "8254AAB3FBD54AC9")
106+
self.assertEqual(signature["verification_status"], "verified")

gitlab/tests/test_gitlab.py

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -843,50 +843,6 @@ def resp_deactivate(url, request):
843843
self.gl.users.get(1, lazy=True).activate()
844844
self.gl.users.get(1, lazy=True).deactivate()
845845

846-
def test_commit_revert(self):
847-
@urlmatch(
848-
scheme="http",
849-
netloc="localhost",
850-
path="/api/v4/projects/1/repository/commits/6b2257ea",
851-
method="get",
852-
)
853-
def resp_get_commit(url, request):
854-
headers = {"content-type": "application/json"}
855-
content = """{
856-
"id": "6b2257eabcec3db1f59dafbd84935e3caea04235",
857-
"short_id": "6b2257ea",
858-
"title": "Initial commit"
859-
}"""
860-
content = content.encode("utf-8")
861-
return response(200, content, headers, None, 5, request)
862-
863-
@urlmatch(
864-
scheme="http",
865-
netloc="localhost",
866-
path="/api/v4/projects/1/repository/commits/6b2257ea",
867-
method="post",
868-
)
869-
def resp_revert_commit(url, request):
870-
headers = {"content-type": "application/json"}
871-
content = """{
872-
"id": "8b090c1b79a14f2bd9e8a738f717824ff53aebad",
873-
"short_id": "8b090c1b",
874-
"title":"Revert \\"Initial commit\\""
875-
}"""
876-
content = content.encode("utf-8")
877-
return response(200, content, headers, None, 5, request)
878-
879-
with HTTMock(resp_get_commit):
880-
project = self.gl.projects.get(1, lazy=True)
881-
commit = project.commits.get("6b2257ea")
882-
self.assertEqual(commit.short_id, "6b2257ea")
883-
self.assertEqual(commit.title, "Initial commit")
884-
885-
with HTTMock(resp_revert_commit):
886-
revert_commit = commit.revert(branch="master")
887-
self.assertEqual(revert_commit["short_id"], "8b090c1b")
888-
self.assertEqual(revert_commit["title"], 'Revert "Initial commit"')
889-
890846
def test_update_submodule(self):
891847
@urlmatch(
892848
scheme="http", netloc="localhost", path="/api/v4/projects/1$", method="get"

gitlab/v4/objects.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2172,6 +2172,24 @@ def revert(self, branch, **kwargs):
21722172
post_data = {"branch": branch}
21732173
return self.manager.gitlab.http_post(path, post_data=post_data, **kwargs)
21742174

2175+
@cli.register_custom_action("ProjectCommit")
2176+
@exc.on_http_error(exc.GitlabGetError)
2177+
def signature(self, **kwargs):
2178+
"""Get the GPG signature of the commit.
2179+
2180+
Args:
2181+
**kwargs: Extra options to send to the server (e.g. sudo)
2182+
2183+
Raises:
2184+
GitlabAuthenticationError: If authentication is not correct
2185+
GitlabGetError: If the signature could not be retrieved
2186+
2187+
Returns:
2188+
dict: The commit's GPG signature data
2189+
"""
2190+
path = "%s/%s/signature" % (self.manager.path, self.get_id())
2191+
return self.manager.gitlab.http_get(path, **kwargs)
2192+
21752193

21762194
class ProjectCommitManager(RetrieveMixin, CreateMixin, RESTManager):
21772195
_path = "/projects/%(project_id)s/repository/commits"

tools/cli_test_v4.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,13 @@ testcase "revert commit" '
113113
--id "$COMMIT_ID" --branch master
114114
'
115115

116+
# Test commit GPG signature
117+
testcase "attempt to get GPG signature of unsigned commit" '
118+
OUTPUT=$(GITLAB project-commit signature --project-id "$PROJECT_ID" \
119+
--id "$COMMIT_ID" 2>&1 || exit 0)
120+
echo "$OUTPUT" | grep -q "404 GPG Signature Not Found"
121+
'
122+
116123
# Test project labels
117124
testcase "create project label" '
118125
OUTPUT=$(GITLAB -v project-label create --project-id $PROJECT_ID \

tools/python_test_v4.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,14 @@
473473
# assert commit.refs()
474474
# assert commit.merge_requests()
475475

476+
# commit GPG signature (for unsigned commits)
477+
# TODO: reasonable tests for signed commits?
478+
try:
479+
signature = commit.signature()
480+
except gitlab.GitlabGetError as e:
481+
error_message = e.error_message
482+
assert error_message == "404 GPG Signature Not Found"
483+
476484
# commit comment
477485
commit.comments.create({"note": "This is a commit comment"})
478486
# assert len(commit.comments.list()) == 1

0 commit comments

Comments
 (0)