* 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.
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):
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.
__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._tag_time = None
self._tag_timezone = None
self._tag_timezone_neg_utc = False
+ self._signature = None
@classmethod
def from_path(cls, filename):
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):
(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)
"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'])):
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
: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:
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
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:
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'