Skip to content

Commit

Permalink
Add support for creating signed tags.
Browse files Browse the repository at this point in the history
  • Loading branch information
jelmer committed Jan 13, 2019
1 parent d546184 commit 72aec0c
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 10 deletions.
3 changes: 3 additions & 0 deletions NEWS
Expand Up @@ -15,6 +15,9 @@
* Support plain strings as refspec arguments to
``dulwich.porcelain.push``. (Jelmer Vernooij)

* Add support for creating signed tags.
(Jelmer Vernooij, #542)

BUG FIXES

* Handle invalid ref that pretends to be a sub-folder under a valid ref.
Expand Down
12 changes: 7 additions & 5 deletions bin/dulwich
Expand Up @@ -311,11 +311,13 @@ class cmd_rev_list(Command):
class cmd_tag(Command):

def run(self, args):
opts, args = getopt(args, '', [])
if len(args) < 2:
print('Usage: dulwich tag NAME')
sys.exit(1)
porcelain.tag('.', args[0])
parser = optparse.OptionParser()
parser.add_option("-a", "--annotated", help="Create an annotated tag.", action="store_true")
parser.add_option("-s", "--sign", help="Sign the annotated tag.", action="store_true")
options, args = parser.parse_args(args)
porcelain.tag_create(
'.', args[0], annotated=options.annotated,
sign=options.sign)


class cmd_repack(Command):
Expand Down
25 changes: 22 additions & 3 deletions dulwich/objects.py
Expand Up @@ -67,6 +67,8 @@

MAX_TIME = 9223372036854775807 # (2**63) - 1 - signed long int max

BEGIN_PGP_SIGNATURE = b"-----BEGIN PGP SIGNATURE-----"


def S_ISGITLINK(m):
"""Check if a mode indicates a submodule.
Expand Down Expand Up @@ -691,14 +693,15 @@ class Tag(ShaFile):

__slots__ = ('_tag_timezone_neg_utc', '_name', '_object_sha',
'_object_class', '_tag_time', '_tag_timezone',
'_tagger', '_message')
'_tagger', '_message', '_signature')

def __init__(self):
super(Tag, self).__init__()
self._tagger = None
self._tag_time = None
self._tag_timezone = None
self._tag_timezone_neg_utc = False
self._signature = None

@classmethod
def from_path(cls, filename):
Expand Down Expand Up @@ -757,6 +760,8 @@ def _serialize(self):
if self._message is not None:
chunks.append(b'\n') # To close headers
chunks.append(self._message)
if self._signature is not None:
chunks.append(self._signature)
return chunks

def _deserialize(self, chunks):
Expand All @@ -781,7 +786,18 @@ def _deserialize(self, chunks):
(self._tag_timezone,
self._tag_timezone_neg_utc)) = parse_time_entry(value)
elif field is None:
self._message = value
if value is None:
self._message = None
self._signature = None
else:
try:
sig_idx = value.index(BEGIN_PGP_SIGNATURE)
except ValueError:
self._message = value
self._signature = None
else:
self._message = value[:sig_idx]
self._signature = value[sig_idx:]
else:
raise ObjectFormatException("Unknown field %s" % field)

Expand Down Expand Up @@ -810,7 +826,10 @@ def _set_object(self, value):
"tag_timezone",
"The timezone that tag_time is in.")
message = serializable_property(
"message", "The message attached to this tag")
"message", "the message attached to this tag")

signature = serializable_property(
"signature", "Optional detached GPG signature")


class TreeEntry(namedtuple('TreeEntry', ['path', 'mode', 'sha'])):
Expand Down
10 changes: 8 additions & 2 deletions dulwich/porcelain.py
Expand Up @@ -681,7 +681,8 @@ def tag(*args, **kwargs):

def tag_create(
repo, tag, author=None, message=None, annotated=False,
objectish="HEAD", tag_time=None, tag_timezone=None):
objectish="HEAD", tag_time=None, tag_timezone=None,
sign=False):
"""Creates a tag in git via dulwich calls:
:param repo: Path to repository
Expand All @@ -692,6 +693,7 @@ def tag_create(
:param objectish: object the tag should point at, defaults to HEAD
:param tag_time: Optional time for annotated tag
:param tag_timezone: Optional timezone for annotated tag
:param sign: GPG Sign the tag
"""

with open_repo_closing(repo) as r:
Expand All @@ -702,7 +704,7 @@ def tag_create(
tag_obj = Tag()
if author is None:
# TODO(jelmer): Don't use repo private method.
author = r._get_user_identity()
author = r._get_user_identity(r.get_config_stack())
tag_obj.tagger = author
tag_obj.message = message
tag_obj.name = tag
Expand All @@ -716,6 +718,10 @@ def tag_create(
elif isinstance(tag_timezone, str):
tag_timezone = parse_timezone(tag_timezone)
tag_obj.tag_timezone = tag_timezone
if sign:
import gpg
with gpg.Context(armor=True) as c:
tag_obj.signature, result = c.sign(tag_obj.as_raw_string())
r.object_store.add_object(tag_obj)
tag_id = tag_obj.id
else:
Expand Down
3 changes: 3 additions & 0 deletions dulwich/tests/test_objects.py
Expand Up @@ -204,6 +204,9 @@ def test_read_tag_from_file(self):
self.assertEqual(
t.message,
b'This is a signed tag\n'
)
self.assertEqual(
t.signature,
b'-----BEGIN PGP SIGNATURE-----\n'
b'Version: GnuPG v1.4.9 (GNU/Linux)\n'
b'\n'
Expand Down

0 comments on commit 72aec0c

Please sign in to comment.