##// END OF EJS Templates
gpg: make a message translatable...
FUJIWARA Katsunori -
r29239:ecf29665 default
parent child Browse files
Show More
@@ -1,318 +1,318 b''
1 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
1 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 '''commands to sign and verify changesets'''
6 '''commands to sign and verify changesets'''
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import binascii
10 import binascii
11 import os
11 import os
12 import tempfile
12 import tempfile
13
13
14 from mercurial.i18n import _
14 from mercurial.i18n import _
15 from mercurial import (
15 from mercurial import (
16 cmdutil,
16 cmdutil,
17 commands,
17 commands,
18 error,
18 error,
19 match,
19 match,
20 node as hgnode,
20 node as hgnode,
21 util,
21 util,
22 )
22 )
23
23
24 cmdtable = {}
24 cmdtable = {}
25 command = cmdutil.command(cmdtable)
25 command = cmdutil.command(cmdtable)
26 # Note for extension authors: ONLY specify testedwith = 'internal' for
26 # Note for extension authors: ONLY specify testedwith = 'internal' for
27 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
27 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
28 # be specifying the version(s) of Mercurial they are tested with, or
28 # be specifying the version(s) of Mercurial they are tested with, or
29 # leave the attribute unspecified.
29 # leave the attribute unspecified.
30 testedwith = 'internal'
30 testedwith = 'internal'
31
31
32 class gpg(object):
32 class gpg(object):
33 def __init__(self, path, key=None):
33 def __init__(self, path, key=None):
34 self.path = path
34 self.path = path
35 self.key = (key and " --local-user \"%s\"" % key) or ""
35 self.key = (key and " --local-user \"%s\"" % key) or ""
36
36
37 def sign(self, data):
37 def sign(self, data):
38 gpgcmd = "%s --sign --detach-sign%s" % (self.path, self.key)
38 gpgcmd = "%s --sign --detach-sign%s" % (self.path, self.key)
39 return util.filter(data, gpgcmd)
39 return util.filter(data, gpgcmd)
40
40
41 def verify(self, data, sig):
41 def verify(self, data, sig):
42 """ returns of the good and bad signatures"""
42 """ returns of the good and bad signatures"""
43 sigfile = datafile = None
43 sigfile = datafile = None
44 try:
44 try:
45 # create temporary files
45 # create temporary files
46 fd, sigfile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".sig")
46 fd, sigfile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".sig")
47 fp = os.fdopen(fd, 'wb')
47 fp = os.fdopen(fd, 'wb')
48 fp.write(sig)
48 fp.write(sig)
49 fp.close()
49 fp.close()
50 fd, datafile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".txt")
50 fd, datafile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".txt")
51 fp = os.fdopen(fd, 'wb')
51 fp = os.fdopen(fd, 'wb')
52 fp.write(data)
52 fp.write(data)
53 fp.close()
53 fp.close()
54 gpgcmd = ("%s --logger-fd 1 --status-fd 1 --verify "
54 gpgcmd = ("%s --logger-fd 1 --status-fd 1 --verify "
55 "\"%s\" \"%s\"" % (self.path, sigfile, datafile))
55 "\"%s\" \"%s\"" % (self.path, sigfile, datafile))
56 ret = util.filter("", gpgcmd)
56 ret = util.filter("", gpgcmd)
57 finally:
57 finally:
58 for f in (sigfile, datafile):
58 for f in (sigfile, datafile):
59 try:
59 try:
60 if f:
60 if f:
61 os.unlink(f)
61 os.unlink(f)
62 except OSError:
62 except OSError:
63 pass
63 pass
64 keys = []
64 keys = []
65 key, fingerprint = None, None
65 key, fingerprint = None, None
66 for l in ret.splitlines():
66 for l in ret.splitlines():
67 # see DETAILS in the gnupg documentation
67 # see DETAILS in the gnupg documentation
68 # filter the logger output
68 # filter the logger output
69 if not l.startswith("[GNUPG:]"):
69 if not l.startswith("[GNUPG:]"):
70 continue
70 continue
71 l = l[9:]
71 l = l[9:]
72 if l.startswith("VALIDSIG"):
72 if l.startswith("VALIDSIG"):
73 # fingerprint of the primary key
73 # fingerprint of the primary key
74 fingerprint = l.split()[10]
74 fingerprint = l.split()[10]
75 elif l.startswith("ERRSIG"):
75 elif l.startswith("ERRSIG"):
76 key = l.split(" ", 3)[:2]
76 key = l.split(" ", 3)[:2]
77 key.append("")
77 key.append("")
78 fingerprint = None
78 fingerprint = None
79 elif (l.startswith("GOODSIG") or
79 elif (l.startswith("GOODSIG") or
80 l.startswith("EXPSIG") or
80 l.startswith("EXPSIG") or
81 l.startswith("EXPKEYSIG") or
81 l.startswith("EXPKEYSIG") or
82 l.startswith("BADSIG")):
82 l.startswith("BADSIG")):
83 if key is not None:
83 if key is not None:
84 keys.append(key + [fingerprint])
84 keys.append(key + [fingerprint])
85 key = l.split(" ", 2)
85 key = l.split(" ", 2)
86 fingerprint = None
86 fingerprint = None
87 if key is not None:
87 if key is not None:
88 keys.append(key + [fingerprint])
88 keys.append(key + [fingerprint])
89 return keys
89 return keys
90
90
91 def newgpg(ui, **opts):
91 def newgpg(ui, **opts):
92 """create a new gpg instance"""
92 """create a new gpg instance"""
93 gpgpath = ui.config("gpg", "cmd", "gpg")
93 gpgpath = ui.config("gpg", "cmd", "gpg")
94 gpgkey = opts.get('key')
94 gpgkey = opts.get('key')
95 if not gpgkey:
95 if not gpgkey:
96 gpgkey = ui.config("gpg", "key", None)
96 gpgkey = ui.config("gpg", "key", None)
97 return gpg(gpgpath, gpgkey)
97 return gpg(gpgpath, gpgkey)
98
98
99 def sigwalk(repo):
99 def sigwalk(repo):
100 """
100 """
101 walk over every sigs, yields a couple
101 walk over every sigs, yields a couple
102 ((node, version, sig), (filename, linenumber))
102 ((node, version, sig), (filename, linenumber))
103 """
103 """
104 def parsefile(fileiter, context):
104 def parsefile(fileiter, context):
105 ln = 1
105 ln = 1
106 for l in fileiter:
106 for l in fileiter:
107 if not l:
107 if not l:
108 continue
108 continue
109 yield (l.split(" ", 2), (context, ln))
109 yield (l.split(" ", 2), (context, ln))
110 ln += 1
110 ln += 1
111
111
112 # read the heads
112 # read the heads
113 fl = repo.file(".hgsigs")
113 fl = repo.file(".hgsigs")
114 for r in reversed(fl.heads()):
114 for r in reversed(fl.heads()):
115 fn = ".hgsigs|%s" % hgnode.short(r)
115 fn = ".hgsigs|%s" % hgnode.short(r)
116 for item in parsefile(fl.read(r).splitlines(), fn):
116 for item in parsefile(fl.read(r).splitlines(), fn):
117 yield item
117 yield item
118 try:
118 try:
119 # read local signatures
119 # read local signatures
120 fn = "localsigs"
120 fn = "localsigs"
121 for item in parsefile(repo.vfs(fn), fn):
121 for item in parsefile(repo.vfs(fn), fn):
122 yield item
122 yield item
123 except IOError:
123 except IOError:
124 pass
124 pass
125
125
126 def getkeys(ui, repo, mygpg, sigdata, context):
126 def getkeys(ui, repo, mygpg, sigdata, context):
127 """get the keys who signed a data"""
127 """get the keys who signed a data"""
128 fn, ln = context
128 fn, ln = context
129 node, version, sig = sigdata
129 node, version, sig = sigdata
130 prefix = "%s:%d" % (fn, ln)
130 prefix = "%s:%d" % (fn, ln)
131 node = hgnode.bin(node)
131 node = hgnode.bin(node)
132
132
133 data = node2txt(repo, node, version)
133 data = node2txt(repo, node, version)
134 sig = binascii.a2b_base64(sig)
134 sig = binascii.a2b_base64(sig)
135 keys = mygpg.verify(data, sig)
135 keys = mygpg.verify(data, sig)
136
136
137 validkeys = []
137 validkeys = []
138 # warn for expired key and/or sigs
138 # warn for expired key and/or sigs
139 for key in keys:
139 for key in keys:
140 if key[0] == "ERRSIG":
140 if key[0] == "ERRSIG":
141 ui.write(_("%s Unknown key ID \"%s\"\n")
141 ui.write(_("%s Unknown key ID \"%s\"\n")
142 % (prefix, shortkey(ui, key[1][:15])))
142 % (prefix, shortkey(ui, key[1][:15])))
143 continue
143 continue
144 if key[0] == "BADSIG":
144 if key[0] == "BADSIG":
145 ui.write(_("%s Bad signature from \"%s\"\n") % (prefix, key[2]))
145 ui.write(_("%s Bad signature from \"%s\"\n") % (prefix, key[2]))
146 continue
146 continue
147 if key[0] == "EXPSIG":
147 if key[0] == "EXPSIG":
148 ui.write(_("%s Note: Signature has expired"
148 ui.write(_("%s Note: Signature has expired"
149 " (signed by: \"%s\")\n") % (prefix, key[2]))
149 " (signed by: \"%s\")\n") % (prefix, key[2]))
150 elif key[0] == "EXPKEYSIG":
150 elif key[0] == "EXPKEYSIG":
151 ui.write(_("%s Note: This key has expired"
151 ui.write(_("%s Note: This key has expired"
152 " (signed by: \"%s\")\n") % (prefix, key[2]))
152 " (signed by: \"%s\")\n") % (prefix, key[2]))
153 validkeys.append((key[1], key[2], key[3]))
153 validkeys.append((key[1], key[2], key[3]))
154 return validkeys
154 return validkeys
155
155
156 @command("sigs", [], _('hg sigs'))
156 @command("sigs", [], _('hg sigs'))
157 def sigs(ui, repo):
157 def sigs(ui, repo):
158 """list signed changesets"""
158 """list signed changesets"""
159 mygpg = newgpg(ui)
159 mygpg = newgpg(ui)
160 revs = {}
160 revs = {}
161
161
162 for data, context in sigwalk(repo):
162 for data, context in sigwalk(repo):
163 node, version, sig = data
163 node, version, sig = data
164 fn, ln = context
164 fn, ln = context
165 try:
165 try:
166 n = repo.lookup(node)
166 n = repo.lookup(node)
167 except KeyError:
167 except KeyError:
168 ui.warn(_("%s:%d node does not exist\n") % (fn, ln))
168 ui.warn(_("%s:%d node does not exist\n") % (fn, ln))
169 continue
169 continue
170 r = repo.changelog.rev(n)
170 r = repo.changelog.rev(n)
171 keys = getkeys(ui, repo, mygpg, data, context)
171 keys = getkeys(ui, repo, mygpg, data, context)
172 if not keys:
172 if not keys:
173 continue
173 continue
174 revs.setdefault(r, [])
174 revs.setdefault(r, [])
175 revs[r].extend(keys)
175 revs[r].extend(keys)
176 for rev in sorted(revs, reverse=True):
176 for rev in sorted(revs, reverse=True):
177 for k in revs[rev]:
177 for k in revs[rev]:
178 r = "%5d:%s" % (rev, hgnode.hex(repo.changelog.node(rev)))
178 r = "%5d:%s" % (rev, hgnode.hex(repo.changelog.node(rev)))
179 ui.write("%-30s %s\n" % (keystr(ui, k), r))
179 ui.write("%-30s %s\n" % (keystr(ui, k), r))
180
180
181 @command("sigcheck", [], _('hg sigcheck REV'))
181 @command("sigcheck", [], _('hg sigcheck REV'))
182 def sigcheck(ui, repo, rev):
182 def sigcheck(ui, repo, rev):
183 """verify all the signatures there may be for a particular revision"""
183 """verify all the signatures there may be for a particular revision"""
184 mygpg = newgpg(ui)
184 mygpg = newgpg(ui)
185 rev = repo.lookup(rev)
185 rev = repo.lookup(rev)
186 hexrev = hgnode.hex(rev)
186 hexrev = hgnode.hex(rev)
187 keys = []
187 keys = []
188
188
189 for data, context in sigwalk(repo):
189 for data, context in sigwalk(repo):
190 node, version, sig = data
190 node, version, sig = data
191 if node == hexrev:
191 if node == hexrev:
192 k = getkeys(ui, repo, mygpg, data, context)
192 k = getkeys(ui, repo, mygpg, data, context)
193 if k:
193 if k:
194 keys.extend(k)
194 keys.extend(k)
195
195
196 if not keys:
196 if not keys:
197 ui.write(_("no valid signature for %s\n") % hgnode.short(rev))
197 ui.write(_("no valid signature for %s\n") % hgnode.short(rev))
198 return
198 return
199
199
200 # print summary
200 # print summary
201 ui.write("%s is signed by:\n" % hgnode.short(rev))
201 ui.write(_("%s is signed by:\n") % hgnode.short(rev))
202 for key in keys:
202 for key in keys:
203 ui.write(" %s\n" % keystr(ui, key))
203 ui.write(" %s\n" % keystr(ui, key))
204
204
205 def keystr(ui, key):
205 def keystr(ui, key):
206 """associate a string to a key (username, comment)"""
206 """associate a string to a key (username, comment)"""
207 keyid, user, fingerprint = key
207 keyid, user, fingerprint = key
208 comment = ui.config("gpg", fingerprint, None)
208 comment = ui.config("gpg", fingerprint, None)
209 if comment:
209 if comment:
210 return "%s (%s)" % (user, comment)
210 return "%s (%s)" % (user, comment)
211 else:
211 else:
212 return user
212 return user
213
213
214 @command("sign",
214 @command("sign",
215 [('l', 'local', None, _('make the signature local')),
215 [('l', 'local', None, _('make the signature local')),
216 ('f', 'force', None, _('sign even if the sigfile is modified')),
216 ('f', 'force', None, _('sign even if the sigfile is modified')),
217 ('', 'no-commit', None, _('do not commit the sigfile after signing')),
217 ('', 'no-commit', None, _('do not commit the sigfile after signing')),
218 ('k', 'key', '',
218 ('k', 'key', '',
219 _('the key id to sign with'), _('ID')),
219 _('the key id to sign with'), _('ID')),
220 ('m', 'message', '',
220 ('m', 'message', '',
221 _('use text as commit message'), _('TEXT')),
221 _('use text as commit message'), _('TEXT')),
222 ('e', 'edit', False, _('invoke editor on commit messages')),
222 ('e', 'edit', False, _('invoke editor on commit messages')),
223 ] + commands.commitopts2,
223 ] + commands.commitopts2,
224 _('hg sign [OPTION]... [REV]...'))
224 _('hg sign [OPTION]... [REV]...'))
225 def sign(ui, repo, *revs, **opts):
225 def sign(ui, repo, *revs, **opts):
226 """add a signature for the current or given revision
226 """add a signature for the current or given revision
227
227
228 If no revision is given, the parent of the working directory is used,
228 If no revision is given, the parent of the working directory is used,
229 or tip if no revision is checked out.
229 or tip if no revision is checked out.
230
230
231 The ``gpg.cmd`` config setting can be used to specify the command
231 The ``gpg.cmd`` config setting can be used to specify the command
232 to run. A default key can be specified with ``gpg.key``.
232 to run. A default key can be specified with ``gpg.key``.
233
233
234 See :hg:`help dates` for a list of formats valid for -d/--date.
234 See :hg:`help dates` for a list of formats valid for -d/--date.
235 """
235 """
236 with repo.wlock():
236 with repo.wlock():
237 return _dosign(ui, repo, *revs, **opts)
237 return _dosign(ui, repo, *revs, **opts)
238
238
239 def _dosign(ui, repo, *revs, **opts):
239 def _dosign(ui, repo, *revs, **opts):
240 mygpg = newgpg(ui, **opts)
240 mygpg = newgpg(ui, **opts)
241 sigver = "0"
241 sigver = "0"
242 sigmessage = ""
242 sigmessage = ""
243
243
244 date = opts.get('date')
244 date = opts.get('date')
245 if date:
245 if date:
246 opts['date'] = util.parsedate(date)
246 opts['date'] = util.parsedate(date)
247
247
248 if revs:
248 if revs:
249 nodes = [repo.lookup(n) for n in revs]
249 nodes = [repo.lookup(n) for n in revs]
250 else:
250 else:
251 nodes = [node for node in repo.dirstate.parents()
251 nodes = [node for node in repo.dirstate.parents()
252 if node != hgnode.nullid]
252 if node != hgnode.nullid]
253 if len(nodes) > 1:
253 if len(nodes) > 1:
254 raise error.Abort(_('uncommitted merge - please provide a '
254 raise error.Abort(_('uncommitted merge - please provide a '
255 'specific revision'))
255 'specific revision'))
256 if not nodes:
256 if not nodes:
257 nodes = [repo.changelog.tip()]
257 nodes = [repo.changelog.tip()]
258
258
259 for n in nodes:
259 for n in nodes:
260 hexnode = hgnode.hex(n)
260 hexnode = hgnode.hex(n)
261 ui.write(_("signing %d:%s\n") % (repo.changelog.rev(n),
261 ui.write(_("signing %d:%s\n") % (repo.changelog.rev(n),
262 hgnode.short(n)))
262 hgnode.short(n)))
263 # build data
263 # build data
264 data = node2txt(repo, n, sigver)
264 data = node2txt(repo, n, sigver)
265 sig = mygpg.sign(data)
265 sig = mygpg.sign(data)
266 if not sig:
266 if not sig:
267 raise error.Abort(_("error while signing"))
267 raise error.Abort(_("error while signing"))
268 sig = binascii.b2a_base64(sig)
268 sig = binascii.b2a_base64(sig)
269 sig = sig.replace("\n", "")
269 sig = sig.replace("\n", "")
270 sigmessage += "%s %s %s\n" % (hexnode, sigver, sig)
270 sigmessage += "%s %s %s\n" % (hexnode, sigver, sig)
271
271
272 # write it
272 # write it
273 if opts['local']:
273 if opts['local']:
274 repo.vfs.append("localsigs", sigmessage)
274 repo.vfs.append("localsigs", sigmessage)
275 return
275 return
276
276
277 if not opts["force"]:
277 if not opts["force"]:
278 msigs = match.exact(repo.root, '', ['.hgsigs'])
278 msigs = match.exact(repo.root, '', ['.hgsigs'])
279 if any(repo.status(match=msigs, unknown=True, ignored=True)):
279 if any(repo.status(match=msigs, unknown=True, ignored=True)):
280 raise error.Abort(_("working copy of .hgsigs is changed "),
280 raise error.Abort(_("working copy of .hgsigs is changed "),
281 hint=_("please commit .hgsigs manually"))
281 hint=_("please commit .hgsigs manually"))
282
282
283 sigsfile = repo.wfile(".hgsigs", "ab")
283 sigsfile = repo.wfile(".hgsigs", "ab")
284 sigsfile.write(sigmessage)
284 sigsfile.write(sigmessage)
285 sigsfile.close()
285 sigsfile.close()
286
286
287 if '.hgsigs' not in repo.dirstate:
287 if '.hgsigs' not in repo.dirstate:
288 repo[None].add([".hgsigs"])
288 repo[None].add([".hgsigs"])
289
289
290 if opts["no_commit"]:
290 if opts["no_commit"]:
291 return
291 return
292
292
293 message = opts['message']
293 message = opts['message']
294 if not message:
294 if not message:
295 # we don't translate commit messages
295 # we don't translate commit messages
296 message = "\n".join(["Added signature for changeset %s"
296 message = "\n".join(["Added signature for changeset %s"
297 % hgnode.short(n)
297 % hgnode.short(n)
298 for n in nodes])
298 for n in nodes])
299 try:
299 try:
300 editor = cmdutil.getcommiteditor(editform='gpg.sign', **opts)
300 editor = cmdutil.getcommiteditor(editform='gpg.sign', **opts)
301 repo.commit(message, opts['user'], opts['date'], match=msigs,
301 repo.commit(message, opts['user'], opts['date'], match=msigs,
302 editor=editor)
302 editor=editor)
303 except ValueError as inst:
303 except ValueError as inst:
304 raise error.Abort(str(inst))
304 raise error.Abort(str(inst))
305
305
306 def shortkey(ui, key):
306 def shortkey(ui, key):
307 if len(key) != 16:
307 if len(key) != 16:
308 ui.debug("key ID \"%s\" format error\n" % key)
308 ui.debug("key ID \"%s\" format error\n" % key)
309 return key
309 return key
310
310
311 return key[-8:]
311 return key[-8:]
312
312
313 def node2txt(repo, node, ver):
313 def node2txt(repo, node, ver):
314 """map a manifest into some text"""
314 """map a manifest into some text"""
315 if ver == "0":
315 if ver == "0":
316 return "%s\n" % hgnode.hex(node)
316 return "%s\n" % hgnode.hex(node)
317 else:
317 else:
318 raise error.Abort(_("unknown signature version"))
318 raise error.Abort(_("unknown signature version"))
General Comments 0
You need to be logged in to leave comments. Login now