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