##// END OF EJS Templates
fncache: clean up fncache during strips...
Durham Goode -
r20885:f49d60fa default
parent child Browse files
Show More
@@ -1,176 +1,178 b''
1 1 # repair.py - functions for repository repair for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 4 # Copyright 2007 Matt Mackall
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from mercurial import changegroup
10 10 from mercurial.node import short
11 11 from mercurial.i18n import _
12 12 import os
13 13 import errno
14 14
15 15 def _bundle(repo, bases, heads, node, suffix, compress=True):
16 16 """create a bundle with the specified revisions as a backup"""
17 17 cg = repo.changegroupsubset(bases, heads, 'strip')
18 18 backupdir = repo.join("strip-backup")
19 19 if not os.path.isdir(backupdir):
20 20 os.mkdir(backupdir)
21 21 name = os.path.join(backupdir, "%s-%s.hg" % (short(node), suffix))
22 22 if compress:
23 23 bundletype = "HG10BZ"
24 24 else:
25 25 bundletype = "HG10UN"
26 26 return changegroup.writebundle(cg, name, bundletype)
27 27
28 28 def _collectfiles(repo, striprev):
29 29 """find out the filelogs affected by the strip"""
30 30 files = set()
31 31
32 32 for x in xrange(striprev, len(repo)):
33 33 files.update(repo[x].files())
34 34
35 35 return sorted(files)
36 36
37 37 def _collectbrokencsets(repo, files, striprev):
38 38 """return the changesets which will be broken by the truncation"""
39 39 s = set()
40 40 def collectone(revlog):
41 41 _, brokenset = revlog.getstrippoint(striprev)
42 42 s.update([revlog.linkrev(r) for r in brokenset])
43 43
44 44 collectone(repo.manifest)
45 45 for fname in files:
46 46 collectone(repo.file(fname))
47 47
48 48 return s
49 49
50 50 def strip(ui, repo, nodelist, backup="all", topic='backup'):
51 51 repo = repo.unfiltered()
52 52 repo.destroying()
53 53
54 54 cl = repo.changelog
55 55 # TODO handle undo of merge sets
56 56 if isinstance(nodelist, str):
57 57 nodelist = [nodelist]
58 58 striplist = [cl.rev(node) for node in nodelist]
59 59 striprev = min(striplist)
60 60
61 61 keeppartialbundle = backup == 'strip'
62 62
63 63 # Some revisions with rev > striprev may not be descendants of striprev.
64 64 # We have to find these revisions and put them in a bundle, so that
65 65 # we can restore them after the truncations.
66 66 # To create the bundle we use repo.changegroupsubset which requires
67 67 # the list of heads and bases of the set of interesting revisions.
68 68 # (head = revision in the set that has no descendant in the set;
69 69 # base = revision in the set that has no ancestor in the set)
70 70 tostrip = set(striplist)
71 71 for rev in striplist:
72 72 for desc in cl.descendants([rev]):
73 73 tostrip.add(desc)
74 74
75 75 files = _collectfiles(repo, striprev)
76 76 saverevs = _collectbrokencsets(repo, files, striprev)
77 77
78 78 # compute heads
79 79 saveheads = set(saverevs)
80 80 for r in xrange(striprev + 1, len(cl)):
81 81 if r not in tostrip:
82 82 saverevs.add(r)
83 83 saveheads.difference_update(cl.parentrevs(r))
84 84 saveheads.add(r)
85 85 saveheads = [cl.node(r) for r in saveheads]
86 86
87 87 # compute base nodes
88 88 if saverevs:
89 89 descendants = set(cl.descendants(saverevs))
90 90 saverevs.difference_update(descendants)
91 91 savebases = [cl.node(r) for r in saverevs]
92 92 stripbases = [cl.node(r) for r in tostrip]
93 93
94 94 # For a set s, max(parents(s) - s) is the same as max(heads(::s - s)), but
95 95 # is much faster
96 96 newbmtarget = repo.revs('max(parents(%ld) - (%ld))', tostrip, tostrip)
97 97 if newbmtarget:
98 98 newbmtarget = repo[newbmtarget[0]].node()
99 99 else:
100 100 newbmtarget = '.'
101 101
102 102 bm = repo._bookmarks
103 103 updatebm = []
104 104 for m in bm:
105 105 rev = repo[bm[m]].rev()
106 106 if rev in tostrip:
107 107 updatebm.append(m)
108 108
109 109 # create a changegroup for all the branches we need to keep
110 110 backupfile = None
111 111 if backup == "all":
112 112 backupfile = _bundle(repo, stripbases, cl.heads(), node, topic)
113 113 repo.ui.status(_("saved backup bundle to %s\n") % backupfile)
114 114 repo.ui.log("backupbundle", "saved backup bundle to %s\n", backupfile)
115 115 if saveheads or savebases:
116 116 # do not compress partial bundle if we remove it from disk later
117 117 chgrpfile = _bundle(repo, savebases, saveheads, node, 'temp',
118 118 compress=keeppartialbundle)
119 119
120 120 mfst = repo.manifest
121 121
122 122 tr = repo.transaction("strip")
123 123 offset = len(tr.entries)
124 124
125 125 try:
126 126 tr.startgroup()
127 127 cl.strip(striprev, tr)
128 128 mfst.strip(striprev, tr)
129 129 for fn in files:
130 130 repo.file(fn).strip(striprev, tr)
131 131 tr.endgroup()
132 132
133 133 try:
134 134 for i in xrange(offset, len(tr.entries)):
135 135 file, troffset, ignore = tr.entries[i]
136 136 repo.sopener(file, 'a').truncate(troffset)
137 if troffset == 0:
138 repo.store.markremoved(file)
137 139 tr.close()
138 140 except: # re-raises
139 141 tr.abort()
140 142 raise
141 143
142 144 if saveheads or savebases:
143 145 ui.note(_("adding branch\n"))
144 146 f = open(chgrpfile, "rb")
145 147 gen = changegroup.readbundle(f, chgrpfile)
146 148 if not repo.ui.verbose:
147 149 # silence internal shuffling chatter
148 150 repo.ui.pushbuffer()
149 151 repo.addchangegroup(gen, 'strip', 'bundle:' + chgrpfile, True)
150 152 if not repo.ui.verbose:
151 153 repo.ui.popbuffer()
152 154 f.close()
153 155 if not keeppartialbundle:
154 156 os.unlink(chgrpfile)
155 157
156 158 # remove undo files
157 159 for undofile in repo.undofiles():
158 160 try:
159 161 os.unlink(undofile)
160 162 except OSError, e:
161 163 if e.errno != errno.ENOENT:
162 164 ui.warn(_('error removing %s: %s\n') % (undofile, str(e)))
163 165
164 166 for m in updatebm:
165 167 bm[m] = repo[newbmtarget].node()
166 168 bm.write()
167 169 except: # re-raises
168 170 if backupfile:
169 171 ui.warn(_("strip failed, full bundle stored in '%s'\n")
170 172 % backupfile)
171 173 elif saveheads:
172 174 ui.warn(_("strip failed, partial bundle stored in '%s'\n")
173 175 % chgrpfile)
174 176 raise
175 177
176 178 repo.destroyed()
@@ -1,529 +1,544 b''
1 1 # store.py - repository store handling for Mercurial
2 2 #
3 3 # Copyright 2008 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import scmutil, util, parsers
10 10 import os, stat, errno
11 11
12 12 _sha = util.sha1
13 13
14 14 # This avoids a collision between a file named foo and a dir named
15 15 # foo.i or foo.d
16 16 def _encodedir(path):
17 17 '''
18 18 >>> _encodedir('data/foo.i')
19 19 'data/foo.i'
20 20 >>> _encodedir('data/foo.i/bla.i')
21 21 'data/foo.i.hg/bla.i'
22 22 >>> _encodedir('data/foo.i.hg/bla.i')
23 23 'data/foo.i.hg.hg/bla.i'
24 24 >>> _encodedir('data/foo.i\\ndata/foo.i/bla.i\\ndata/foo.i.hg/bla.i\\n')
25 25 'data/foo.i\\ndata/foo.i.hg/bla.i\\ndata/foo.i.hg.hg/bla.i\\n'
26 26 '''
27 27 return (path
28 28 .replace(".hg/", ".hg.hg/")
29 29 .replace(".i/", ".i.hg/")
30 30 .replace(".d/", ".d.hg/"))
31 31
32 32 encodedir = getattr(parsers, 'encodedir', _encodedir)
33 33
34 34 def decodedir(path):
35 35 '''
36 36 >>> decodedir('data/foo.i')
37 37 'data/foo.i'
38 38 >>> decodedir('data/foo.i.hg/bla.i')
39 39 'data/foo.i/bla.i'
40 40 >>> decodedir('data/foo.i.hg.hg/bla.i')
41 41 'data/foo.i.hg/bla.i'
42 42 '''
43 43 if ".hg/" not in path:
44 44 return path
45 45 return (path
46 46 .replace(".d.hg/", ".d/")
47 47 .replace(".i.hg/", ".i/")
48 48 .replace(".hg.hg/", ".hg/"))
49 49
50 50 def _buildencodefun():
51 51 '''
52 52 >>> enc, dec = _buildencodefun()
53 53
54 54 >>> enc('nothing/special.txt')
55 55 'nothing/special.txt'
56 56 >>> dec('nothing/special.txt')
57 57 'nothing/special.txt'
58 58
59 59 >>> enc('HELLO')
60 60 '_h_e_l_l_o'
61 61 >>> dec('_h_e_l_l_o')
62 62 'HELLO'
63 63
64 64 >>> enc('hello:world?')
65 65 'hello~3aworld~3f'
66 66 >>> dec('hello~3aworld~3f')
67 67 'hello:world?'
68 68
69 69 >>> enc('the\x07quick\xADshot')
70 70 'the~07quick~adshot'
71 71 >>> dec('the~07quick~adshot')
72 72 'the\\x07quick\\xadshot'
73 73 '''
74 74 e = '_'
75 75 winreserved = [ord(x) for x in '\\:*?"<>|']
76 76 cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
77 77 for x in (range(32) + range(126, 256) + winreserved):
78 78 cmap[chr(x)] = "~%02x" % x
79 79 for x in range(ord("A"), ord("Z") + 1) + [ord(e)]:
80 80 cmap[chr(x)] = e + chr(x).lower()
81 81 dmap = {}
82 82 for k, v in cmap.iteritems():
83 83 dmap[v] = k
84 84 def decode(s):
85 85 i = 0
86 86 while i < len(s):
87 87 for l in xrange(1, 4):
88 88 try:
89 89 yield dmap[s[i:i + l]]
90 90 i += l
91 91 break
92 92 except KeyError:
93 93 pass
94 94 else:
95 95 raise KeyError
96 96 return (lambda s: ''.join([cmap[c] for c in s]),
97 97 lambda s: ''.join(list(decode(s))))
98 98
99 99 _encodefname, _decodefname = _buildencodefun()
100 100
101 101 def encodefilename(s):
102 102 '''
103 103 >>> encodefilename('foo.i/bar.d/bla.hg/hi:world?/HELLO')
104 104 'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o'
105 105 '''
106 106 return _encodefname(encodedir(s))
107 107
108 108 def decodefilename(s):
109 109 '''
110 110 >>> decodefilename('foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o')
111 111 'foo.i/bar.d/bla.hg/hi:world?/HELLO'
112 112 '''
113 113 return decodedir(_decodefname(s))
114 114
115 115 def _buildlowerencodefun():
116 116 '''
117 117 >>> f = _buildlowerencodefun()
118 118 >>> f('nothing/special.txt')
119 119 'nothing/special.txt'
120 120 >>> f('HELLO')
121 121 'hello'
122 122 >>> f('hello:world?')
123 123 'hello~3aworld~3f'
124 124 >>> f('the\x07quick\xADshot')
125 125 'the~07quick~adshot'
126 126 '''
127 127 winreserved = [ord(x) for x in '\\:*?"<>|']
128 128 cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
129 129 for x in (range(32) + range(126, 256) + winreserved):
130 130 cmap[chr(x)] = "~%02x" % x
131 131 for x in range(ord("A"), ord("Z") + 1):
132 132 cmap[chr(x)] = chr(x).lower()
133 133 return lambda s: "".join([cmap[c] for c in s])
134 134
135 135 lowerencode = getattr(parsers, 'lowerencode', None) or _buildlowerencodefun()
136 136
137 137 # Windows reserved names: con, prn, aux, nul, com1..com9, lpt1..lpt9
138 138 _winres3 = ('aux', 'con', 'prn', 'nul') # length 3
139 139 _winres4 = ('com', 'lpt') # length 4 (with trailing 1..9)
140 140 def _auxencode(path, dotencode):
141 141 '''
142 142 Encodes filenames containing names reserved by Windows or which end in
143 143 period or space. Does not touch other single reserved characters c.
144 144 Specifically, c in '\\:*?"<>|' or ord(c) <= 31 are *not* encoded here.
145 145 Additionally encodes space or period at the beginning, if dotencode is
146 146 True. Parameter path is assumed to be all lowercase.
147 147 A segment only needs encoding if a reserved name appears as a
148 148 basename (e.g. "aux", "aux.foo"). A directory or file named "foo.aux"
149 149 doesn't need encoding.
150 150
151 151 >>> s = '.foo/aux.txt/txt.aux/con/prn/nul/foo.'
152 152 >>> _auxencode(s.split('/'), True)
153 153 ['~2efoo', 'au~78.txt', 'txt.aux', 'co~6e', 'pr~6e', 'nu~6c', 'foo~2e']
154 154 >>> s = '.com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.'
155 155 >>> _auxencode(s.split('/'), False)
156 156 ['.com1com2', 'lp~749.lpt4.lpt1', 'conprn', 'com0', 'lpt0', 'foo~2e']
157 157 >>> _auxencode(['foo. '], True)
158 158 ['foo.~20']
159 159 >>> _auxencode([' .foo'], True)
160 160 ['~20.foo']
161 161 '''
162 162 for i, n in enumerate(path):
163 163 if not n:
164 164 continue
165 165 if dotencode and n[0] in '. ':
166 166 n = "~%02x" % ord(n[0]) + n[1:]
167 167 path[i] = n
168 168 else:
169 169 l = n.find('.')
170 170 if l == -1:
171 171 l = len(n)
172 172 if ((l == 3 and n[:3] in _winres3) or
173 173 (l == 4 and n[3] <= '9' and n[3] >= '1'
174 174 and n[:3] in _winres4)):
175 175 # encode third letter ('aux' -> 'au~78')
176 176 ec = "~%02x" % ord(n[2])
177 177 n = n[0:2] + ec + n[3:]
178 178 path[i] = n
179 179 if n[-1] in '. ':
180 180 # encode last period or space ('foo...' -> 'foo..~2e')
181 181 path[i] = n[:-1] + "~%02x" % ord(n[-1])
182 182 return path
183 183
184 184 _maxstorepathlen = 120
185 185 _dirprefixlen = 8
186 186 _maxshortdirslen = 8 * (_dirprefixlen + 1) - 4
187 187
188 188 def _hashencode(path, dotencode):
189 189 digest = _sha(path).hexdigest()
190 190 le = lowerencode(path).split('/')[1:]
191 191 parts = _auxencode(le, dotencode)
192 192 basename = parts[-1]
193 193 _root, ext = os.path.splitext(basename)
194 194 sdirs = []
195 195 sdirslen = 0
196 196 for p in parts[:-1]:
197 197 d = p[:_dirprefixlen]
198 198 if d[-1] in '. ':
199 199 # Windows can't access dirs ending in period or space
200 200 d = d[:-1] + '_'
201 201 if sdirslen == 0:
202 202 t = len(d)
203 203 else:
204 204 t = sdirslen + 1 + len(d)
205 205 if t > _maxshortdirslen:
206 206 break
207 207 sdirs.append(d)
208 208 sdirslen = t
209 209 dirs = '/'.join(sdirs)
210 210 if len(dirs) > 0:
211 211 dirs += '/'
212 212 res = 'dh/' + dirs + digest + ext
213 213 spaceleft = _maxstorepathlen - len(res)
214 214 if spaceleft > 0:
215 215 filler = basename[:spaceleft]
216 216 res = 'dh/' + dirs + filler + digest + ext
217 217 return res
218 218
219 219 def _hybridencode(path, dotencode):
220 220 '''encodes path with a length limit
221 221
222 222 Encodes all paths that begin with 'data/', according to the following.
223 223
224 224 Default encoding (reversible):
225 225
226 226 Encodes all uppercase letters 'X' as '_x'. All reserved or illegal
227 227 characters are encoded as '~xx', where xx is the two digit hex code
228 228 of the character (see encodefilename).
229 229 Relevant path components consisting of Windows reserved filenames are
230 230 masked by encoding the third character ('aux' -> 'au~78', see _auxencode).
231 231
232 232 Hashed encoding (not reversible):
233 233
234 234 If the default-encoded path is longer than _maxstorepathlen, a
235 235 non-reversible hybrid hashing of the path is done instead.
236 236 This encoding uses up to _dirprefixlen characters of all directory
237 237 levels of the lowerencoded path, but not more levels than can fit into
238 238 _maxshortdirslen.
239 239 Then follows the filler followed by the sha digest of the full path.
240 240 The filler is the beginning of the basename of the lowerencoded path
241 241 (the basename is everything after the last path separator). The filler
242 242 is as long as possible, filling in characters from the basename until
243 243 the encoded path has _maxstorepathlen characters (or all chars of the
244 244 basename have been taken).
245 245 The extension (e.g. '.i' or '.d') is preserved.
246 246
247 247 The string 'data/' at the beginning is replaced with 'dh/', if the hashed
248 248 encoding was used.
249 249 '''
250 250 path = encodedir(path)
251 251 ef = _encodefname(path).split('/')
252 252 res = '/'.join(_auxencode(ef, dotencode))
253 253 if len(res) > _maxstorepathlen:
254 254 res = _hashencode(path, dotencode)
255 255 return res
256 256
257 257 def _pathencode(path):
258 258 de = encodedir(path)
259 259 if len(path) > _maxstorepathlen:
260 260 return _hashencode(de, True)
261 261 ef = _encodefname(de).split('/')
262 262 res = '/'.join(_auxencode(ef, True))
263 263 if len(res) > _maxstorepathlen:
264 264 return _hashencode(de, True)
265 265 return res
266 266
267 267 _pathencode = getattr(parsers, 'pathencode', _pathencode)
268 268
269 269 def _plainhybridencode(f):
270 270 return _hybridencode(f, False)
271 271
272 272 def _calcmode(vfs):
273 273 try:
274 274 # files in .hg/ will be created using this mode
275 275 mode = vfs.stat().st_mode
276 276 # avoid some useless chmods
277 277 if (0777 & ~util.umask) == (0777 & mode):
278 278 mode = None
279 279 except OSError:
280 280 mode = None
281 281 return mode
282 282
283 283 _data = ('data 00manifest.d 00manifest.i 00changelog.d 00changelog.i'
284 284 ' phaseroots obsstore')
285 285
286 286 class basicstore(object):
287 287 '''base class for local repository stores'''
288 288 def __init__(self, path, vfstype):
289 289 vfs = vfstype(path)
290 290 self.path = vfs.base
291 291 self.createmode = _calcmode(vfs)
292 292 vfs.createmode = self.createmode
293 293 self.rawvfs = vfs
294 294 self.vfs = scmutil.filtervfs(vfs, encodedir)
295 295 self.opener = self.vfs
296 296
297 297 def join(self, f):
298 298 return self.path + '/' + encodedir(f)
299 299
300 300 def _walk(self, relpath, recurse):
301 301 '''yields (unencoded, encoded, size)'''
302 302 path = self.path
303 303 if relpath:
304 304 path += '/' + relpath
305 305 striplen = len(self.path) + 1
306 306 l = []
307 307 if self.rawvfs.isdir(path):
308 308 visit = [path]
309 309 readdir = self.rawvfs.readdir
310 310 while visit:
311 311 p = visit.pop()
312 312 for f, kind, st in readdir(p, stat=True):
313 313 fp = p + '/' + f
314 314 if kind == stat.S_IFREG and f[-2:] in ('.d', '.i'):
315 315 n = util.pconvert(fp[striplen:])
316 316 l.append((decodedir(n), n, st.st_size))
317 317 elif kind == stat.S_IFDIR and recurse:
318 318 visit.append(fp)
319 319 l.sort()
320 320 return l
321 321
322 322 def datafiles(self):
323 323 return self._walk('data', True)
324 324
325 325 def topfiles(self):
326 326 # yield manifest before changelog
327 327 return reversed(self._walk('', False))
328 328
329 329 def walk(self):
330 330 '''yields (unencoded, encoded, size)'''
331 331 # yield data files first
332 332 for x in self.datafiles():
333 333 yield x
334 334 for x in self.topfiles():
335 335 yield x
336 336
337 337 def copylist(self):
338 338 return ['requires'] + _data.split()
339 339
340 340 def write(self, tr):
341 341 pass
342 342
343 343 def invalidatecaches(self):
344 344 pass
345 345
346 def markremoved(self, fn):
347 pass
348
346 349 def __contains__(self, path):
347 350 '''Checks if the store contains path'''
348 351 path = "/".join(("data", path))
349 352 # file?
350 353 if self.vfs.exists(path + ".i"):
351 354 return True
352 355 # dir?
353 356 if not path.endswith("/"):
354 357 path = path + "/"
355 358 return self.vfs.exists(path)
356 359
357 360 class encodedstore(basicstore):
358 361 def __init__(self, path, vfstype):
359 362 vfs = vfstype(path + '/store')
360 363 self.path = vfs.base
361 364 self.createmode = _calcmode(vfs)
362 365 vfs.createmode = self.createmode
363 366 self.rawvfs = vfs
364 367 self.vfs = scmutil.filtervfs(vfs, encodefilename)
365 368 self.opener = self.vfs
366 369
367 370 def datafiles(self):
368 371 for a, b, size in self._walk('data', True):
369 372 try:
370 373 a = decodefilename(a)
371 374 except KeyError:
372 375 a = None
373 376 yield a, b, size
374 377
375 378 def join(self, f):
376 379 return self.path + '/' + encodefilename(f)
377 380
378 381 def copylist(self):
379 382 return (['requires', '00changelog.i'] +
380 383 ['store/' + f for f in _data.split()])
381 384
382 385 class fncache(object):
383 386 # the filename used to be partially encoded
384 387 # hence the encodedir/decodedir dance
385 388 def __init__(self, vfs):
386 389 self.vfs = vfs
387 390 self.entries = None
388 391 self._dirty = False
389 392
390 393 def _load(self):
391 394 '''fill the entries from the fncache file'''
392 395 self._dirty = False
393 396 try:
394 397 fp = self.vfs('fncache', mode='rb')
395 398 except IOError:
396 399 # skip nonexistent file
397 400 self.entries = set()
398 401 return
399 402 self.entries = set(decodedir(fp.read()).splitlines())
400 403 if '' in self.entries:
401 404 fp.seek(0)
402 405 for n, line in enumerate(fp):
403 406 if not line.rstrip('\n'):
404 407 t = _('invalid entry in fncache, line %s') % (n + 1)
405 408 raise util.Abort(t)
406 409 fp.close()
407 410
408 411 def write(self, tr):
409 412 if self._dirty:
410 413 tr.addbackup('fncache')
411 414 fp = self.vfs('fncache', mode='wb', atomictemp=True)
412 415 if self.entries:
413 416 fp.write(encodedir('\n'.join(self.entries) + '\n'))
414 417 fp.close()
415 418 self._dirty = False
416 419
417 420 def add(self, fn):
418 421 if self.entries is None:
419 422 self._load()
420 423 if fn not in self.entries:
421 424 self._dirty = True
422 425 self.entries.add(fn)
423 426
427 def remove(self, fn):
428 if self.entries is None:
429 self._load()
430 try:
431 self.entries.remove(fn)
432 self._dirty = True
433 except KeyError:
434 pass
435
424 436 def __contains__(self, fn):
425 437 if self.entries is None:
426 438 self._load()
427 439 return fn in self.entries
428 440
429 441 def __iter__(self):
430 442 if self.entries is None:
431 443 self._load()
432 444 return iter(self.entries)
433 445
434 446 class _fncachevfs(scmutil.abstractvfs, scmutil.auditvfs):
435 447 def __init__(self, vfs, fnc, encode):
436 448 scmutil.auditvfs.__init__(self, vfs)
437 449 self.fncache = fnc
438 450 self.encode = encode
439 451
440 452 def __call__(self, path, mode='r', *args, **kw):
441 453 if mode not in ('r', 'rb') and path.startswith('data/'):
442 454 self.fncache.add(path)
443 455 return self.vfs(self.encode(path), mode, *args, **kw)
444 456
445 457 def join(self, path):
446 458 if path:
447 459 return self.vfs.join(self.encode(path))
448 460 else:
449 461 return self.vfs.join(path)
450 462
451 463 class fncachestore(basicstore):
452 464 def __init__(self, path, vfstype, dotencode):
453 465 if dotencode:
454 466 encode = _pathencode
455 467 else:
456 468 encode = _plainhybridencode
457 469 self.encode = encode
458 470 vfs = vfstype(path + '/store')
459 471 self.path = vfs.base
460 472 self.pathsep = self.path + '/'
461 473 self.createmode = _calcmode(vfs)
462 474 vfs.createmode = self.createmode
463 475 self.rawvfs = vfs
464 476 fnc = fncache(vfs)
465 477 self.fncache = fnc
466 478 self.vfs = _fncachevfs(vfs, fnc, encode)
467 479 self.opener = self.vfs
468 480
469 481 def join(self, f):
470 482 return self.pathsep + self.encode(f)
471 483
472 484 def getsize(self, path):
473 485 return self.rawvfs.stat(path).st_size
474 486
475 487 def datafiles(self):
476 488 existing = []
477 489 for f in sorted(self.fncache):
478 490 ef = self.encode(f)
479 491 try:
480 492 yield f, ef, self.getsize(ef)
481 493 existing.append(f)
482 494 except OSError, err:
483 495 if err.errno != errno.ENOENT:
484 496 raise
485 497
486 498 def copylist(self):
487 499 d = ('data dh fncache phaseroots obsstore'
488 500 ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
489 501 return (['requires', '00changelog.i'] +
490 502 ['store/' + f for f in d.split()])
491 503
492 504 def write(self, tr):
493 505 self.fncache.write(tr)
494 506
495 507 def invalidatecaches(self):
496 508 self.fncache.entries = None
497 509
510 def markremoved(self, fn):
511 self.fncache.remove(fn)
512
498 513 def _exists(self, f):
499 514 ef = self.encode(f)
500 515 try:
501 516 self.getsize(ef)
502 517 return True
503 518 except OSError, err:
504 519 if err.errno != errno.ENOENT:
505 520 raise
506 521 # nonexistent entry
507 522 return False
508 523
509 524 def __contains__(self, path):
510 525 '''Checks if the store contains path'''
511 526 path = "/".join(("data", path))
512 527 # check for files (exact match)
513 528 e = path + '.i'
514 529 if e in self.fncache and self._exists(e):
515 530 return True
516 531 # now check for directories (prefix match)
517 532 if not path.endswith('/'):
518 533 path += '/'
519 534 for e in self.fncache:
520 535 if e.startswith(path) and self._exists(e):
521 536 return True
522 537 return False
523 538
524 539 def store(requirements, path, vfstype):
525 540 if 'store' in requirements:
526 541 if 'fncache' in requirements:
527 542 return fncachestore(path, vfstype, 'dotencode' in requirements)
528 543 return encodedstore(path, vfstype)
529 544 return basicstore(path, vfstype)
@@ -1,503 +1,516 b''
1 1 $ echo "[extensions]" >> $HGRCPATH
2 2 $ echo "strip=" >> $HGRCPATH
3 3
4 4 $ restore() {
5 5 > hg unbundle -q .hg/strip-backup/*
6 6 > rm .hg/strip-backup/*
7 7 > }
8 8 $ teststrip() {
9 9 > hg up -C $1
10 10 > echo % before update $1, strip $2
11 11 > hg parents
12 12 > hg --traceback strip $2
13 13 > echo % after update $1, strip $2
14 14 > hg parents
15 15 > restore
16 16 > }
17 17
18 18 $ hg init test
19 19 $ cd test
20 20
21 21 $ echo foo > bar
22 22 $ hg ci -Ama
23 23 adding bar
24 24
25 25 $ echo more >> bar
26 26 $ hg ci -Amb
27 27
28 28 $ echo blah >> bar
29 29 $ hg ci -Amc
30 30
31 31 $ hg up 1
32 32 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
33 33 $ echo blah >> bar
34 34 $ hg ci -Amd
35 35 created new head
36 36
37 37 $ echo final >> bar
38 38 $ hg ci -Ame
39 39
40 40 $ hg log
41 41 changeset: 4:443431ffac4f
42 42 tag: tip
43 43 user: test
44 44 date: Thu Jan 01 00:00:00 1970 +0000
45 45 summary: e
46 46
47 47 changeset: 3:65bd5f99a4a3
48 48 parent: 1:ef3a871183d7
49 49 user: test
50 50 date: Thu Jan 01 00:00:00 1970 +0000
51 51 summary: d
52 52
53 53 changeset: 2:264128213d29
54 54 user: test
55 55 date: Thu Jan 01 00:00:00 1970 +0000
56 56 summary: c
57 57
58 58 changeset: 1:ef3a871183d7
59 59 user: test
60 60 date: Thu Jan 01 00:00:00 1970 +0000
61 61 summary: b
62 62
63 63 changeset: 0:9ab35a2d17cb
64 64 user: test
65 65 date: Thu Jan 01 00:00:00 1970 +0000
66 66 summary: a
67 67
68 68
69 69 $ teststrip 4 4
70 70 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
71 71 % before update 4, strip 4
72 72 changeset: 4:443431ffac4f
73 73 tag: tip
74 74 user: test
75 75 date: Thu Jan 01 00:00:00 1970 +0000
76 76 summary: e
77 77
78 78 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
79 79 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
80 80 % after update 4, strip 4
81 81 changeset: 3:65bd5f99a4a3
82 82 tag: tip
83 83 parent: 1:ef3a871183d7
84 84 user: test
85 85 date: Thu Jan 01 00:00:00 1970 +0000
86 86 summary: d
87 87
88 88 $ teststrip 4 3
89 89 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
90 90 % before update 4, strip 3
91 91 changeset: 4:443431ffac4f
92 92 tag: tip
93 93 user: test
94 94 date: Thu Jan 01 00:00:00 1970 +0000
95 95 summary: e
96 96
97 97 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
98 98 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
99 99 % after update 4, strip 3
100 100 changeset: 1:ef3a871183d7
101 101 user: test
102 102 date: Thu Jan 01 00:00:00 1970 +0000
103 103 summary: b
104 104
105 105 $ teststrip 1 4
106 106 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
107 107 % before update 1, strip 4
108 108 changeset: 1:ef3a871183d7
109 109 user: test
110 110 date: Thu Jan 01 00:00:00 1970 +0000
111 111 summary: b
112 112
113 113 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
114 114 % after update 1, strip 4
115 115 changeset: 1:ef3a871183d7
116 116 user: test
117 117 date: Thu Jan 01 00:00:00 1970 +0000
118 118 summary: b
119 119
120 120 $ teststrip 4 2
121 121 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
122 122 % before update 4, strip 2
123 123 changeset: 4:443431ffac4f
124 124 tag: tip
125 125 user: test
126 126 date: Thu Jan 01 00:00:00 1970 +0000
127 127 summary: e
128 128
129 129 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
130 130 % after update 4, strip 2
131 131 changeset: 3:443431ffac4f
132 132 tag: tip
133 133 user: test
134 134 date: Thu Jan 01 00:00:00 1970 +0000
135 135 summary: e
136 136
137 137 $ teststrip 4 1
138 138 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
139 139 % before update 4, strip 1
140 140 changeset: 4:264128213d29
141 141 tag: tip
142 142 parent: 1:ef3a871183d7
143 143 user: test
144 144 date: Thu Jan 01 00:00:00 1970 +0000
145 145 summary: c
146 146
147 147 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
148 148 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
149 149 % after update 4, strip 1
150 150 changeset: 0:9ab35a2d17cb
151 151 tag: tip
152 152 user: test
153 153 date: Thu Jan 01 00:00:00 1970 +0000
154 154 summary: a
155 155
156 156 $ teststrip null 4
157 157 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
158 158 % before update null, strip 4
159 159 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
160 160 % after update null, strip 4
161 161
162 162 $ hg log
163 163 changeset: 4:264128213d29
164 164 tag: tip
165 165 parent: 1:ef3a871183d7
166 166 user: test
167 167 date: Thu Jan 01 00:00:00 1970 +0000
168 168 summary: c
169 169
170 170 changeset: 3:443431ffac4f
171 171 user: test
172 172 date: Thu Jan 01 00:00:00 1970 +0000
173 173 summary: e
174 174
175 175 changeset: 2:65bd5f99a4a3
176 176 user: test
177 177 date: Thu Jan 01 00:00:00 1970 +0000
178 178 summary: d
179 179
180 180 changeset: 1:ef3a871183d7
181 181 user: test
182 182 date: Thu Jan 01 00:00:00 1970 +0000
183 183 summary: b
184 184
185 185 changeset: 0:9ab35a2d17cb
186 186 user: test
187 187 date: Thu Jan 01 00:00:00 1970 +0000
188 188 summary: a
189 189
190 190
191 191 $ hg up -C 2
192 192 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
193 193 $ hg merge 4
194 194 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
195 195 (branch merge, don't forget to commit)
196 196
197 197 before strip of merge parent
198 198
199 199 $ hg parents
200 200 changeset: 2:65bd5f99a4a3
201 201 user: test
202 202 date: Thu Jan 01 00:00:00 1970 +0000
203 203 summary: d
204 204
205 205 changeset: 4:264128213d29
206 206 tag: tip
207 207 parent: 1:ef3a871183d7
208 208 user: test
209 209 date: Thu Jan 01 00:00:00 1970 +0000
210 210 summary: c
211 211
212 212 $ hg strip 4
213 213 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
214 214 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
215 215
216 216 after strip of merge parent
217 217
218 218 $ hg parents
219 219 changeset: 1:ef3a871183d7
220 220 user: test
221 221 date: Thu Jan 01 00:00:00 1970 +0000
222 222 summary: b
223 223
224 224 $ restore
225 225
226 226 $ hg up
227 227 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
228 228 $ hg log -G
229 229 @ changeset: 4:264128213d29
230 230 | tag: tip
231 231 | parent: 1:ef3a871183d7
232 232 | user: test
233 233 | date: Thu Jan 01 00:00:00 1970 +0000
234 234 | summary: c
235 235 |
236 236 | o changeset: 3:443431ffac4f
237 237 | | user: test
238 238 | | date: Thu Jan 01 00:00:00 1970 +0000
239 239 | | summary: e
240 240 | |
241 241 | o changeset: 2:65bd5f99a4a3
242 242 |/ user: test
243 243 | date: Thu Jan 01 00:00:00 1970 +0000
244 244 | summary: d
245 245 |
246 246 o changeset: 1:ef3a871183d7
247 247 | user: test
248 248 | date: Thu Jan 01 00:00:00 1970 +0000
249 249 | summary: b
250 250 |
251 251 o changeset: 0:9ab35a2d17cb
252 252 user: test
253 253 date: Thu Jan 01 00:00:00 1970 +0000
254 254 summary: a
255 255
256 256
257 257 2 is parent of 3, only one strip should happen
258 258
259 259 $ hg strip "roots(2)" 3
260 260 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
261 261 $ hg log -G
262 262 @ changeset: 2:264128213d29
263 263 | tag: tip
264 264 | user: test
265 265 | date: Thu Jan 01 00:00:00 1970 +0000
266 266 | summary: c
267 267 |
268 268 o changeset: 1:ef3a871183d7
269 269 | user: test
270 270 | date: Thu Jan 01 00:00:00 1970 +0000
271 271 | summary: b
272 272 |
273 273 o changeset: 0:9ab35a2d17cb
274 274 user: test
275 275 date: Thu Jan 01 00:00:00 1970 +0000
276 276 summary: a
277 277
278 278 $ restore
279 279 $ hg log -G
280 280 o changeset: 4:443431ffac4f
281 281 | tag: tip
282 282 | user: test
283 283 | date: Thu Jan 01 00:00:00 1970 +0000
284 284 | summary: e
285 285 |
286 286 o changeset: 3:65bd5f99a4a3
287 287 | parent: 1:ef3a871183d7
288 288 | user: test
289 289 | date: Thu Jan 01 00:00:00 1970 +0000
290 290 | summary: d
291 291 |
292 292 | @ changeset: 2:264128213d29
293 293 |/ user: test
294 294 | date: Thu Jan 01 00:00:00 1970 +0000
295 295 | summary: c
296 296 |
297 297 o changeset: 1:ef3a871183d7
298 298 | user: test
299 299 | date: Thu Jan 01 00:00:00 1970 +0000
300 300 | summary: b
301 301 |
302 302 o changeset: 0:9ab35a2d17cb
303 303 user: test
304 304 date: Thu Jan 01 00:00:00 1970 +0000
305 305 summary: a
306 306
307 307
308 308 2 different branches: 2 strips
309 309
310 310 $ hg strip 2 4
311 311 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
312 312 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
313 313 $ hg log -G
314 314 o changeset: 2:65bd5f99a4a3
315 315 | tag: tip
316 316 | user: test
317 317 | date: Thu Jan 01 00:00:00 1970 +0000
318 318 | summary: d
319 319 |
320 320 @ changeset: 1:ef3a871183d7
321 321 | user: test
322 322 | date: Thu Jan 01 00:00:00 1970 +0000
323 323 | summary: b
324 324 |
325 325 o changeset: 0:9ab35a2d17cb
326 326 user: test
327 327 date: Thu Jan 01 00:00:00 1970 +0000
328 328 summary: a
329 329
330 330 $ restore
331 331
332 332 2 different branches and a common ancestor: 1 strip
333 333
334 334 $ hg strip 1 "2|4"
335 335 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
336 336 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
337 337 $ restore
338 338
339 verify fncache is kept up-to-date
340
341 $ touch a
342 $ hg ci -qAm a
343 $ cat .hg/store/fncache | sort
344 data/a.i
345 data/bar.i
346 $ hg strip tip
347 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
348 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
349 $ cat .hg/store/fncache
350 data/bar.i
351
339 352 stripping an empty revset
340 353
341 354 $ hg strip "1 and not 1"
342 355 abort: empty revision set
343 356 [255]
344 357
345 358 remove branchy history for qimport tests
346 359
347 360 $ hg strip 3
348 361 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
349 362
350 363
351 364 strip of applied mq should cleanup status file
352 365
353 366 $ echo "mq=" >> $HGRCPATH
354 367 $ hg up -C 3
355 368 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
356 369 $ echo fooagain >> bar
357 370 $ hg ci -mf
358 371 $ hg qimport -r tip:2
359 372
360 373 applied patches before strip
361 374
362 375 $ hg qapplied
363 376 2.diff
364 377 3.diff
365 378 4.diff
366 379
367 380 stripping revision in queue
368 381
369 382 $ hg strip 3
370 383 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
371 384 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
372 385
373 386 applied patches after stripping rev in queue
374 387
375 388 $ hg qapplied
376 389 2.diff
377 390
378 391 stripping ancestor of queue
379 392
380 393 $ hg strip 1
381 394 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
382 395 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
383 396
384 397 applied patches after stripping ancestor of queue
385 398
386 399 $ hg qapplied
387 400
388 401 Verify strip protects against stripping wc parent when there are uncommitted mods
389 402
390 403 $ echo b > b
391 404 $ hg add b
392 405 $ hg ci -m 'b'
393 406 $ hg log --graph
394 407 @ changeset: 1:7519abd79d14
395 408 | tag: tip
396 409 | user: test
397 410 | date: Thu Jan 01 00:00:00 1970 +0000
398 411 | summary: b
399 412 |
400 413 o changeset: 0:9ab35a2d17cb
401 414 user: test
402 415 date: Thu Jan 01 00:00:00 1970 +0000
403 416 summary: a
404 417
405 418
406 419 $ echo c > b
407 420 $ echo c > bar
408 421 $ hg strip tip
409 422 abort: local changes found
410 423 [255]
411 424 $ hg strip tip --keep
412 425 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
413 426 $ hg log --graph
414 427 @ changeset: 0:9ab35a2d17cb
415 428 tag: tip
416 429 user: test
417 430 date: Thu Jan 01 00:00:00 1970 +0000
418 431 summary: a
419 432
420 433 $ hg status
421 434 M bar
422 435 ? b
423 436
424 437 Strip adds, removes, modifies with --keep
425 438
426 439 $ touch b
427 440 $ hg add b
428 441 $ hg commit -mb
429 442 $ touch c
430 443
431 444 ... with a clean working dir
432 445
433 446 $ hg add c
434 447 $ hg rm bar
435 448 $ hg commit -mc
436 449 $ hg status
437 450 $ hg strip --keep tip
438 451 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
439 452 $ hg status
440 453 ! bar
441 454 ? c
442 455
443 456 ... with a dirty working dir
444 457
445 458 $ hg add c
446 459 $ hg rm bar
447 460 $ hg commit -mc
448 461 $ hg status
449 462 $ echo b > b
450 463 $ echo d > d
451 464 $ hg strip --keep tip
452 465 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
453 466 $ hg status
454 467 M b
455 468 ! bar
456 469 ? c
457 470 ? d
458 471 $ cd ..
459 472
460 473 stripping many nodes on a complex graph (issue3299)
461 474
462 475 $ hg init issue3299
463 476 $ cd issue3299
464 477 $ hg debugbuilddag '@a.:a@b.:b.:x<a@a.:a<b@b.:b<a@a.:a'
465 478 $ hg strip 'not ancestors(x)'
466 479 saved backup bundle to $TESTTMP/issue3299/.hg/strip-backup/*-backup.hg (glob)
467 480
468 481 test hg strip -B bookmark
469 482
470 483 $ cd ..
471 484 $ hg init bookmarks
472 485 $ cd bookmarks
473 486 $ hg debugbuilddag '..<2.*1/2:m<2+3:c<m+3:a<2.:b'
474 487 $ hg bookmark -r 'a' 'todelete'
475 488 $ hg bookmark -r 'b' 'B'
476 489 $ hg bookmark -r 'b' 'nostrip'
477 490 $ hg bookmark -r 'c' 'delete'
478 491 $ hg up -C todelete
479 492 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
480 493 $ hg strip -B nostrip
481 494 bookmark 'nostrip' deleted
482 495 abort: empty revision set
483 496 [255]
484 497 $ hg strip -B todelete
485 498 bookmark 'todelete' deleted
486 499 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
487 500 saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/*-backup.hg (glob)
488 501 $ hg id -ir dcbb326fdec2
489 502 abort: unknown revision 'dcbb326fdec2'!
490 503 [255]
491 504 $ hg id -ir d62d843c9a01
492 505 d62d843c9a01
493 506 $ hg bookmarks
494 507 B 9:ff43616e5d0f
495 508 delete 6:2702dd0c91e7
496 509 $ hg strip -B delete
497 510 bookmark 'delete' deleted
498 511 saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/*-backup.hg (glob)
499 512 $ hg id -ir 6:2702dd0c91e7
500 513 abort: unknown revision '2702dd0c91e7'!
501 514 [255]
502 515
503 516 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now