From 6bf773d00d3459b4ecd906034849e78ff2a7fc28 Mon Sep 17 00:00:00 2001 From: Joel Purra Date: Tue, 21 Sep 2021 14:33:12 +0200 Subject: [PATCH] Parse gpgsig in commits - Git commits can optionally be signed using GNU Privacy Guard (GPG). - Without checking for `gpgsig` in the `git` output, signed commits would be skipped by `git2json`. - Adds the `gpgsig` property to each commit in the JSON output. - Signed commits use the multiline signature string. - Unsigned commits use `null`. - The signature is not cryptographically verified by `git2json`. - Note that signed _tags_ may be more common, but this change is only for signed _commits_. See - https://en.wikipedia.org/wiki/GNU_Privacy_Guard - https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work --- git2json/parser.py | 17 ++++++++++- tests/fixtures/test_git2json-3.txt | 34 ++++++++++++++++++++++ tests/test_git2json.py | 46 ++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/test_git2json-3.txt diff --git a/git2json/parser.py b/git2json/parser.py index 7009d9e..5a2c07e 100644 --- a/git2json/parser.py +++ b/git2json/parser.py @@ -21,7 +21,12 @@ tree\ (?P[a-f0-9]+)\n (?P(parent\ [a-f0-9]+\n)*) (?Pauthor \s+(.+)\s+<(.*)>\s+(\d+)\s+([+\-]\d\d\d\d)\n) -(?Pcommitter \s+(.+)\s+<(.*)>\s+(\d+)\s+([+\-]\d\d\d\d)\n)\n +(?Pcommitter \s+(.+)\s+<(.*)>\s+(\d+)\s+([+\-]\d\d\d\d)\n) +(?Pgpgsig\ -----BEGIN\ PGP\ SIGNATURE-----\n +\ \n +(?:\ .+\n)+ +\ -----END\ PGP\ SIGNATURE-----\n)? +\n (?P (\ \ \ \ [^\n]*\n)* ) @@ -67,6 +72,7 @@ def parse_commit(parts): ] commit['author'] = parse_author_line(parts['author']) commit['committer'] = parse_committer_line(parts['committer']) + commit['gpgsig'] = parse_gpgsig_lines(parts['gpgsig']) message_lines = [ parse_message_line(msgline) for msgline in @@ -139,6 +145,15 @@ def parse_author_line(line): return parse_person_line(line, 'author') +def parse_gpgsig_lines(lines): + if lines is None: + return None + else: + RE_GPGSIG = r'^(?:gpgsig)? (.*)$' + result = re.findall(RE_GPGSIG, lines, re.MULTILINE) + return "\n".join(result) + + def parse_message_line(line): RE_MESSAGE = r' (.*)' result = re.match(RE_MESSAGE, line) diff --git a/tests/fixtures/test_git2json-3.txt b/tests/fixtures/test_git2json-3.txt new file mode 100644 index 0000000..ee44a61 --- /dev/null +++ b/tests/fixtures/test_git2json-3.txt @@ -0,0 +1,34 @@ +commit 267f9acef754e1a627abcc049e04ae1ac4bf38d6 +tree 9d17b226048adc90307681941a2a8e5deec4041e +author Joel Purra 1599337129 +0200 +committer Joel Purra 1599347053 +0200 +gpgsig -----BEGIN PGP SIGNATURE----- + + iQJFBAABCgAvFiEE1iQVyH1ESYvl05dqlOu2SaIlXnMFAl9UGW0RHG1pZ0Bqb2Vs + cHVycmEuc2UACgkQlOu2SaIlXnMZBg/+K88Agj37hjhyuO/FlvHuh9RNVE28otSM + SyTgzkoQE7+eUXtwRKIG24rwTnT2v9qqUCQwLeiPn+TEaeYglXfnai8vxjgI7f+J + 4RxQSTrbFtiyoLDtyuZVjuJ8ih9OjSQ0xghh05DXCuvxiaatf6diZnJqmYJTV/1f + 4zGY1qChis2y+wNSWNzP+cHCiPRuDVEAgghs4c5r7Biu4ydK1Xo27bsOOVez23o/ + uEtYR6RLkiWF2iWgXuCA+04kmcsKlmtuTDW0NeB4YpQFgNr4Jj8u2VsFyfhCaF21 + iF86oCob1+rxqsoDe938g9bXjVqyuUeZJPAl+sBR6vwApgeDHy+RCRoFp6Xc/ZCc + DfxnVHndyre9+x5i0xasUy6dnrbQbssmH+zn3O9VqL8RZCix2iWiVcSqD2ona1f6 + o2q6nfn+L4foovVg4YiPmrepaknvsHsUwsrChVABnez34MSrgFtzPuJYHzVAhOJs + mYx1vHVqxrsQxttogV62L7S+DA+nDoDq3Ws3zaW1bchTNZJr6pp+GMOJkZ7X/hNH + ydIO+cCDp2UG9iwcGL9a5GYG5Di/TmasHRJULTeSTGnXK5pU/GXmkBqJi3VcRLwN + KibkkotraTKP7JnzkYSxwmC6+3FgLxEDpJGetJB1eDgUZ1APuYqAiBLzSbYxaWFp + TBkad9g7+Qg= + =NuZn + -----END PGP SIGNATURE----- + + Dummy commit text + + - This is a root commit, it has no parents. + - This commit is signed using GNU Privacy Guard (GPG). + + See + + - https://en.wikipedia.org/wiki/GNU_Privacy_Guard + - https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work + +1 0 .gitignore +109 0 README.md diff --git a/tests/test_git2json.py b/tests/test_git2json.py index 71ed257..cf5d6ce 100644 --- a/tests/test_git2json.py +++ b/tests/test_git2json.py @@ -56,6 +56,9 @@ def int_test_parse_commits(): email = commits[1]['committer']['email'] eq_(email, 'benjaminrk@gmail.com') + eq_(commits[0]['gpgsig'], None) + eq_(commits[1]['gpgsig'], None) + eq_(len(commits[0]['changes']), 0) eq_(len(commits[1]['changes']), 1) @@ -115,3 +118,46 @@ def reg_test_empty_message_lines(): Hi\rthere''' eq_(message, expected_message) + + +def int_test_parse_gpgsig_commit(): + '''Read a gpg signed root commit''' + fixture = open(get_tst_path() + 'fixtures/test_git2json-3.txt') + commits = list(git2json.parse_commits(fixture.read())) + assert len(commits) == 1 + + # Root commit, no parents. + parent = commits[0]['parents'] + assert len(parent) == 0 + + # The signature is not verified here, only extracted. + # The empty line is from the git output, so keeping it. + eq_(commits[0]['gpgsig'], '''-----BEGIN PGP SIGNATURE----- + +iQJFBAABCgAvFiEE1iQVyH1ESYvl05dqlOu2SaIlXnMFAl9UGW0RHG1pZ0Bqb2Vs +cHVycmEuc2UACgkQlOu2SaIlXnMZBg/+K88Agj37hjhyuO/FlvHuh9RNVE28otSM +SyTgzkoQE7+eUXtwRKIG24rwTnT2v9qqUCQwLeiPn+TEaeYglXfnai8vxjgI7f+J +4RxQSTrbFtiyoLDtyuZVjuJ8ih9OjSQ0xghh05DXCuvxiaatf6diZnJqmYJTV/1f +4zGY1qChis2y+wNSWNzP+cHCiPRuDVEAgghs4c5r7Biu4ydK1Xo27bsOOVez23o/ +uEtYR6RLkiWF2iWgXuCA+04kmcsKlmtuTDW0NeB4YpQFgNr4Jj8u2VsFyfhCaF21 +iF86oCob1+rxqsoDe938g9bXjVqyuUeZJPAl+sBR6vwApgeDHy+RCRoFp6Xc/ZCc +DfxnVHndyre9+x5i0xasUy6dnrbQbssmH+zn3O9VqL8RZCix2iWiVcSqD2ona1f6 +o2q6nfn+L4foovVg4YiPmrepaknvsHsUwsrChVABnez34MSrgFtzPuJYHzVAhOJs +mYx1vHVqxrsQxttogV62L7S+DA+nDoDq3Ws3zaW1bchTNZJr6pp+GMOJkZ7X/hNH +ydIO+cCDp2UG9iwcGL9a5GYG5Di/TmasHRJULTeSTGnXK5pU/GXmkBqJi3VcRLwN +KibkkotraTKP7JnzkYSxwmC6+3FgLxEDpJGetJB1eDgUZ1APuYqAiBLzSbYxaWFp +TBkad9g7+Qg= +=NuZn +-----END PGP SIGNATURE-----''') + + eq_(commits[0]['message'], '''Dummy commit text + +- This is a root commit, it has no parents. +- This commit is signed using GNU Privacy Guard (GPG). + +See + +- https://en.wikipedia.org/wiki/GNU_Privacy_Guard +- https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work''') + + eq_(len(commits[0]['changes']), 2)