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