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