diff --git a/hgext/gpg.py b/hgext/gpg.py --- a/hgext/gpg.py +++ b/hgext/gpg.py @@ -1,6 +1,14 @@ -import os, tempfile, binascii, errno +# GnuPG signing extension for Mercurial +# +# Copyright 2005, 2006 Benoit Boissinot +# +# 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 from mercurial import util from mercurial import node as hgnode +from mercurial.i18n import gettext as _ class gpg: def __init__(self, path, key=None): @@ -14,6 +22,7 @@ class gpg: def verify(self, data, sig): """ returns of the good and bad signatures""" try: + # create temporary files fd, sigfile = tempfile.mkstemp(prefix="hggpgsig") fp = os.fdopen(fd, 'wb') fp.write(sig) @@ -22,8 +31,8 @@ class gpg: fp = os.fdopen(fd, 'wb') fp.write(data) fp.close() - gpgcmd = "%s --logger-fd 1 --status-fd 1 --verify \"%s\" \"%s\"" % (self.path, sigfile, datafile) - #gpgcmd = "%s --status-fd 1 --verify \"%s\" \"%s\"" % (self.path, sigfile, datafile) + gpgcmd = ("%s --logger-fd 1 --status-fd 1 --verify " + "\"%s\" \"%s\"" % (self.path, sigfile, datafile)) ret = util.filter("", gpgcmd) except: for f in (sigfile, datafile): @@ -41,7 +50,7 @@ class gpg: continue l = l[9:] if l.startswith("ERRSIG"): - err = "error while verifying signature" + err = _("error while verifying signature") break elif l.startswith("VALIDSIG"): # fingerprint of the primary key @@ -61,12 +70,97 @@ class gpg: return err, keys def newgpg(ui, **opts): + """create a new gpg instance""" gpgpath = ui.config("gpg", "cmd", "gpg") gpgkey = opts.get('key') if not gpgkey: gpgkey = ui.config("gpg", "key", None) return gpg(gpgpath, gpgkey) +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() + for r in nodes: + for k in revs[r]: + r = "%5d:%s" % (r, hgnode.hex(repo.changelog.node(r))) + ui.write("%-30s %s\n" % (keystr(ui, k), r)) + def check(ui, repo, rev): """verify all the signatures there may be for a particular revision""" mygpg = newgpg(ui) @@ -74,63 +168,30 @@ def check(ui, repo, rev): hexrev = hgnode.hex(rev) keys = [] - def addsig(fn, ln, l): - if not l: return - n, v, sig = l.split(" ", 2) - if n == hexrev: - data = node2txt(repo, rev, v) - sig = binascii.a2b_base64(sig) - err, k = mygpg.verify(data, sig) - if not err: - keys.append((k, fn, ln)) - else: - ui.warn("%s:%d %s\n" % (fn, ln , err)) - - fl = repo.file(".hgsigs") - h = fl.heads() - h.reverse() - # read the heads - for r in h: - ln = 1 - for l in fl.read(r).splitlines(): - addsig(".hgsigs|%s" % hgnode.short(r), ln, l) - ln +=1 - try: - # read local signatures - ln = 1 - f = repo.opener("localsigs") - for l in f: - addsig("localsigs", ln, l) - ln +=1 - except IOError: - pass + 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) if not keys: - ui.write("%s not signed\n" % hgnode.short(rev)) + ui.write(_("No valid signature for %s\n") % hgnode.short(rev)) return - valid = [] - # warn for expired key and/or sigs - for k, fn, ln in keys: - prefix = "%s:%d" % (fn, ln) - for key in k: - 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])) - valid.append((key[1], key[2], key[3])) + # print summary ui.write("%s is signed by:\n" % hgnode.short(rev)) - for keyid, user, fingerprint in valid: - role = getrole(ui, fingerprint) - ui.write(" %s (%s)\n" % (user, role)) + for key in keys: + ui.write(" %s\n" % keystr(ui, key)) -def getrole(ui, fingerprint): - return ui.config("gpg", fingerprint, "no role defined") +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 def sign(ui, repo, *revs, **opts): """add a signature for the current tip or a given revision""" @@ -150,7 +211,7 @@ def sign(ui, repo, *revs, **opts): data = node2txt(repo, n, sigver) sig = mygpg.sign(data) if not sig: - raise util.Abort("Error while signing") + raise util.Abort(_("Error while signing")) sig = binascii.b2a_base64(sig) sig = sig.replace("\n", "") sigmessage += "%s %s %s\n" % (hexnode, sigver, sig) @@ -162,9 +223,9 @@ def sign(ui, repo, *revs, **opts): for x in repo.changes(): if ".hgsigs" in x and not opts["force"]: - raise util.Abort("working copy of .hgsigs is changed " - "(please commit .hgsigs manually " - "or use --force)") + raise util.Abort(_("working copy of .hgsigs is changed " + "(please commit .hgsigs manually " + "or use --force)")) repo.wfile(".hgsigs", "ab").write(sigmessage) @@ -176,7 +237,8 @@ def sign(ui, repo, *revs, **opts): message = opts['message'] if not message: - message = "\n".join(["Added signature for changeset %s" % hgnode.hex(n) + message = "\n".join([_("Added signature for changeset %s") + % hgnode.hex(n) for n in nodes]) try: repo.commit([".hgsigs"], message, opts['user'], opts['date']) @@ -188,19 +250,20 @@ def node2txt(repo, node, ver): if ver == "0": return "%s\n" % hgnode.hex(node) else: - util.Abort("unknown signature version") + util.Abort(_("unknown signature version")) cmdtable = { "sign": (sign, - [('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"), - ('m', 'message', "", "commit message"), - ('d', 'date', "", "date code"), - ('u', 'user', "", "user"), - ('k', 'key', "", "the key id to sign with")], - "hg sign [OPTION]... REVISIONS"), - "sigcheck": (check, [], 'hg sigcheck REVISION') + [('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")), + ('m', 'message', "", _("commit message")), + ('d', 'date', "", _("date code")), + ('u', 'user', "", _("user")), + ('k', 'key', "", _("the key id to sign with"))], + _("hg sign [OPTION]... REVISIONS")), + "sigcheck": (check, [], _('hg sigcheck REVISION')), + "sigs": (sigs, [], _('hg sigs')), }