##// END OF EJS Templates
Merge with crew-stable, fix test output
Merge with crew-stable, fix test output

File last commit:

r6163:1f733c2f default
r6550:628da4a9 merge default
Show More
gpg.py
286 lines | 8.9 KiB | text/x-python | PythonLexer
Benoit Boissinot
fixes for gpg.py extension...
r1681 # GnuPG signing extension for Mercurial
#
# Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
#
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.
import os, tempfile, binascii
Benoit Boissinot
refactor options from cmdtable...
r5147 from mercurial import util, commands
Benoit Boissinot
gpg signing extension for hg...
r1592 from mercurial import node as hgnode
Matt Mackall
Simplify i18n imports
r3891 from mercurial.i18n import _
Benoit Boissinot
gpg signing extension for hg...
r1592
class gpg:
def __init__(self, path, key=None):
self.path = path
self.key = (key and " --local-user \"%s\"" % key) or ""
def sign(self, data):
gpgcmd = "%s --sign --detach-sign%s" % (self.path, self.key)
return util.filter(data, gpgcmd)
def verify(self, data, sig):
""" returns of the good and bad signatures"""
Thomas Arendsen Hein
gpg extension: Always remove temporary files created by 'hg sigcheck'.
r2231 sigfile = datafile = None
Benoit Boissinot
gpg signing extension for hg...
r1592 try:
Benoit Boissinot
fixes for gpg.py extension...
r1681 # create temporary files
Thomas Arendsen Hein
Use better names (hg-{usage}-{random}.{suffix}) for temporary files.
r2165 fd, sigfile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".sig")
Benoit Boissinot
gpg signing extension for hg...
r1592 fp = os.fdopen(fd, 'wb')
fp.write(sig)
fp.close()
Thomas Arendsen Hein
Use better names (hg-{usage}-{random}.{suffix}) for temporary files.
r2165 fd, datafile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".txt")
Benoit Boissinot
gpg signing extension for hg...
r1592 fp = os.fdopen(fd, 'wb')
fp.write(data)
fp.close()
Benoit Boissinot
fixes for gpg.py extension...
r1681 gpgcmd = ("%s --logger-fd 1 --status-fd 1 --verify "
"\"%s\" \"%s\"" % (self.path, sigfile, datafile))
Benoit Boissinot
gpg signing extension for hg...
r1592 ret = util.filter("", gpgcmd)
Thomas Arendsen Hein
gpg extension: Always remove temporary files created by 'hg sigcheck'.
r2231 finally:
Benoit Boissinot
gpg signing extension for hg...
r1592 for f in (sigfile, datafile):
try:
if f: os.unlink(f)
except: pass
keys = []
key, fingerprint = None, None
err = ""
for l in ret.splitlines():
# see DETAILS in the gnupg documentation
# filter the logger output
if not l.startswith("[GNUPG:]"):
continue
l = l[9:]
if l.startswith("ERRSIG"):
Benoit Boissinot
fixes for gpg.py extension...
r1681 err = _("error while verifying signature")
Benoit Boissinot
gpg signing extension for hg...
r1592 break
elif l.startswith("VALIDSIG"):
# fingerprint of the primary key
fingerprint = l.split()[10]
elif (l.startswith("GOODSIG") or
l.startswith("EXPSIG") or
l.startswith("EXPKEYSIG") or
l.startswith("BADSIG")):
if key is not None:
keys.append(key + [fingerprint])
key = l.split(" ", 2)
fingerprint = None
if err:
return err, []
if key is not None:
keys.append(key + [fingerprint])
return err, keys
def newgpg(ui, **opts):
Benoit Boissinot
fixes for gpg.py extension...
r1681 """create a new gpg instance"""
Benoit Boissinot
gpg signing extension for hg...
r1592 gpgpath = ui.config("gpg", "cmd", "gpg")
gpgkey = opts.get('key')
if not gpgkey:
gpgkey = ui.config("gpg", "key", None)
return gpg(gpgpath, gpgkey)
Benoit Boissinot
fixes for gpg.py extension...
r1681 def sigwalk(repo):
"""
walk over every sigs, yields a couple
((node, version, sig), (filename, linenumber))
"""
def parsefile(fileiter, context):
ln = 1
for l in fileiter:
if not l:
continue
yield (l.split(" ", 2), (context, ln))
ln +=1
fl = repo.file(".hgsigs")
h = fl.heads()
h.reverse()
# read the heads
for r in h:
fn = ".hgsigs|%s" % hgnode.short(r)
for item in parsefile(fl.read(r).splitlines(), fn):
yield item
try:
# read local signatures
fn = "localsigs"
for item in parsefile(repo.opener(fn), fn):
yield item
except IOError:
pass
def getkeys(ui, repo, mygpg, sigdata, context):
"""get the keys who signed a data"""
fn, ln = context
node, version, sig = sigdata
prefix = "%s:%d" % (fn, ln)
node = hgnode.bin(node)
data = node2txt(repo, node, version)
sig = binascii.a2b_base64(sig)
err, keys = mygpg.verify(data, sig)
if err:
ui.warn("%s:%d %s\n" % (fn, ln , err))
return None
validkeys = []
# warn for expired key and/or sigs
for key in keys:
if key[0] == "BADSIG":
ui.write(_("%s Bad signature from \"%s\"\n") % (prefix, key[2]))
continue
if key[0] == "EXPSIG":
ui.write(_("%s Note: Signature has expired"
" (signed by: \"%s\")\n") % (prefix, key[2]))
elif key[0] == "EXPKEYSIG":
ui.write(_("%s Note: This key has expired"
" (signed by: \"%s\")\n") % (prefix, key[2]))
validkeys.append((key[1], key[2], key[3]))
return validkeys
def sigs(ui, repo):
"""list signed changesets"""
mygpg = newgpg(ui)
revs = {}
for data, context in sigwalk(repo):
node, version, sig = data
fn, ln = context
try:
n = repo.lookup(node)
except KeyError:
ui.warn(_("%s:%d node does not exist\n") % (fn, ln))
continue
r = repo.changelog.rev(n)
keys = getkeys(ui, repo, mygpg, data, context)
if not keys:
continue
revs.setdefault(r, [])
revs[r].extend(keys)
nodes = list(revs)
nodes.reverse()
Benoit Boissinot
fix an exception in gpg.py with multiples sigs for the same cset...
r1682 for rev in nodes:
for k in revs[rev]:
r = "%5d:%s" % (rev, hgnode.hex(repo.changelog.node(rev)))
Benoit Boissinot
fixes for gpg.py extension...
r1681 ui.write("%-30s %s\n" % (keystr(ui, k), r))
Benoit Boissinot
gpg signing extension for hg...
r1592 def check(ui, repo, rev):
"""verify all the signatures there may be for a particular revision"""
mygpg = newgpg(ui)
rev = repo.lookup(rev)
hexrev = hgnode.hex(rev)
keys = []
Benoit Boissinot
fixes for gpg.py extension...
r1681 for data, context in sigwalk(repo):
node, version, sig = data
if node == hexrev:
k = getkeys(ui, repo, mygpg, data, context)
if k:
keys.extend(k)
Benoit Boissinot
gpg signing extension for hg...
r1592
if not keys:
Benoit Boissinot
fixes for gpg.py extension...
r1681 ui.write(_("No valid signature for %s\n") % hgnode.short(rev))
Benoit Boissinot
gpg signing extension for hg...
r1592 return
Benoit Boissinot
fixes for gpg.py extension...
r1681
Benoit Boissinot
gpg signing extension for hg...
r1592 # print summary
ui.write("%s is signed by:\n" % hgnode.short(rev))
Benoit Boissinot
fixes for gpg.py extension...
r1681 for key in keys:
ui.write(" %s\n" % keystr(ui, key))
Benoit Boissinot
gpg signing extension for hg...
r1592
Benoit Boissinot
fixes for gpg.py extension...
r1681 def keystr(ui, key):
"""associate a string to a key (username, comment)"""
keyid, user, fingerprint = key
comment = ui.config("gpg", fingerprint, None)
if comment:
return "%s (%s)" % (user, comment)
else:
return user
Benoit Boissinot
gpg signing extension for hg...
r1592
def sign(ui, repo, *revs, **opts):
Thomas Arendsen Hein
Make 'hg sign' behave like other commands: Default to current parent.
r3916 """add a signature for the current or given revision
If no revision is given, the parent of the working directory is used,
or tip if no revision is checked out.
Thomas Arendsen Hein
Document log date ranges and mention 'hg help dates' for all commands (issue998)
r6163
See 'hg help dates' for a list of formats valid for -d/--date.
Thomas Arendsen Hein
Make 'hg sign' behave like other commands: Default to current parent.
r3916 """
Benoit Boissinot
gpg signing extension for hg...
r1592 mygpg = newgpg(ui, **opts)
sigver = "0"
sigmessage = ""
Thomas Arendsen Hein
Fix bad behaviour when specifying an invalid date (issue700)...
r6139
date = opts.get('date')
if date:
opts['date'] = util.parsedate(date)
Benoit Boissinot
gpg signing extension for hg...
r1592 if revs:
nodes = [repo.lookup(n) for n in revs]
else:
Thomas Arendsen Hein
Make 'hg sign' behave like other commands: Default to current parent.
r3916 nodes = [node for node in repo.dirstate.parents()
if node != hgnode.nullid]
if len(nodes) > 1:
raise util.Abort(_('uncommitted merge - please provide a '
'specific revision'))
if not nodes:
nodes = [repo.changelog.tip()]
Benoit Boissinot
gpg signing extension for hg...
r1592
for n in nodes:
hexnode = hgnode.hex(n)
ui.write("Signing %d:%s\n" % (repo.changelog.rev(n),
hgnode.short(n)))
# build data
data = node2txt(repo, n, sigver)
sig = mygpg.sign(data)
if not sig:
Benoit Boissinot
fixes for gpg.py extension...
r1681 raise util.Abort(_("Error while signing"))
Benoit Boissinot
gpg signing extension for hg...
r1592 sig = binascii.b2a_base64(sig)
sig = sig.replace("\n", "")
sigmessage += "%s %s %s\n" % (hexnode, sigver, sig)
# write it
if opts['local']:
repo.opener("localsigs", "ab").write(sigmessage)
return
Vadim Gelfer
remove localrepository.changes....
r2875 for x in repo.status()[:5]:
Benoit Boissinot
gpg signing extension for hg...
r1592 if ".hgsigs" in x and not opts["force"]:
Benoit Boissinot
fixes for gpg.py extension...
r1681 raise util.Abort(_("working copy of .hgsigs is changed "
"(please commit .hgsigs manually "
"or use --force)"))
Benoit Boissinot
gpg signing extension for hg...
r1592
repo.wfile(".hgsigs", "ab").write(sigmessage)
Matt Mackall
dirstate: add __contains__ and make __getitem__ more useful...
r4906 if '.hgsigs' not in repo.dirstate:
Benoit Boissinot
gpg signing extension for hg...
r1592 repo.add([".hgsigs"])
if opts["no_commit"]:
return
message = opts['message']
if not message:
Benoit Boissinot
fixes for gpg.py extension...
r1681 message = "\n".join([_("Added signature for changeset %s")
Benoit Boissinot
gpg: use the same log message format as hg tag
r5475 % hgnode.short(n)
Benoit Boissinot
gpg signing extension for hg...
r1592 for n in nodes])
try:
repo.commit([".hgsigs"], message, opts['user'], opts['date'])
except ValueError, inst:
raise util.Abort(str(inst))
def node2txt(repo, node, ver):
"""map a manifest into some text"""
if ver == "0":
return "%s\n" % hgnode.hex(node)
else:
Benoit Boissinot
extension gpg.py: really raise the exception in case of invalid data
r1685 raise util.Abort(_("unknown signature version"))
Benoit Boissinot
gpg signing extension for hg...
r1592
cmdtable = {
"sign":
(sign,
Thomas Arendsen Hein
Updated command tables in commands.py and hgext extensions....
r4730 [('l', 'local', None, _('make the signature local')),
('f', 'force', None, _('sign even if the sigfile is modified')),
('', 'no-commit', None, _('do not commit the sigfile after signing')),
Benoit Boissinot
refactor options from cmdtable...
r5147 ('k', 'key', '', _('the key id to sign with')),
Thomas Arendsen Hein
Updated command tables in commands.py and hgext extensions....
r4730 ('m', 'message', '', _('commit message')),
Benoit Boissinot
refactor options from cmdtable...
r5147 ] + commands.commitopts2,
Thomas Arendsen Hein
Updated command tables in commands.py and hgext extensions....
r4730 _('hg sign [OPTION]... [REVISION]...')),
Benoit Boissinot
fixes for gpg.py extension...
r1681 "sigcheck": (check, [], _('hg sigcheck REVISION')),
"sigs": (sigs, [], _('hg sigs')),
Benoit Boissinot
gpg signing extension for hg...
r1592 }