##// END OF EJS Templates
treemanifests: fix local clone...
Martin von Zweigbergk -
r28006:bb45190a default
parent child Browse files
Show More
@@ -1,552 +1,552 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 __future__ import absolute_import
9 9
10 10 import errno
11 11 import os
12 12 import stat
13 13
14 14 from .i18n import _
15 15 from . import (
16 16 error,
17 17 parsers,
18 18 scmutil,
19 19 util,
20 20 )
21 21
22 22 _sha = util.sha1
23 23
24 24 # This avoids a collision between a file named foo and a dir named
25 25 # foo.i or foo.d
26 26 def _encodedir(path):
27 27 '''
28 28 >>> _encodedir('data/foo.i')
29 29 'data/foo.i'
30 30 >>> _encodedir('data/foo.i/bla.i')
31 31 'data/foo.i.hg/bla.i'
32 32 >>> _encodedir('data/foo.i.hg/bla.i')
33 33 'data/foo.i.hg.hg/bla.i'
34 34 >>> _encodedir('data/foo.i\\ndata/foo.i/bla.i\\ndata/foo.i.hg/bla.i\\n')
35 35 'data/foo.i\\ndata/foo.i.hg/bla.i\\ndata/foo.i.hg.hg/bla.i\\n'
36 36 '''
37 37 return (path
38 38 .replace(".hg/", ".hg.hg/")
39 39 .replace(".i/", ".i.hg/")
40 40 .replace(".d/", ".d.hg/"))
41 41
42 42 encodedir = getattr(parsers, 'encodedir', _encodedir)
43 43
44 44 def decodedir(path):
45 45 '''
46 46 >>> decodedir('data/foo.i')
47 47 'data/foo.i'
48 48 >>> decodedir('data/foo.i.hg/bla.i')
49 49 'data/foo.i/bla.i'
50 50 >>> decodedir('data/foo.i.hg.hg/bla.i')
51 51 'data/foo.i.hg/bla.i'
52 52 '''
53 53 if ".hg/" not in path:
54 54 return path
55 55 return (path
56 56 .replace(".d.hg/", ".d/")
57 57 .replace(".i.hg/", ".i/")
58 58 .replace(".hg.hg/", ".hg/"))
59 59
60 60 def _buildencodefun():
61 61 '''
62 62 >>> enc, dec = _buildencodefun()
63 63
64 64 >>> enc('nothing/special.txt')
65 65 'nothing/special.txt'
66 66 >>> dec('nothing/special.txt')
67 67 'nothing/special.txt'
68 68
69 69 >>> enc('HELLO')
70 70 '_h_e_l_l_o'
71 71 >>> dec('_h_e_l_l_o')
72 72 'HELLO'
73 73
74 74 >>> enc('hello:world?')
75 75 'hello~3aworld~3f'
76 76 >>> dec('hello~3aworld~3f')
77 77 'hello:world?'
78 78
79 79 >>> enc('the\x07quick\xADshot')
80 80 'the~07quick~adshot'
81 81 >>> dec('the~07quick~adshot')
82 82 'the\\x07quick\\xadshot'
83 83 '''
84 84 e = '_'
85 85 winreserved = [ord(x) for x in '\\:*?"<>|']
86 86 cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
87 87 for x in (range(32) + range(126, 256) + winreserved):
88 88 cmap[chr(x)] = "~%02x" % x
89 89 for x in range(ord("A"), ord("Z") + 1) + [ord(e)]:
90 90 cmap[chr(x)] = e + chr(x).lower()
91 91 dmap = {}
92 92 for k, v in cmap.iteritems():
93 93 dmap[v] = k
94 94 def decode(s):
95 95 i = 0
96 96 while i < len(s):
97 97 for l in xrange(1, 4):
98 98 try:
99 99 yield dmap[s[i:i + l]]
100 100 i += l
101 101 break
102 102 except KeyError:
103 103 pass
104 104 else:
105 105 raise KeyError
106 106 return (lambda s: ''.join([cmap[c] for c in s]),
107 107 lambda s: ''.join(list(decode(s))))
108 108
109 109 _encodefname, _decodefname = _buildencodefun()
110 110
111 111 def encodefilename(s):
112 112 '''
113 113 >>> encodefilename('foo.i/bar.d/bla.hg/hi:world?/HELLO')
114 114 'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o'
115 115 '''
116 116 return _encodefname(encodedir(s))
117 117
118 118 def decodefilename(s):
119 119 '''
120 120 >>> decodefilename('foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o')
121 121 'foo.i/bar.d/bla.hg/hi:world?/HELLO'
122 122 '''
123 123 return decodedir(_decodefname(s))
124 124
125 125 def _buildlowerencodefun():
126 126 '''
127 127 >>> f = _buildlowerencodefun()
128 128 >>> f('nothing/special.txt')
129 129 'nothing/special.txt'
130 130 >>> f('HELLO')
131 131 'hello'
132 132 >>> f('hello:world?')
133 133 'hello~3aworld~3f'
134 134 >>> f('the\x07quick\xADshot')
135 135 'the~07quick~adshot'
136 136 '''
137 137 winreserved = [ord(x) for x in '\\:*?"<>|']
138 138 cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
139 139 for x in (range(32) + range(126, 256) + winreserved):
140 140 cmap[chr(x)] = "~%02x" % x
141 141 for x in range(ord("A"), ord("Z") + 1):
142 142 cmap[chr(x)] = chr(x).lower()
143 143 return lambda s: "".join([cmap[c] for c in s])
144 144
145 145 lowerencode = getattr(parsers, 'lowerencode', None) or _buildlowerencodefun()
146 146
147 147 # Windows reserved names: con, prn, aux, nul, com1..com9, lpt1..lpt9
148 148 _winres3 = ('aux', 'con', 'prn', 'nul') # length 3
149 149 _winres4 = ('com', 'lpt') # length 4 (with trailing 1..9)
150 150 def _auxencode(path, dotencode):
151 151 '''
152 152 Encodes filenames containing names reserved by Windows or which end in
153 153 period or space. Does not touch other single reserved characters c.
154 154 Specifically, c in '\\:*?"<>|' or ord(c) <= 31 are *not* encoded here.
155 155 Additionally encodes space or period at the beginning, if dotencode is
156 156 True. Parameter path is assumed to be all lowercase.
157 157 A segment only needs encoding if a reserved name appears as a
158 158 basename (e.g. "aux", "aux.foo"). A directory or file named "foo.aux"
159 159 doesn't need encoding.
160 160
161 161 >>> s = '.foo/aux.txt/txt.aux/con/prn/nul/foo.'
162 162 >>> _auxencode(s.split('/'), True)
163 163 ['~2efoo', 'au~78.txt', 'txt.aux', 'co~6e', 'pr~6e', 'nu~6c', 'foo~2e']
164 164 >>> s = '.com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.'
165 165 >>> _auxencode(s.split('/'), False)
166 166 ['.com1com2', 'lp~749.lpt4.lpt1', 'conprn', 'com0', 'lpt0', 'foo~2e']
167 167 >>> _auxencode(['foo. '], True)
168 168 ['foo.~20']
169 169 >>> _auxencode([' .foo'], True)
170 170 ['~20.foo']
171 171 '''
172 172 for i, n in enumerate(path):
173 173 if not n:
174 174 continue
175 175 if dotencode and n[0] in '. ':
176 176 n = "~%02x" % ord(n[0]) + n[1:]
177 177 path[i] = n
178 178 else:
179 179 l = n.find('.')
180 180 if l == -1:
181 181 l = len(n)
182 182 if ((l == 3 and n[:3] in _winres3) or
183 183 (l == 4 and n[3] <= '9' and n[3] >= '1'
184 184 and n[:3] in _winres4)):
185 185 # encode third letter ('aux' -> 'au~78')
186 186 ec = "~%02x" % ord(n[2])
187 187 n = n[0:2] + ec + n[3:]
188 188 path[i] = n
189 189 if n[-1] in '. ':
190 190 # encode last period or space ('foo...' -> 'foo..~2e')
191 191 path[i] = n[:-1] + "~%02x" % ord(n[-1])
192 192 return path
193 193
194 194 _maxstorepathlen = 120
195 195 _dirprefixlen = 8
196 196 _maxshortdirslen = 8 * (_dirprefixlen + 1) - 4
197 197
198 198 def _hashencode(path, dotencode):
199 199 digest = _sha(path).hexdigest()
200 200 le = lowerencode(path[5:]).split('/') # skips prefix 'data/' or 'meta/'
201 201 parts = _auxencode(le, dotencode)
202 202 basename = parts[-1]
203 203 _root, ext = os.path.splitext(basename)
204 204 sdirs = []
205 205 sdirslen = 0
206 206 for p in parts[:-1]:
207 207 d = p[:_dirprefixlen]
208 208 if d[-1] in '. ':
209 209 # Windows can't access dirs ending in period or space
210 210 d = d[:-1] + '_'
211 211 if sdirslen == 0:
212 212 t = len(d)
213 213 else:
214 214 t = sdirslen + 1 + len(d)
215 215 if t > _maxshortdirslen:
216 216 break
217 217 sdirs.append(d)
218 218 sdirslen = t
219 219 dirs = '/'.join(sdirs)
220 220 if len(dirs) > 0:
221 221 dirs += '/'
222 222 res = 'dh/' + dirs + digest + ext
223 223 spaceleft = _maxstorepathlen - len(res)
224 224 if spaceleft > 0:
225 225 filler = basename[:spaceleft]
226 226 res = 'dh/' + dirs + filler + digest + ext
227 227 return res
228 228
229 229 def _hybridencode(path, dotencode):
230 230 '''encodes path with a length limit
231 231
232 232 Encodes all paths that begin with 'data/', according to the following.
233 233
234 234 Default encoding (reversible):
235 235
236 236 Encodes all uppercase letters 'X' as '_x'. All reserved or illegal
237 237 characters are encoded as '~xx', where xx is the two digit hex code
238 238 of the character (see encodefilename).
239 239 Relevant path components consisting of Windows reserved filenames are
240 240 masked by encoding the third character ('aux' -> 'au~78', see _auxencode).
241 241
242 242 Hashed encoding (not reversible):
243 243
244 244 If the default-encoded path is longer than _maxstorepathlen, a
245 245 non-reversible hybrid hashing of the path is done instead.
246 246 This encoding uses up to _dirprefixlen characters of all directory
247 247 levels of the lowerencoded path, but not more levels than can fit into
248 248 _maxshortdirslen.
249 249 Then follows the filler followed by the sha digest of the full path.
250 250 The filler is the beginning of the basename of the lowerencoded path
251 251 (the basename is everything after the last path separator). The filler
252 252 is as long as possible, filling in characters from the basename until
253 253 the encoded path has _maxstorepathlen characters (or all chars of the
254 254 basename have been taken).
255 255 The extension (e.g. '.i' or '.d') is preserved.
256 256
257 257 The string 'data/' at the beginning is replaced with 'dh/', if the hashed
258 258 encoding was used.
259 259 '''
260 260 path = encodedir(path)
261 261 ef = _encodefname(path).split('/')
262 262 res = '/'.join(_auxencode(ef, dotencode))
263 263 if len(res) > _maxstorepathlen:
264 264 res = _hashencode(path, dotencode)
265 265 return res
266 266
267 267 def _pathencode(path):
268 268 de = encodedir(path)
269 269 if len(path) > _maxstorepathlen:
270 270 return _hashencode(de, True)
271 271 ef = _encodefname(de).split('/')
272 272 res = '/'.join(_auxencode(ef, True))
273 273 if len(res) > _maxstorepathlen:
274 274 return _hashencode(de, True)
275 275 return res
276 276
277 277 _pathencode = getattr(parsers, 'pathencode', _pathencode)
278 278
279 279 def _plainhybridencode(f):
280 280 return _hybridencode(f, False)
281 281
282 282 def _calcmode(vfs):
283 283 try:
284 284 # files in .hg/ will be created using this mode
285 285 mode = vfs.stat().st_mode
286 286 # avoid some useless chmods
287 287 if (0o777 & ~util.umask) == (0o777 & mode):
288 288 mode = None
289 289 except OSError:
290 290 mode = None
291 291 return mode
292 292
293 _data = ('data 00manifest.d 00manifest.i 00changelog.d 00changelog.i'
293 _data = ('data meta 00manifest.d 00manifest.i 00changelog.d 00changelog.i'
294 294 ' phaseroots obsstore')
295 295
296 296 class basicstore(object):
297 297 '''base class for local repository stores'''
298 298 def __init__(self, path, vfstype):
299 299 vfs = vfstype(path)
300 300 self.path = vfs.base
301 301 self.createmode = _calcmode(vfs)
302 302 vfs.createmode = self.createmode
303 303 self.rawvfs = vfs
304 304 self.vfs = scmutil.filtervfs(vfs, encodedir)
305 305 self.opener = self.vfs
306 306
307 307 def join(self, f):
308 308 return self.path + '/' + encodedir(f)
309 309
310 310 def _walk(self, relpath, recurse):
311 311 '''yields (unencoded, encoded, size)'''
312 312 path = self.path
313 313 if relpath:
314 314 path += '/' + relpath
315 315 striplen = len(self.path) + 1
316 316 l = []
317 317 if self.rawvfs.isdir(path):
318 318 visit = [path]
319 319 readdir = self.rawvfs.readdir
320 320 while visit:
321 321 p = visit.pop()
322 322 for f, kind, st in readdir(p, stat=True):
323 323 fp = p + '/' + f
324 324 if kind == stat.S_IFREG and f[-2:] in ('.d', '.i'):
325 325 n = util.pconvert(fp[striplen:])
326 326 l.append((decodedir(n), n, st.st_size))
327 327 elif kind == stat.S_IFDIR and recurse:
328 328 visit.append(fp)
329 329 l.sort()
330 330 return l
331 331
332 332 def datafiles(self):
333 333 return self._walk('data', True)
334 334
335 335 def topfiles(self):
336 336 # yield manifest before changelog
337 337 return reversed(self._walk('', False))
338 338
339 339 def walk(self):
340 340 '''yields (unencoded, encoded, size)'''
341 341 # yield data files first
342 342 for x in self.datafiles():
343 343 yield x
344 344 for x in self.topfiles():
345 345 yield x
346 346
347 347 def copylist(self):
348 348 return ['requires'] + _data.split()
349 349
350 350 def write(self, tr):
351 351 pass
352 352
353 353 def invalidatecaches(self):
354 354 pass
355 355
356 356 def markremoved(self, fn):
357 357 pass
358 358
359 359 def __contains__(self, path):
360 360 '''Checks if the store contains path'''
361 361 path = "/".join(("data", path))
362 362 # file?
363 363 if self.vfs.exists(path + ".i"):
364 364 return True
365 365 # dir?
366 366 if not path.endswith("/"):
367 367 path = path + "/"
368 368 return self.vfs.exists(path)
369 369
370 370 class encodedstore(basicstore):
371 371 def __init__(self, path, vfstype):
372 372 vfs = vfstype(path + '/store')
373 373 self.path = vfs.base
374 374 self.createmode = _calcmode(vfs)
375 375 vfs.createmode = self.createmode
376 376 self.rawvfs = vfs
377 377 self.vfs = scmutil.filtervfs(vfs, encodefilename)
378 378 self.opener = self.vfs
379 379
380 380 def datafiles(self):
381 381 for a, b, size in self._walk('data', True):
382 382 try:
383 383 a = decodefilename(a)
384 384 except KeyError:
385 385 a = None
386 386 yield a, b, size
387 387
388 388 def join(self, f):
389 389 return self.path + '/' + encodefilename(f)
390 390
391 391 def copylist(self):
392 392 return (['requires', '00changelog.i'] +
393 393 ['store/' + f for f in _data.split()])
394 394
395 395 class fncache(object):
396 396 # the filename used to be partially encoded
397 397 # hence the encodedir/decodedir dance
398 398 def __init__(self, vfs):
399 399 self.vfs = vfs
400 400 self.entries = None
401 401 self._dirty = False
402 402
403 403 def _load(self):
404 404 '''fill the entries from the fncache file'''
405 405 self._dirty = False
406 406 try:
407 407 fp = self.vfs('fncache', mode='rb')
408 408 except IOError:
409 409 # skip nonexistent file
410 410 self.entries = set()
411 411 return
412 412 self.entries = set(decodedir(fp.read()).splitlines())
413 413 if '' in self.entries:
414 414 fp.seek(0)
415 415 for n, line in enumerate(fp):
416 416 if not line.rstrip('\n'):
417 417 t = _('invalid entry in fncache, line %d') % (n + 1)
418 418 raise error.Abort(t)
419 419 fp.close()
420 420
421 421 def write(self, tr):
422 422 if self._dirty:
423 423 tr.addbackup('fncache')
424 424 fp = self.vfs('fncache', mode='wb', atomictemp=True)
425 425 if self.entries:
426 426 fp.write(encodedir('\n'.join(self.entries) + '\n'))
427 427 fp.close()
428 428 self._dirty = False
429 429
430 430 def add(self, fn):
431 431 if self.entries is None:
432 432 self._load()
433 433 if fn not in self.entries:
434 434 self._dirty = True
435 435 self.entries.add(fn)
436 436
437 437 def remove(self, fn):
438 438 if self.entries is None:
439 439 self._load()
440 440 try:
441 441 self.entries.remove(fn)
442 442 self._dirty = True
443 443 except KeyError:
444 444 pass
445 445
446 446 def __contains__(self, fn):
447 447 if self.entries is None:
448 448 self._load()
449 449 return fn in self.entries
450 450
451 451 def __iter__(self):
452 452 if self.entries is None:
453 453 self._load()
454 454 return iter(self.entries)
455 455
456 456 class _fncachevfs(scmutil.abstractvfs, scmutil.auditvfs):
457 457 def __init__(self, vfs, fnc, encode):
458 458 scmutil.auditvfs.__init__(self, vfs)
459 459 self.fncache = fnc
460 460 self.encode = encode
461 461
462 462 def __call__(self, path, mode='r', *args, **kw):
463 463 if mode not in ('r', 'rb') and path.startswith('data/'):
464 464 self.fncache.add(path)
465 465 return self.vfs(self.encode(path), mode, *args, **kw)
466 466
467 467 def join(self, path):
468 468 if path:
469 469 return self.vfs.join(self.encode(path))
470 470 else:
471 471 return self.vfs.join(path)
472 472
473 473 class fncachestore(basicstore):
474 474 def __init__(self, path, vfstype, dotencode):
475 475 if dotencode:
476 476 encode = _pathencode
477 477 else:
478 478 encode = _plainhybridencode
479 479 self.encode = encode
480 480 vfs = vfstype(path + '/store')
481 481 self.path = vfs.base
482 482 self.pathsep = self.path + '/'
483 483 self.createmode = _calcmode(vfs)
484 484 vfs.createmode = self.createmode
485 485 self.rawvfs = vfs
486 486 fnc = fncache(vfs)
487 487 self.fncache = fnc
488 488 self.vfs = _fncachevfs(vfs, fnc, encode)
489 489 self.opener = self.vfs
490 490
491 491 def join(self, f):
492 492 return self.pathsep + self.encode(f)
493 493
494 494 def getsize(self, path):
495 495 return self.rawvfs.stat(path).st_size
496 496
497 497 def datafiles(self):
498 498 for f in sorted(self.fncache):
499 499 ef = self.encode(f)
500 500 try:
501 501 yield f, ef, self.getsize(ef)
502 502 except OSError as err:
503 503 if err.errno != errno.ENOENT:
504 504 raise
505 505
506 506 def copylist(self):
507 d = ('data dh fncache phaseroots obsstore'
507 d = ('data meta dh fncache phaseroots obsstore'
508 508 ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
509 509 return (['requires', '00changelog.i'] +
510 510 ['store/' + f for f in d.split()])
511 511
512 512 def write(self, tr):
513 513 self.fncache.write(tr)
514 514
515 515 def invalidatecaches(self):
516 516 self.fncache.entries = None
517 517
518 518 def markremoved(self, fn):
519 519 self.fncache.remove(fn)
520 520
521 521 def _exists(self, f):
522 522 ef = self.encode(f)
523 523 try:
524 524 self.getsize(ef)
525 525 return True
526 526 except OSError as err:
527 527 if err.errno != errno.ENOENT:
528 528 raise
529 529 # nonexistent entry
530 530 return False
531 531
532 532 def __contains__(self, path):
533 533 '''Checks if the store contains path'''
534 534 path = "/".join(("data", path))
535 535 # check for files (exact match)
536 536 e = path + '.i'
537 537 if e in self.fncache and self._exists(e):
538 538 return True
539 539 # now check for directories (prefix match)
540 540 if not path.endswith('/'):
541 541 path += '/'
542 542 for e in self.fncache:
543 543 if e.startswith(path) and self._exists(e):
544 544 return True
545 545 return False
546 546
547 547 def store(requirements, path, vfstype):
548 548 if 'store' in requirements:
549 549 if 'fncache' in requirements:
550 550 return fncachestore(path, vfstype, 'dotencode' in requirements)
551 551 return encodedstore(path, vfstype)
552 552 return basicstore(path, vfstype)
@@ -1,515 +1,568 b''
1 1 $ cat << EOF >> $HGRCPATH
2 2 > [format]
3 3 > usegeneraldelta=yes
4 4 > [ui]
5 5 > ssh=python "$TESTDIR/dummyssh"
6 6 > EOF
7 7
8 8 Set up repo
9 9
10 10 $ hg --config experimental.treemanifest=True init repo
11 11 $ cd repo
12 12
13 13 Requirements get set on init
14 14
15 15 $ grep treemanifest .hg/requires
16 16 treemanifest
17 17
18 18 Without directories, looks like any other repo
19 19
20 20 $ echo 0 > a
21 21 $ echo 0 > b
22 22 $ hg ci -Aqm initial
23 23 $ hg debugdata -m 0
24 24 a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
25 25 b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
26 26
27 27 Submanifest is stored in separate revlog
28 28
29 29 $ mkdir dir1
30 30 $ echo 1 > dir1/a
31 31 $ echo 1 > dir1/b
32 32 $ echo 1 > e
33 33 $ hg ci -Aqm 'add dir1'
34 34 $ hg debugdata -m 1
35 35 a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
36 36 b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
37 37 dir1\x008b3ffd73f901e83304c83d33132c8e774ceac44et (esc)
38 38 e\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
39 39 $ hg debugdata --dir dir1 0
40 40 a\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
41 41 b\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
42 42
43 43 Can add nested directories
44 44
45 45 $ mkdir dir1/dir1
46 46 $ echo 2 > dir1/dir1/a
47 47 $ echo 2 > dir1/dir1/b
48 48 $ mkdir dir1/dir2
49 49 $ echo 2 > dir1/dir2/a
50 50 $ echo 2 > dir1/dir2/b
51 51 $ hg ci -Aqm 'add dir1/dir1'
52 52 $ hg files -r .
53 53 a
54 54 b
55 55 dir1/a (glob)
56 56 dir1/b (glob)
57 57 dir1/dir1/a (glob)
58 58 dir1/dir1/b (glob)
59 59 dir1/dir2/a (glob)
60 60 dir1/dir2/b (glob)
61 61 e
62 62
63 63 Revision is not created for unchanged directory
64 64
65 65 $ mkdir dir2
66 66 $ echo 3 > dir2/a
67 67 $ hg add dir2
68 68 adding dir2/a (glob)
69 69 $ hg debugindex --dir dir1 > before
70 70 $ hg ci -qm 'add dir2'
71 71 $ hg debugindex --dir dir1 > after
72 72 $ diff before after
73 73 $ rm before after
74 74
75 75 Removing directory does not create an revlog entry
76 76
77 77 $ hg rm dir1/dir1
78 78 removing dir1/dir1/a (glob)
79 79 removing dir1/dir1/b (glob)
80 80 $ hg debugindex --dir dir1/dir1 > before
81 81 $ hg ci -qm 'remove dir1/dir1'
82 82 $ hg debugindex --dir dir1/dir1 > after
83 83 $ diff before after
84 84 $ rm before after
85 85
86 86 Check that hg files (calls treemanifest.walk()) works
87 87 without loading all directory revlogs
88 88
89 89 $ hg co 'desc("add dir2")'
90 90 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
91 91 $ mv .hg/store/meta/dir2 .hg/store/meta/dir2-backup
92 92 $ hg files -r . dir1
93 93 dir1/a (glob)
94 94 dir1/b (glob)
95 95 dir1/dir1/a (glob)
96 96 dir1/dir1/b (glob)
97 97 dir1/dir2/a (glob)
98 98 dir1/dir2/b (glob)
99 99
100 100 Check that status between revisions works (calls treemanifest.matches())
101 101 without loading all directory revlogs
102 102
103 103 $ hg status --rev 'desc("add dir1")' --rev . dir1
104 104 A dir1/dir1/a
105 105 A dir1/dir1/b
106 106 A dir1/dir2/a
107 107 A dir1/dir2/b
108 108 $ mv .hg/store/meta/dir2-backup .hg/store/meta/dir2
109 109
110 110 Merge creates 2-parent revision of directory revlog
111 111
112 112 $ echo 5 > dir1/a
113 113 $ hg ci -Aqm 'modify dir1/a'
114 114 $ hg co '.^'
115 115 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
116 116 $ echo 6 > dir1/b
117 117 $ hg ci -Aqm 'modify dir1/b'
118 118 $ hg merge 'desc("modify dir1/a")'
119 119 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
120 120 (branch merge, don't forget to commit)
121 121 $ hg ci -m 'conflict-free merge involving dir1/'
122 122 $ cat dir1/a
123 123 5
124 124 $ cat dir1/b
125 125 6
126 126 $ hg debugindex --dir dir1
127 127 rev offset length delta linkrev nodeid p1 p2
128 128 0 0 54 -1 1 8b3ffd73f901 000000000000 000000000000
129 129 1 54 68 0 2 68e9d057c5a8 8b3ffd73f901 000000000000
130 130 2 122 12 1 4 4698198d2624 68e9d057c5a8 000000000000
131 131 3 134 55 1 5 44844058ccce 68e9d057c5a8 000000000000
132 132 4 189 55 1 6 bf3d9b744927 68e9d057c5a8 000000000000
133 133 5 244 55 4 7 dde7c0af2a03 bf3d9b744927 44844058ccce
134 134
135 135 Merge keeping directory from parent 1 does not create revlog entry. (Note that
136 136 dir1's manifest does change, but only because dir1/a's filelog changes.)
137 137
138 138 $ hg co 'desc("add dir2")'
139 139 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
140 140 $ echo 8 > dir2/a
141 141 $ hg ci -m 'modify dir2/a'
142 142 created new head
143 143
144 144 $ hg debugindex --dir dir2 > before
145 145 $ hg merge 'desc("modify dir1/a")'
146 146 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
147 147 (branch merge, don't forget to commit)
148 148 $ hg revert -r 'desc("modify dir2/a")' .
149 149 reverting dir1/a (glob)
150 150 $ hg ci -m 'merge, keeping parent 1'
151 151 $ hg debugindex --dir dir2 > after
152 152 $ diff before after
153 153 $ rm before after
154 154
155 155 Merge keeping directory from parent 2 does not create revlog entry. (Note that
156 156 dir2's manifest does change, but only because dir2/a's filelog changes.)
157 157
158 158 $ hg co 'desc("modify dir2/a")'
159 159 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
160 160 $ hg debugindex --dir dir1 > before
161 161 $ hg merge 'desc("modify dir1/a")'
162 162 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
163 163 (branch merge, don't forget to commit)
164 164 $ hg revert -r 'desc("modify dir1/a")' .
165 165 reverting dir2/a (glob)
166 166 $ hg ci -m 'merge, keeping parent 2'
167 167 created new head
168 168 $ hg debugindex --dir dir1 > after
169 169 $ diff before after
170 170 $ rm before after
171 171
172 172 Create flat source repo for tests with mixed flat/tree manifests
173 173
174 174 $ cd ..
175 175 $ hg init repo-flat
176 176 $ cd repo-flat
177 177
178 178 Create a few commits with flat manifest
179 179
180 180 $ echo 0 > a
181 181 $ echo 0 > b
182 182 $ echo 0 > e
183 183 $ for d in dir1 dir1/dir1 dir1/dir2 dir2
184 184 > do
185 185 > mkdir $d
186 186 > echo 0 > $d/a
187 187 > echo 0 > $d/b
188 188 > done
189 189 $ hg ci -Aqm initial
190 190
191 191 $ echo 1 > a
192 192 $ echo 1 > dir1/a
193 193 $ echo 1 > dir1/dir1/a
194 194 $ hg ci -Aqm 'modify on branch 1'
195 195
196 196 $ hg co 0
197 197 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
198 198 $ echo 2 > b
199 199 $ echo 2 > dir1/b
200 200 $ echo 2 > dir1/dir1/b
201 201 $ hg ci -Aqm 'modify on branch 2'
202 202
203 203 $ hg merge 1
204 204 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
205 205 (branch merge, don't forget to commit)
206 206 $ hg ci -m 'merge of flat manifests to new flat manifest'
207 207
208 208 $ hg serve -p $HGPORT -d --pid-file=hg.pid --errorlog=errors.log
209 209 $ cat hg.pid >> $DAEMON_PIDS
210 210
211 211 Create clone with tree manifests enabled
212 212
213 213 $ cd ..
214 214 $ hg clone --config experimental.treemanifest=1 \
215 215 > http://localhost:$HGPORT repo-mixed -r 1
216 216 adding changesets
217 217 adding manifests
218 218 adding file changes
219 219 added 2 changesets with 14 changes to 11 files
220 220 updating to branch default
221 221 11 files updated, 0 files merged, 0 files removed, 0 files unresolved
222 222 $ cd repo-mixed
223 223 $ test -d .hg/store/meta
224 224 [1]
225 225 $ grep treemanifest .hg/requires
226 226 treemanifest
227 227
228 228 Should be possible to push updates from flat to tree manifest repo
229 229
230 230 $ hg -R ../repo-flat push ssh://user@dummy/repo-mixed
231 231 pushing to ssh://user@dummy/repo-mixed
232 232 searching for changes
233 233 remote: adding changesets
234 234 remote: adding manifests
235 235 remote: adding file changes
236 236 remote: added 2 changesets with 3 changes to 3 files
237 237
238 238 Commit should store revlog per directory
239 239
240 240 $ hg co 1
241 241 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
242 242 $ echo 3 > a
243 243 $ echo 3 > dir1/a
244 244 $ echo 3 > dir1/dir1/a
245 245 $ hg ci -m 'first tree'
246 246 created new head
247 247 $ find .hg/store/meta | sort
248 248 .hg/store/meta
249 249 .hg/store/meta/dir1
250 250 .hg/store/meta/dir1/00manifest.i
251 251 .hg/store/meta/dir1/dir1
252 252 .hg/store/meta/dir1/dir1/00manifest.i
253 253 .hg/store/meta/dir1/dir2
254 254 .hg/store/meta/dir1/dir2/00manifest.i
255 255 .hg/store/meta/dir2
256 256 .hg/store/meta/dir2/00manifest.i
257 257
258 258 Merge of two trees
259 259
260 260 $ hg co 2
261 261 6 files updated, 0 files merged, 0 files removed, 0 files unresolved
262 262 $ hg merge 1
263 263 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
264 264 (branch merge, don't forget to commit)
265 265 $ hg ci -m 'merge of flat manifests to new tree manifest'
266 266 created new head
267 267 $ hg diff -r 3
268 268
269 269 Parent of tree root manifest should be flat manifest, and two for merge
270 270
271 271 $ hg debugindex -m
272 272 rev offset length delta linkrev nodeid p1 p2
273 273 0 0 80 -1 0 40536115ed9e 000000000000 000000000000
274 274 1 80 83 0 1 f3376063c255 40536115ed9e 000000000000
275 275 2 163 89 0 2 5d9b9da231a2 40536115ed9e 000000000000
276 276 3 252 83 2 3 d17d663cbd8a 5d9b9da231a2 f3376063c255
277 277 4 335 124 1 4 51e32a8c60ee f3376063c255 000000000000
278 278 5 459 126 2 5 cc5baa78b230 5d9b9da231a2 f3376063c255
279 279
280 280
281 281 Status across flat/tree boundary should work
282 282
283 283 $ hg status --rev '.^' --rev .
284 284 M a
285 285 M dir1/a
286 286 M dir1/dir1/a
287 287
288 288
289 289 Turning off treemanifest config has no effect
290 290
291 291 $ hg debugindex --dir dir1
292 292 rev offset length delta linkrev nodeid p1 p2
293 293 0 0 127 -1 4 064927a0648a 000000000000 000000000000
294 294 1 127 111 0 5 25ecb8cb8618 000000000000 000000000000
295 295 $ echo 2 > dir1/a
296 296 $ hg --config experimental.treemanifest=False ci -qm 'modify dir1/a'
297 297 $ hg debugindex --dir dir1
298 298 rev offset length delta linkrev nodeid p1 p2
299 299 0 0 127 -1 4 064927a0648a 000000000000 000000000000
300 300 1 127 111 0 5 25ecb8cb8618 000000000000 000000000000
301 301 2 238 55 1 6 5b16163a30c6 25ecb8cb8618 000000000000
302 302
303 303 Stripping and recovering changes should work
304 304
305 305 $ hg st --change tip
306 306 M dir1/a
307 307 $ hg --config extensions.strip= strip tip
308 308 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
309 309 saved backup bundle to $TESTTMP/repo-mixed/.hg/strip-backup/51cfd7b1e13b-78a2f3ed-backup.hg (glob)
310 310 $ hg unbundle -q .hg/strip-backup/*
311 311 $ hg st --change tip
312 312 M dir1/a
313 313
314 314 Shelving and unshelving should work
315 315
316 316 $ echo foo >> dir1/a
317 317 $ hg --config extensions.shelve= shelve
318 318 shelved as default
319 319 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
320 320 $ hg --config extensions.shelve= unshelve
321 321 unshelving change 'default'
322 322 $ hg diff --nodates
323 323 diff -r 708a273da119 dir1/a
324 324 --- a/dir1/a
325 325 +++ b/dir1/a
326 326 @@ -1,1 +1,2 @@
327 327 1
328 328 +foo
329 329
330 330 Pushing from treemanifest repo to an empty repo makes that a treemanifest repo
331 331
332 332 $ cd ..
333 333 $ hg init empty-repo
334 334 $ cat << EOF >> empty-repo/.hg/hgrc
335 335 > [experimental]
336 336 > changegroup3=yes
337 337 > EOF
338 338 $ grep treemanifest empty-repo/.hg/requires
339 339 [1]
340 340 $ hg push -R repo -r 0 empty-repo
341 341 pushing to empty-repo
342 342 searching for changes
343 343 adding changesets
344 344 adding manifests
345 345 adding file changes
346 346 added 1 changesets with 2 changes to 2 files
347 347 $ grep treemanifest empty-repo/.hg/requires
348 348 treemanifest
349 349
350 350 Pushing to an empty repo works
351 351
352 352 $ hg --config experimental.treemanifest=1 init clone
353 353 $ grep treemanifest clone/.hg/requires
354 354 treemanifest
355 355 $ hg push -R repo clone
356 356 pushing to clone
357 357 searching for changes
358 358 adding changesets
359 359 adding manifests
360 360 adding file changes
361 361 added 11 changesets with 15 changes to 10 files (+3 heads)
362 362 $ grep treemanifest clone/.hg/requires
363 363 treemanifest
364 364
365 365 Create deeper repo with tree manifests.
366 366
367 367 $ hg --config experimental.treemanifest=True init deeprepo
368 368 $ cd deeprepo
369 369
370 370 $ mkdir a
371 371 $ mkdir b
372 372 $ mkdir b/bar
373 373 $ mkdir b/bar/orange
374 374 $ mkdir b/bar/orange/fly
375 375 $ mkdir b/foo
376 376 $ mkdir b/foo/apple
377 377 $ mkdir b/foo/apple/bees
378 378
379 379 $ touch a/one.txt
380 380 $ touch a/two.txt
381 381 $ touch b/bar/fruits.txt
382 382 $ touch b/bar/orange/fly/gnat.py
383 383 $ touch b/bar/orange/fly/housefly.txt
384 384 $ touch b/foo/apple/bees/flower.py
385 385 $ touch c.txt
386 386 $ touch d.py
387 387
388 388 $ hg ci -Aqm 'initial'
389 389
390 390 We'll see that visitdir works by removing some treemanifest revlogs and running
391 391 the files command with various parameters.
392 392
393 393 Test files from the root.
394 394
395 395 $ hg files -r .
396 396 a/one.txt (glob)
397 397 a/two.txt (glob)
398 398 b/bar/fruits.txt (glob)
399 399 b/bar/orange/fly/gnat.py (glob)
400 400 b/bar/orange/fly/housefly.txt (glob)
401 401 b/foo/apple/bees/flower.py (glob)
402 402 c.txt
403 403 d.py
404 404
405 405 Excludes with a glob should not exclude everything from the glob's root
406 406
407 407 $ hg files -r . -X 'b/fo?' b
408 408 b/bar/fruits.txt (glob)
409 409 b/bar/orange/fly/gnat.py (glob)
410 410 b/bar/orange/fly/housefly.txt (glob)
411 411 $ cp -r .hg/store .hg/store-copy
412 412
413 413 Test files for a subdirectory.
414 414
415 415 $ rm -r .hg/store/meta/a
416 416 $ hg files -r . b
417 417 b/bar/fruits.txt (glob)
418 418 b/bar/orange/fly/gnat.py (glob)
419 419 b/bar/orange/fly/housefly.txt (glob)
420 420 b/foo/apple/bees/flower.py (glob)
421 421 $ cp -rT .hg/store-copy .hg/store
422 422
423 423 Test files with just includes and excludes.
424 424
425 425 $ rm -r .hg/store/meta/a
426 426 $ rm -r .hg/store/meta/b/bar/orange/fly
427 427 $ rm -r .hg/store/meta/b/foo/apple/bees
428 428 $ hg files -r . -I path:b/bar -X path:b/bar/orange/fly -I path:b/foo -X path:b/foo/apple/bees
429 429 b/bar/fruits.txt (glob)
430 430 $ cp -rT .hg/store-copy .hg/store
431 431
432 432 Test files for a subdirectory, excluding a directory within it.
433 433
434 434 $ rm -r .hg/store/meta/a
435 435 $ rm -r .hg/store/meta/b/foo
436 436 $ hg files -r . -X path:b/foo b
437 437 b/bar/fruits.txt (glob)
438 438 b/bar/orange/fly/gnat.py (glob)
439 439 b/bar/orange/fly/housefly.txt (glob)
440 440 $ cp -rT .hg/store-copy .hg/store
441 441
442 442 Test files for a sub directory, including only a directory within it, and
443 443 including an unrelated directory.
444 444
445 445 $ rm -r .hg/store/meta/a
446 446 $ rm -r .hg/store/meta/b/foo
447 447 $ hg files -r . -I path:b/bar/orange -I path:a b
448 448 b/bar/orange/fly/gnat.py (glob)
449 449 b/bar/orange/fly/housefly.txt (glob)
450 450 $ cp -rT .hg/store-copy .hg/store
451 451
452 452 Test files for a pattern, including a directory, and excluding a directory
453 453 within that.
454 454
455 455 $ rm -r .hg/store/meta/a
456 456 $ rm -r .hg/store/meta/b/foo
457 457 $ rm -r .hg/store/meta/b/bar/orange
458 458 $ hg files -r . glob:**.txt -I path:b/bar -X path:b/bar/orange
459 459 b/bar/fruits.txt (glob)
460 460 $ cp -rT .hg/store-copy .hg/store
461 461
462 462 Add some more changes to the deep repo
463 463 $ echo narf >> b/bar/fruits.txt
464 464 $ hg ci -m narf
465 465 $ echo troz >> b/bar/orange/fly/gnat.py
466 466 $ hg ci -m troz
467 467
468 468 Test cloning a treemanifest repo over http.
469 469 $ hg serve -p $HGPORT2 -d --pid-file=hg.pid --errorlog=errors.log
470 470 $ cat hg.pid >> $DAEMON_PIDS
471 471 $ cd ..
472 472 We can clone even with the knob turned off and we'll get a treemanifest repo.
473 473 $ hg clone --config experimental.treemanifest=False \
474 474 > --config experimental.changegroup3=True \
475 475 > http://localhost:$HGPORT2 deepclone
476 476 requesting all changes
477 477 adding changesets
478 478 adding manifests
479 479 adding file changes
480 480 added 3 changesets with 10 changes to 8 files
481 481 updating to branch default
482 482 8 files updated, 0 files merged, 0 files removed, 0 files unresolved
483 483 No server errors.
484 484 $ cat deeprepo/errors.log
485 485 requires got updated to include treemanifest
486 486 $ cat deepclone/.hg/requires | grep treemanifest
487 487 treemanifest
488 488 Tree manifest revlogs exist.
489 489 $ find deepclone/.hg/store/meta | sort
490 490 deepclone/.hg/store/meta
491 491 deepclone/.hg/store/meta/a
492 492 deepclone/.hg/store/meta/a/00manifest.i
493 493 deepclone/.hg/store/meta/b
494 494 deepclone/.hg/store/meta/b/00manifest.i
495 495 deepclone/.hg/store/meta/b/bar
496 496 deepclone/.hg/store/meta/b/bar/00manifest.i
497 497 deepclone/.hg/store/meta/b/bar/orange
498 498 deepclone/.hg/store/meta/b/bar/orange/00manifest.i
499 499 deepclone/.hg/store/meta/b/bar/orange/fly
500 500 deepclone/.hg/store/meta/b/bar/orange/fly/00manifest.i
501 501 deepclone/.hg/store/meta/b/foo
502 502 deepclone/.hg/store/meta/b/foo/00manifest.i
503 503 deepclone/.hg/store/meta/b/foo/apple
504 504 deepclone/.hg/store/meta/b/foo/apple/00manifest.i
505 505 deepclone/.hg/store/meta/b/foo/apple/bees
506 506 deepclone/.hg/store/meta/b/foo/apple/bees/00manifest.i
507 507 Verify passes.
508 508 $ cd deepclone
509 509 $ hg verify
510 510 checking changesets
511 511 checking manifests
512 512 crosschecking files in changesets and manifests
513 513 checking files
514 514 8 files, 3 changesets, 10 total revisions
515 515 $ cd ..
516
517 Create clones using old repo formats to use in later tests
518 $ hg clone --config format.usestore=False \
519 > --config experimental.changegroup3=True \
520 > http://localhost:$HGPORT2 deeprepo-basicstore
521 requesting all changes
522 adding changesets
523 adding manifests
524 adding file changes
525 added 3 changesets with 10 changes to 8 files
526 updating to branch default
527 8 files updated, 0 files merged, 0 files removed, 0 files unresolved
528 $ grep store deeprepo-basicstore/.hg/requires
529 [1]
530 $ hg clone --config format.usefncache=False \
531 > --config experimental.changegroup3=True \
532 > http://localhost:$HGPORT2 deeprepo-encodedstore
533 requesting all changes
534 adding changesets
535 adding manifests
536 adding file changes
537 added 3 changesets with 10 changes to 8 files
538 updating to branch default
539 8 files updated, 0 files merged, 0 files removed, 0 files unresolved
540 $ grep fncache deeprepo-encodedstore/.hg/requires
541 [1]
542
543 Local clone with basicstore
544 $ hg clone -U deeprepo-basicstore local-clone-basicstore
545 $ hg -R local-clone-basicstore verify
546 checking changesets
547 checking manifests
548 crosschecking files in changesets and manifests
549 checking files
550 8 files, 3 changesets, 10 total revisions
551
552 Local clone with encodedstore
553 $ hg clone -U deeprepo-encodedstore local-clone-encodedstore
554 $ hg -R local-clone-encodedstore verify
555 checking changesets
556 checking manifests
557 crosschecking files in changesets and manifests
558 checking files
559 8 files, 3 changesets, 10 total revisions
560
561 Local clone with fncachestore
562 $ hg clone -U deeprepo local-clone-fncachestore
563 $ hg -R local-clone-fncachestore verify
564 checking changesets
565 checking manifests
566 crosschecking files in changesets and manifests
567 checking files
568 8 files, 3 changesets, 10 total revisions
General Comments 0
You need to be logged in to leave comments. Login now