##// END OF EJS Templates
merge with default
Benoit Boissinot -
r10543:dd352616 merge stable
parent child Browse files
Show More
@@ -1,218 +1,237 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2
2
3 """\
3 """\
4 reorder a revlog (the manifest by default) to save space
4 reorder a revlog (the manifest by default) to save space
5
5
6 Specifically, this topologically sorts the revisions in the revlog so that
6 Specifically, this topologically sorts the revisions in the revlog so that
7 revisions on the same branch are adjacent as much as possible. This is a
7 revisions on the same branch are adjacent as much as possible. This is a
8 workaround for the fact that Mercurial computes deltas relative to the
8 workaround for the fact that Mercurial computes deltas relative to the
9 previous revision rather than relative to a parent revision.
9 previous revision rather than relative to a parent revision.
10
10
11 This is *not* safe to run on a changelog.
11 This is *not* safe to run on a changelog.
12 """
12 """
13
13
14 # Originally written by Benoit Boissinot <benoit.boissinot at ens-lyon.org>
14 # Originally written by Benoit Boissinot <benoit.boissinot at ens-lyon.org>
15 # as a patch to rewrite-log. Cleaned up, refactored, documented, and
15 # as a patch to rewrite-log. Cleaned up, refactored, documented, and
16 # renamed by Greg Ward <greg at gerg.ca>.
16 # renamed by Greg Ward <greg at gerg.ca>.
17
17
18 # XXX would be nice to have a way to verify the repository after shrinking,
18 # XXX would be nice to have a way to verify the repository after shrinking,
19 # e.g. by comparing "before" and "after" states of random changesets
19 # e.g. by comparing "before" and "after" states of random changesets
20 # (maybe: export before, shrink, export after, diff).
20 # (maybe: export before, shrink, export after, diff).
21
21
22 import os, tempfile
22 import os, tempfile, errno
23 from mercurial import revlog, transaction, node, util
23 from mercurial import revlog, transaction, node, util
24 from mercurial import changegroup
24 from mercurial import changegroup
25 from mercurial.i18n import _
25 from mercurial.i18n import _
26
26
27 def toposort(ui, rl):
27 def toposort(ui, rl):
28
28
29 children = {}
29 children = {}
30 root = []
30 root = []
31 # build children and roots
31 # build children and roots
32 ui.status(_('reading revs\n'))
32 ui.status(_('reading revs\n'))
33 try:
33 try:
34 for i in rl:
34 for i in rl:
35 ui.progress(_('reading'), i, total=len(rl))
35 ui.progress(_('reading'), i, total=len(rl))
36 children[i] = []
36 children[i] = []
37 parents = [p for p in rl.parentrevs(i) if p != node.nullrev]
37 parents = [p for p in rl.parentrevs(i) if p != node.nullrev]
38 # in case of duplicate parents
38 # in case of duplicate parents
39 if len(parents) == 2 and parents[0] == parents[1]:
39 if len(parents) == 2 and parents[0] == parents[1]:
40 del parents[1]
40 del parents[1]
41 for p in parents:
41 for p in parents:
42 assert p in children
42 assert p in children
43 children[p].append(i)
43 children[p].append(i)
44
44
45 if len(parents) == 0:
45 if len(parents) == 0:
46 root.append(i)
46 root.append(i)
47 finally:
47 finally:
48 ui.progress(_('reading'), None, total=len(rl))
48 ui.progress(_('reading'), None, total=len(rl))
49
49
50 # XXX this is a reimplementation of the 'branchsort' topo sort
50 # XXX this is a reimplementation of the 'branchsort' topo sort
51 # algorithm in hgext.convert.convcmd... would be nice not to duplicate
51 # algorithm in hgext.convert.convcmd... would be nice not to duplicate
52 # the algorithm
52 # the algorithm
53 ui.status(_('sorting revs\n'))
53 ui.status(_('sorting revs\n'))
54 visit = root
54 visit = root
55 ret = []
55 ret = []
56 while visit:
56 while visit:
57 i = visit.pop(0)
57 i = visit.pop(0)
58 ret.append(i)
58 ret.append(i)
59 if i not in children:
59 if i not in children:
60 # This only happens if some node's p1 == p2, which can
60 # This only happens if some node's p1 == p2, which can
61 # happen in the manifest in certain circumstances.
61 # happen in the manifest in certain circumstances.
62 continue
62 continue
63 next = []
63 next = []
64 for c in children.pop(i):
64 for c in children.pop(i):
65 parents_unseen = [p for p in rl.parentrevs(c)
65 parents_unseen = [p for p in rl.parentrevs(c)
66 if p != node.nullrev and p in children]
66 if p != node.nullrev and p in children]
67 if len(parents_unseen) == 0:
67 if len(parents_unseen) == 0:
68 next.append(c)
68 next.append(c)
69 visit = next + visit
69 visit = next + visit
70 return ret
70 return ret
71
71
72 def writerevs(ui, r1, r2, order, tr):
72 def writerevs(ui, r1, r2, order, tr):
73
73
74 ui.status(_('writing revs\n'))
74 ui.status(_('writing revs\n'))
75
75
76 count = [0]
76 count = [0]
77 def progress(*args):
77 def progress(*args):
78 ui.progress(_('writing'), count[0], total=len(order))
78 ui.progress(_('writing'), count[0], total=len(order))
79 count[0] += 1
79 count[0] += 1
80
80
81 order = [r1.node(r) for r in order]
81 order = [r1.node(r) for r in order]
82
82
83 # this is a bit ugly, but it works
83 # this is a bit ugly, but it works
84 lookup = lambda x: "%020d" % r1.linkrev(r1.rev(x))
84 lookup = lambda x: "%020d" % r1.linkrev(r1.rev(x))
85 unlookup = lambda x: int(x, 10)
85 unlookup = lambda x: int(x, 10)
86
86
87 try:
87 try:
88 group = util.chunkbuffer(r1.group(order, lookup, progress))
88 group = util.chunkbuffer(r1.group(order, lookup, progress))
89 chunkiter = changegroup.chunkiter(group)
89 chunkiter = changegroup.chunkiter(group)
90 r2.addgroup(chunkiter, unlookup, tr)
90 r2.addgroup(chunkiter, unlookup, tr)
91 finally:
91 finally:
92 ui.progress(_('writing'), None, len(order))
92 ui.progress(_('writing'), None, len(order))
93
93
94 def report(ui, olddatafn, newdatafn):
94 def report(ui, r1, r2):
95 oldsize = float(os.stat(olddatafn).st_size)
95 def getsize(r):
96 newsize = float(os.stat(newdatafn).st_size)
96 s = 0
97 for fn in (r.indexfile, r.datafile):
98 try:
99 s += os.stat(fn).st_size
100 except OSError, inst:
101 if inst.errno != errno.ENOENT:
102 raise
103 return s
104
105 oldsize = float(getsize(r1))
106 newsize = float(getsize(r2))
97
107
98 # argh: have to pass an int to %d, because a float >= 2^32
108 # argh: have to pass an int to %d, because a float >= 2^32
99 # blows up under Python 2.5 or earlier
109 # blows up under Python 2.5 or earlier
100 ui.write(_('old file size: %12d bytes (%6.1f MiB)\n')
110 ui.write(_('old file size: %12d bytes (%6.1f MiB)\n')
101 % (int(oldsize), oldsize / 1024 / 1024))
111 % (int(oldsize), oldsize / 1024 / 1024))
102 ui.write(_('new file size: %12d bytes (%6.1f MiB)\n')
112 ui.write(_('new file size: %12d bytes (%6.1f MiB)\n')
103 % (int(newsize), newsize / 1024 / 1024))
113 % (int(newsize), newsize / 1024 / 1024))
104
114
105 shrink_percent = (oldsize - newsize) / oldsize * 100
115 shrink_percent = (oldsize - newsize) / oldsize * 100
106 shrink_factor = oldsize / newsize
116 shrink_factor = oldsize / newsize
107 ui.write(_('shrinkage: %.1f%% (%.1fx)\n')
117 ui.write(_('shrinkage: %.1f%% (%.1fx)\n')
108 % (shrink_percent, shrink_factor))
118 % (shrink_percent, shrink_factor))
109
119
110 def shrink(ui, repo, **opts):
120 def shrink(ui, repo, **opts):
111 """
121 """
112 Shrink revlog by re-ordering revisions. Will operate on manifest for
122 Shrink revlog by re-ordering revisions. Will operate on manifest for
113 the given repository if no other revlog is specified."""
123 the given repository if no other revlog is specified."""
114
124
115 if not repo.local():
125 if not repo.local():
116 raise util.Abort(_('not a local repository: %s') % repo.root)
126 raise util.Abort(_('not a local repository: %s') % repo.root)
117
127
118 fn = opts.get('revlog')
128 fn = opts.get('revlog')
119 if not fn:
129 if not fn:
120 indexfn = repo.sjoin('00manifest.i')
130 indexfn = repo.sjoin('00manifest.i')
121 else:
131 else:
122 if not fn.endswith('.i'):
132 if not fn.endswith('.i'):
123 raise util.Abort(_('--revlog option must specify the revlog index '
133 raise util.Abort(_('--revlog option must specify the revlog index '
124 'file (*.i), not %s') % opts.get('revlog'))
134 'file (*.i), not %s') % opts.get('revlog'))
125
135
126 indexfn = os.path.realpath(fn)
136 indexfn = os.path.realpath(fn)
127 store = repo.sjoin('')
137 store = repo.sjoin('')
128 if not indexfn.startswith(store):
138 if not indexfn.startswith(store):
129 raise util.Abort(_('--revlog option must specify a revlog in %s, '
139 raise util.Abort(_('--revlog option must specify a revlog in %s, '
130 'not %s') % (store, indexfn))
140 'not %s') % (store, indexfn))
131
141
132 datafn = indexfn[:-2] + '.d'
133 if not os.path.exists(indexfn):
142 if not os.path.exists(indexfn):
134 raise util.Abort(_('no such file: %s') % indexfn)
143 raise util.Abort(_('no such file: %s') % indexfn)
135 if '00changelog' in indexfn:
144 if '00changelog' in indexfn:
136 raise util.Abort(_('shrinking the changelog '
145 raise util.Abort(_('shrinking the changelog '
137 'will corrupt your repository'))
146 'will corrupt your repository'))
138 if not os.path.exists(datafn):
147
139 # This is just a lazy shortcut because I can't be bothered to
148 ui.write(_('shrinking %s\n') % indexfn)
140 # handle all the special cases that entail from no .d file.
149 prefix = os.path.basename(indexfn)[:-1]
141 raise util.Abort(_('%s does not exist: revlog not big enough '
150 (tmpfd, tmpindexfn) = tempfile.mkstemp(dir=os.path.dirname(indexfn),
142 'to be worth shrinking') % datafn)
151 prefix=prefix,
152 suffix='.i')
153 os.close(tmpfd)
154
155 r1 = revlog.revlog(util.opener(os.getcwd(), audit=False), indexfn)
156 r2 = revlog.revlog(util.opener(os.getcwd(), audit=False), tmpindexfn)
157
158 datafn, tmpdatafn = r1.datafile, r2.datafile
143
159
144 oldindexfn = indexfn + '.old'
160 oldindexfn = indexfn + '.old'
145 olddatafn = datafn + '.old'
161 olddatafn = datafn + '.old'
146 if os.path.exists(oldindexfn) or os.path.exists(olddatafn):
162 if os.path.exists(oldindexfn) or os.path.exists(olddatafn):
147 raise util.Abort(_('one or both of\n'
163 raise util.Abort(_('one or both of\n'
148 ' %s\n'
164 ' %s\n'
149 ' %s\n'
165 ' %s\n'
150 'exists from a previous run; please clean up '
166 'exists from a previous run; please clean up '
151 'before running again') % (oldindexfn, olddatafn))
167 'before running again') % (oldindexfn, olddatafn))
152
168
153 ui.write(_('shrinking %s\n') % indexfn)
154 prefix = os.path.basename(indexfn)[:-1]
155 (tmpfd, tmpindexfn) = tempfile.mkstemp(dir=os.path.dirname(indexfn),
156 prefix=prefix,
157 suffix='.i')
158 tmpdatafn = tmpindexfn[:-2] + '.d'
159 os.close(tmpfd)
160
161 r1 = revlog.revlog(util.opener(os.getcwd(), audit=False), indexfn)
162 r2 = revlog.revlog(util.opener(os.getcwd(), audit=False), tmpindexfn)
163
164 # Don't use repo.transaction(), because then things get hairy with
169 # Don't use repo.transaction(), because then things get hairy with
165 # paths: some need to be relative to .hg, and some need to be
170 # paths: some need to be relative to .hg, and some need to be
166 # absolute. Doing it this way keeps things simple: everything is an
171 # absolute. Doing it this way keeps things simple: everything is an
167 # absolute path.
172 # absolute path.
168 lock = repo.lock(wait=False)
173 lock = repo.lock(wait=False)
169 tr = transaction.transaction(ui.warn,
174 tr = transaction.transaction(ui.warn,
170 open,
175 open,
171 repo.sjoin('journal'))
176 repo.sjoin('journal'))
172
177
178 def ignoremissing(func):
179 def f(*args, **kw):
180 try:
181 return func(*args, **kw)
182 except OSError, inst:
183 if inst.errno != errno.ENOENT:
184 raise
185 return f
186
173 try:
187 try:
174 try:
188 try:
175 order = toposort(ui, r1)
189 order = toposort(ui, r1)
176 writerevs(ui, r1, r2, order, tr)
190 writerevs(ui, r1, r2, order, tr)
177 report(ui, datafn, tmpdatafn)
191 report(ui, r1, r2)
178 tr.close()
192 tr.close()
179 except:
193 except:
180 # Abort transaction first, so we truncate the files before
194 # Abort transaction first, so we truncate the files before
181 # deleting them.
195 # deleting them.
182 tr.abort()
196 tr.abort()
183 if os.path.exists(tmpindexfn):
197 for fn in (tmpindexfn, tmpdatafn):
184 os.unlink(tmpindexfn)
198 ignoremissing(os.unlink)(fn)
185 if os.path.exists(tmpdatafn):
186 os.unlink(tmpdatafn)
187 raise
199 raise
188 if not opts.get('dry_run'):
200 if not opts.get('dry_run'):
189 # Racy since both files cannot be renamed atomically
201 # racy, both files cannot be renamed atomically
202 # copy files
190 util.os_link(indexfn, oldindexfn)
203 util.os_link(indexfn, oldindexfn)
191 util.os_link(datafn, olddatafn)
204 ignoremissing(util.os_link)(datafn, olddatafn)
205 # rename
192 util.rename(tmpindexfn, indexfn)
206 util.rename(tmpindexfn, indexfn)
193 util.rename(tmpdatafn, datafn)
207 try:
208 util.rename(tmpdatafn, datafn)
209 except OSError, inst:
210 if inst.errno != errno.ENOENT:
211 raise
212 ignoremissing(os.unlink)(datafn)
194 else:
213 else:
195 os.unlink(tmpindexfn)
214 for fn in (tmpindexfn, tmpdatafn):
196 os.unlink(tmpdatafn)
215 ignoremissing(os.unlink)(fn)
197 finally:
216 finally:
198 lock.release()
217 lock.release()
199
218
200 if not opts.get('dry_run'):
219 if not opts.get('dry_run'):
201 ui.write(_('note: old revlog saved in:\n'
220 ui.write(_('note: old revlog saved in:\n'
202 ' %s\n'
221 ' %s\n'
203 ' %s\n'
222 ' %s\n'
204 '(You can delete those files when you are satisfied that your\n'
223 '(You can delete those files when you are satisfied that your\n'
205 'repository is still sane. '
224 'repository is still sane. '
206 'Running \'hg verify\' is strongly recommended.)\n')
225 'Running \'hg verify\' is strongly recommended.)\n')
207 % (oldindexfn, olddatafn))
226 % (oldindexfn, olddatafn))
208
227
209 cmdtable = {
228 cmdtable = {
210 'shrink': (shrink,
229 'shrink': (shrink,
211 [('', 'revlog', '', _('index (.i) file of the revlog to shrink')),
230 [('', 'revlog', '', _('index (.i) file of the revlog to shrink')),
212 ('n', 'dry-run', None, _('do not shrink, simulate only')),
231 ('n', 'dry-run', None, _('do not shrink, simulate only')),
213 ],
232 ],
214 _('hg shrink [--revlog PATH]'))
233 _('hg shrink [--revlog PATH]'))
215 }
234 }
216
235
217 if __name__ == "__main__":
236 if __name__ == "__main__":
218 print "shrink-revlog.py is now an extension (see hg help extensions)"
237 print "shrink-revlog.py is now an extension (see hg help extensions)"
@@ -1,286 +1,286 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 import os, tempfile, binascii
8 import os, tempfile, binascii
9 from mercurial import util, commands, match, cmdutil
9 from mercurial import util, commands, match
10 from mercurial import node as hgnode
10 from mercurial import node as hgnode
11 from mercurial.i18n import _
11 from mercurial.i18n import _
12
12
13 class gpg(object):
13 class gpg(object):
14 def __init__(self, path, key=None):
14 def __init__(self, path, key=None):
15 self.path = path
15 self.path = path
16 self.key = (key and " --local-user \"%s\"" % key) or ""
16 self.key = (key and " --local-user \"%s\"" % key) or ""
17
17
18 def sign(self, data):
18 def sign(self, data):
19 gpgcmd = "%s --sign --detach-sign%s" % (self.path, self.key)
19 gpgcmd = "%s --sign --detach-sign%s" % (self.path, self.key)
20 return util.filter(data, gpgcmd)
20 return util.filter(data, gpgcmd)
21
21
22 def verify(self, data, sig):
22 def verify(self, data, sig):
23 """ returns of the good and bad signatures"""
23 """ returns of the good and bad signatures"""
24 sigfile = datafile = None
24 sigfile = datafile = None
25 try:
25 try:
26 # create temporary files
26 # create temporary files
27 fd, sigfile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".sig")
27 fd, sigfile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".sig")
28 fp = os.fdopen(fd, 'wb')
28 fp = os.fdopen(fd, 'wb')
29 fp.write(sig)
29 fp.write(sig)
30 fp.close()
30 fp.close()
31 fd, datafile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".txt")
31 fd, datafile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".txt")
32 fp = os.fdopen(fd, 'wb')
32 fp = os.fdopen(fd, 'wb')
33 fp.write(data)
33 fp.write(data)
34 fp.close()
34 fp.close()
35 gpgcmd = ("%s --logger-fd 1 --status-fd 1 --verify "
35 gpgcmd = ("%s --logger-fd 1 --status-fd 1 --verify "
36 "\"%s\" \"%s\"" % (self.path, sigfile, datafile))
36 "\"%s\" \"%s\"" % (self.path, sigfile, datafile))
37 ret = util.filter("", gpgcmd)
37 ret = util.filter("", gpgcmd)
38 finally:
38 finally:
39 for f in (sigfile, datafile):
39 for f in (sigfile, datafile):
40 try:
40 try:
41 if f:
41 if f:
42 os.unlink(f)
42 os.unlink(f)
43 except:
43 except:
44 pass
44 pass
45 keys = []
45 keys = []
46 key, fingerprint = None, None
46 key, fingerprint = None, None
47 err = ""
47 err = ""
48 for l in ret.splitlines():
48 for l in ret.splitlines():
49 # see DETAILS in the gnupg documentation
49 # see DETAILS in the gnupg documentation
50 # filter the logger output
50 # filter the logger output
51 if not l.startswith("[GNUPG:]"):
51 if not l.startswith("[GNUPG:]"):
52 continue
52 continue
53 l = l[9:]
53 l = l[9:]
54 if l.startswith("ERRSIG"):
54 if l.startswith("ERRSIG"):
55 err = _("error while verifying signature")
55 err = _("error while verifying signature")
56 break
56 break
57 elif l.startswith("VALIDSIG"):
57 elif l.startswith("VALIDSIG"):
58 # fingerprint of the primary key
58 # fingerprint of the primary key
59 fingerprint = l.split()[10]
59 fingerprint = l.split()[10]
60 elif (l.startswith("GOODSIG") or
60 elif (l.startswith("GOODSIG") or
61 l.startswith("EXPSIG") or
61 l.startswith("EXPSIG") or
62 l.startswith("EXPKEYSIG") or
62 l.startswith("EXPKEYSIG") or
63 l.startswith("BADSIG")):
63 l.startswith("BADSIG")):
64 if key is not None:
64 if key is not None:
65 keys.append(key + [fingerprint])
65 keys.append(key + [fingerprint])
66 key = l.split(" ", 2)
66 key = l.split(" ", 2)
67 fingerprint = None
67 fingerprint = None
68 if err:
68 if err:
69 return err, []
69 return err, []
70 if key is not None:
70 if key is not None:
71 keys.append(key + [fingerprint])
71 keys.append(key + [fingerprint])
72 return err, keys
72 return err, keys
73
73
74 def newgpg(ui, **opts):
74 def newgpg(ui, **opts):
75 """create a new gpg instance"""
75 """create a new gpg instance"""
76 gpgpath = ui.config("gpg", "cmd", "gpg")
76 gpgpath = ui.config("gpg", "cmd", "gpg")
77 gpgkey = opts.get('key')
77 gpgkey = opts.get('key')
78 if not gpgkey:
78 if not gpgkey:
79 gpgkey = ui.config("gpg", "key", None)
79 gpgkey = ui.config("gpg", "key", None)
80 return gpg(gpgpath, gpgkey)
80 return gpg(gpgpath, gpgkey)
81
81
82 def sigwalk(repo):
82 def sigwalk(repo):
83 """
83 """
84 walk over every sigs, yields a couple
84 walk over every sigs, yields a couple
85 ((node, version, sig), (filename, linenumber))
85 ((node, version, sig), (filename, linenumber))
86 """
86 """
87 def parsefile(fileiter, context):
87 def parsefile(fileiter, context):
88 ln = 1
88 ln = 1
89 for l in fileiter:
89 for l in fileiter:
90 if not l:
90 if not l:
91 continue
91 continue
92 yield (l.split(" ", 2), (context, ln))
92 yield (l.split(" ", 2), (context, ln))
93 ln += 1
93 ln += 1
94
94
95 # read the heads
95 # read the heads
96 fl = repo.file(".hgsigs")
96 fl = repo.file(".hgsigs")
97 for r in reversed(fl.heads()):
97 for r in reversed(fl.heads()):
98 fn = ".hgsigs|%s" % hgnode.short(r)
98 fn = ".hgsigs|%s" % hgnode.short(r)
99 for item in parsefile(fl.read(r).splitlines(), fn):
99 for item in parsefile(fl.read(r).splitlines(), fn):
100 yield item
100 yield item
101 try:
101 try:
102 # read local signatures
102 # read local signatures
103 fn = "localsigs"
103 fn = "localsigs"
104 for item in parsefile(repo.opener(fn), fn):
104 for item in parsefile(repo.opener(fn), fn):
105 yield item
105 yield item
106 except IOError:
106 except IOError:
107 pass
107 pass
108
108
109 def getkeys(ui, repo, mygpg, sigdata, context):
109 def getkeys(ui, repo, mygpg, sigdata, context):
110 """get the keys who signed a data"""
110 """get the keys who signed a data"""
111 fn, ln = context
111 fn, ln = context
112 node, version, sig = sigdata
112 node, version, sig = sigdata
113 prefix = "%s:%d" % (fn, ln)
113 prefix = "%s:%d" % (fn, ln)
114 node = hgnode.bin(node)
114 node = hgnode.bin(node)
115
115
116 data = node2txt(repo, node, version)
116 data = node2txt(repo, node, version)
117 sig = binascii.a2b_base64(sig)
117 sig = binascii.a2b_base64(sig)
118 err, keys = mygpg.verify(data, sig)
118 err, keys = mygpg.verify(data, sig)
119 if err:
119 if err:
120 ui.warn("%s:%d %s\n" % (fn, ln , err))
120 ui.warn("%s:%d %s\n" % (fn, ln , err))
121 return None
121 return None
122
122
123 validkeys = []
123 validkeys = []
124 # warn for expired key and/or sigs
124 # warn for expired key and/or sigs
125 for key in keys:
125 for key in keys:
126 if key[0] == "BADSIG":
126 if key[0] == "BADSIG":
127 ui.write(_("%s Bad signature from \"%s\"\n") % (prefix, key[2]))
127 ui.write(_("%s Bad signature from \"%s\"\n") % (prefix, key[2]))
128 continue
128 continue
129 if key[0] == "EXPSIG":
129 if key[0] == "EXPSIG":
130 ui.write(_("%s Note: Signature has expired"
130 ui.write(_("%s Note: Signature has expired"
131 " (signed by: \"%s\")\n") % (prefix, key[2]))
131 " (signed by: \"%s\")\n") % (prefix, key[2]))
132 elif key[0] == "EXPKEYSIG":
132 elif key[0] == "EXPKEYSIG":
133 ui.write(_("%s Note: This key has expired"
133 ui.write(_("%s Note: This key has expired"
134 " (signed by: \"%s\")\n") % (prefix, key[2]))
134 " (signed by: \"%s\")\n") % (prefix, key[2]))
135 validkeys.append((key[1], key[2], key[3]))
135 validkeys.append((key[1], key[2], key[3]))
136 return validkeys
136 return validkeys
137
137
138 def sigs(ui, repo):
138 def sigs(ui, repo):
139 """list signed changesets"""
139 """list signed changesets"""
140 mygpg = newgpg(ui)
140 mygpg = newgpg(ui)
141 revs = {}
141 revs = {}
142
142
143 for data, context in sigwalk(repo):
143 for data, context in sigwalk(repo):
144 node, version, sig = data
144 node, version, sig = data
145 fn, ln = context
145 fn, ln = context
146 try:
146 try:
147 n = repo.lookup(node)
147 n = repo.lookup(node)
148 except KeyError:
148 except KeyError:
149 ui.warn(_("%s:%d node does not exist\n") % (fn, ln))
149 ui.warn(_("%s:%d node does not exist\n") % (fn, ln))
150 continue
150 continue
151 r = repo.changelog.rev(n)
151 r = repo.changelog.rev(n)
152 keys = getkeys(ui, repo, mygpg, data, context)
152 keys = getkeys(ui, repo, mygpg, data, context)
153 if not keys:
153 if not keys:
154 continue
154 continue
155 revs.setdefault(r, [])
155 revs.setdefault(r, [])
156 revs[r].extend(keys)
156 revs[r].extend(keys)
157 for rev in sorted(revs, reverse=True):
157 for rev in sorted(revs, reverse=True):
158 for k in revs[rev]:
158 for k in revs[rev]:
159 r = "%5d:%s" % (rev, hgnode.hex(repo.changelog.node(rev)))
159 r = "%5d:%s" % (rev, hgnode.hex(repo.changelog.node(rev)))
160 ui.write("%-30s %s\n" % (keystr(ui, k), r))
160 ui.write("%-30s %s\n" % (keystr(ui, k), r))
161
161
162 def check(ui, repo, rev):
162 def check(ui, repo, rev):
163 """verify all the signatures there may be for a particular revision"""
163 """verify all the signatures there may be for a particular revision"""
164 mygpg = newgpg(ui)
164 mygpg = newgpg(ui)
165 rev = repo.lookup(rev)
165 rev = repo.lookup(rev)
166 hexrev = hgnode.hex(rev)
166 hexrev = hgnode.hex(rev)
167 keys = []
167 keys = []
168
168
169 for data, context in sigwalk(repo):
169 for data, context in sigwalk(repo):
170 node, version, sig = data
170 node, version, sig = data
171 if node == hexrev:
171 if node == hexrev:
172 k = getkeys(ui, repo, mygpg, data, context)
172 k = getkeys(ui, repo, mygpg, data, context)
173 if k:
173 if k:
174 keys.extend(k)
174 keys.extend(k)
175
175
176 if not keys:
176 if not keys:
177 ui.write(_("No valid signature for %s\n") % hgnode.short(rev))
177 ui.write(_("No valid signature for %s\n") % hgnode.short(rev))
178 return
178 return
179
179
180 # print summary
180 # print summary
181 ui.write("%s is signed by:\n" % hgnode.short(rev))
181 ui.write("%s is signed by:\n" % hgnode.short(rev))
182 for key in keys:
182 for key in keys:
183 ui.write(" %s\n" % keystr(ui, key))
183 ui.write(" %s\n" % keystr(ui, key))
184
184
185 def keystr(ui, key):
185 def keystr(ui, key):
186 """associate a string to a key (username, comment)"""
186 """associate a string to a key (username, comment)"""
187 keyid, user, fingerprint = key
187 keyid, user, fingerprint = key
188 comment = ui.config("gpg", fingerprint, None)
188 comment = ui.config("gpg", fingerprint, None)
189 if comment:
189 if comment:
190 return "%s (%s)" % (user, comment)
190 return "%s (%s)" % (user, comment)
191 else:
191 else:
192 return user
192 return user
193
193
194 def sign(ui, repo, *revs, **opts):
194 def sign(ui, repo, *revs, **opts):
195 """add a signature for the current or given revision
195 """add a signature for the current or given revision
196
196
197 If no revision is given, the parent of the working directory is used,
197 If no revision is given, the parent of the working directory is used,
198 or tip if no revision is checked out.
198 or tip if no revision is checked out.
199
199
200 See 'hg help dates' for a list of formats valid for -d/--date.
200 See 'hg help dates' for a list of formats valid for -d/--date.
201 """
201 """
202
202
203 mygpg = newgpg(ui, **opts)
203 mygpg = newgpg(ui, **opts)
204 sigver = "0"
204 sigver = "0"
205 sigmessage = ""
205 sigmessage = ""
206
206
207 date = opts.get('date')
207 date = opts.get('date')
208 if date:
208 if date:
209 opts['date'] = util.parsedate(date)
209 opts['date'] = util.parsedate(date)
210
210
211 if revs:
211 if revs:
212 nodes = [repo.lookup(n) for n in revs]
212 nodes = [repo.lookup(n) for n in revs]
213 else:
213 else:
214 nodes = [node for node in repo.dirstate.parents()
214 nodes = [node for node in repo.dirstate.parents()
215 if node != hgnode.nullid]
215 if node != hgnode.nullid]
216 if len(nodes) > 1:
216 if len(nodes) > 1:
217 raise util.Abort(_('uncommitted merge - please provide a '
217 raise util.Abort(_('uncommitted merge - please provide a '
218 'specific revision'))
218 'specific revision'))
219 if not nodes:
219 if not nodes:
220 nodes = [repo.changelog.tip()]
220 nodes = [repo.changelog.tip()]
221
221
222 for n in nodes:
222 for n in nodes:
223 hexnode = hgnode.hex(n)
223 hexnode = hgnode.hex(n)
224 ui.write(_("Signing %d:%s\n") % (repo.changelog.rev(n),
224 ui.write(_("Signing %d:%s\n") % (repo.changelog.rev(n),
225 hgnode.short(n)))
225 hgnode.short(n)))
226 # build data
226 # build data
227 data = node2txt(repo, n, sigver)
227 data = node2txt(repo, n, sigver)
228 sig = mygpg.sign(data)
228 sig = mygpg.sign(data)
229 if not sig:
229 if not sig:
230 raise util.Abort(_("Error while signing"))
230 raise util.Abort(_("Error while signing"))
231 sig = binascii.b2a_base64(sig)
231 sig = binascii.b2a_base64(sig)
232 sig = sig.replace("\n", "")
232 sig = sig.replace("\n", "")
233 sigmessage += "%s %s %s\n" % (hexnode, sigver, sig)
233 sigmessage += "%s %s %s\n" % (hexnode, sigver, sig)
234
234
235 # write it
235 # write it
236 if opts['local']:
236 if opts['local']:
237 repo.opener("localsigs", "ab").write(sigmessage)
237 repo.opener("localsigs", "ab").write(sigmessage)
238 return
238 return
239
239
240 msigs = cmdutil.matchfiles(repo, ['.hgsigs'])
240 msigs = match.exact(repo.root, '', ['.hgsigs'])
241 s = repo.status(match=msigs, unknown=True, ignored=True)[:6]
241 s = repo.status(match=msigs, unknown=True, ignored=True)[:6]
242 if util.any(s) and not opts["force"]:
242 if util.any(s) and not opts["force"]:
243 raise util.Abort(_("working copy of .hgsigs is changed "
243 raise util.Abort(_("working copy of .hgsigs is changed "
244 "(please commit .hgsigs manually "
244 "(please commit .hgsigs manually "
245 "or use --force)"))
245 "or use --force)"))
246
246
247 repo.wfile(".hgsigs", "ab").write(sigmessage)
247 repo.wfile(".hgsigs", "ab").write(sigmessage)
248
248
249 if '.hgsigs' not in repo.dirstate:
249 if '.hgsigs' not in repo.dirstate:
250 repo.add([".hgsigs"])
250 repo.add([".hgsigs"])
251
251
252 if opts["no_commit"]:
252 if opts["no_commit"]:
253 return
253 return
254
254
255 message = opts['message']
255 message = opts['message']
256 if not message:
256 if not message:
257 # we don't translate commit messages
257 # we don't translate commit messages
258 message = "\n".join(["Added signature for changeset %s"
258 message = "\n".join(["Added signature for changeset %s"
259 % hgnode.short(n)
259 % hgnode.short(n)
260 for n in nodes])
260 for n in nodes])
261 try:
261 try:
262 repo.commit(message, opts['user'], opts['date'], match=msigs)
262 repo.commit(message, opts['user'], opts['date'], match=msigs)
263 except ValueError, inst:
263 except ValueError, inst:
264 raise util.Abort(str(inst))
264 raise util.Abort(str(inst))
265
265
266 def node2txt(repo, node, ver):
266 def node2txt(repo, node, ver):
267 """map a manifest into some text"""
267 """map a manifest into some text"""
268 if ver == "0":
268 if ver == "0":
269 return "%s\n" % hgnode.hex(node)
269 return "%s\n" % hgnode.hex(node)
270 else:
270 else:
271 raise util.Abort(_("unknown signature version"))
271 raise util.Abort(_("unknown signature version"))
272
272
273 cmdtable = {
273 cmdtable = {
274 "sign":
274 "sign":
275 (sign,
275 (sign,
276 [('l', 'local', None, _('make the signature local')),
276 [('l', 'local', None, _('make the signature local')),
277 ('f', 'force', None, _('sign even if the sigfile is modified')),
277 ('f', 'force', None, _('sign even if the sigfile is modified')),
278 ('', 'no-commit', None, _('do not commit the sigfile after signing')),
278 ('', 'no-commit', None, _('do not commit the sigfile after signing')),
279 ('k', 'key', '', _('the key id to sign with')),
279 ('k', 'key', '', _('the key id to sign with')),
280 ('m', 'message', '', _('commit message')),
280 ('m', 'message', '', _('commit message')),
281 ] + commands.commitopts2,
281 ] + commands.commitopts2,
282 _('hg sign [OPTION]... [REVISION]...')),
282 _('hg sign [OPTION]... [REVISION]...')),
283 "sigcheck": (check, [], _('hg sigcheck REVISION')),
283 "sigcheck": (check, [], _('hg sigcheck REVISION')),
284 "sigs": (sigs, [], _('hg sigs')),
284 "sigs": (sigs, [], _('hg sigs')),
285 }
285 }
286
286
General Comments 0
You need to be logged in to leave comments. Login now