##// END OF EJS Templates
verify: check directory manifests...
Martin von Zweigbergk -
r28203:7297e9e1 default
parent child Browse files
Show More
@@ -1,1052 +1,1072 b''
1 1 # manifest.py - manifest revision class for mercurial
2 2 #
3 3 # Copyright 2005-2007 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 array
11 11 import heapq
12 12 import os
13 13 import struct
14 14
15 15 from .i18n import _
16 16 from . import (
17 17 error,
18 18 mdiff,
19 19 parsers,
20 20 revlog,
21 21 util,
22 22 )
23 23
24 24 propertycache = util.propertycache
25 25
26 26 def _parsev1(data):
27 27 # This method does a little bit of excessive-looking
28 28 # precondition checking. This is so that the behavior of this
29 29 # class exactly matches its C counterpart to try and help
30 30 # prevent surprise breakage for anyone that develops against
31 31 # the pure version.
32 32 if data and data[-1] != '\n':
33 33 raise ValueError('Manifest did not end in a newline.')
34 34 prev = None
35 35 for l in data.splitlines():
36 36 if prev is not None and prev > l:
37 37 raise ValueError('Manifest lines not in sorted order.')
38 38 prev = l
39 39 f, n = l.split('\0')
40 40 if len(n) > 40:
41 41 yield f, revlog.bin(n[:40]), n[40:]
42 42 else:
43 43 yield f, revlog.bin(n), ''
44 44
45 45 def _parsev2(data):
46 46 metadataend = data.find('\n')
47 47 # Just ignore metadata for now
48 48 pos = metadataend + 1
49 49 prevf = ''
50 50 while pos < len(data):
51 51 end = data.find('\n', pos + 1) # +1 to skip stem length byte
52 52 if end == -1:
53 53 raise ValueError('Manifest ended with incomplete file entry.')
54 54 stemlen = ord(data[pos])
55 55 items = data[pos + 1:end].split('\0')
56 56 f = prevf[:stemlen] + items[0]
57 57 if prevf > f:
58 58 raise ValueError('Manifest entries not in sorted order.')
59 59 fl = items[1]
60 60 # Just ignore metadata (items[2:] for now)
61 61 n = data[end + 1:end + 21]
62 62 yield f, n, fl
63 63 pos = end + 22
64 64 prevf = f
65 65
66 66 def _parse(data):
67 67 """Generates (path, node, flags) tuples from a manifest text"""
68 68 if data.startswith('\0'):
69 69 return iter(_parsev2(data))
70 70 else:
71 71 return iter(_parsev1(data))
72 72
73 73 def _text(it, usemanifestv2):
74 74 """Given an iterator over (path, node, flags) tuples, returns a manifest
75 75 text"""
76 76 if usemanifestv2:
77 77 return _textv2(it)
78 78 else:
79 79 return _textv1(it)
80 80
81 81 def _textv1(it):
82 82 files = []
83 83 lines = []
84 84 _hex = revlog.hex
85 85 for f, n, fl in it:
86 86 files.append(f)
87 87 # if this is changed to support newlines in filenames,
88 88 # be sure to check the templates/ dir again (especially *-raw.tmpl)
89 89 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
90 90
91 91 _checkforbidden(files)
92 92 return ''.join(lines)
93 93
94 94 def _textv2(it):
95 95 files = []
96 96 lines = ['\0\n']
97 97 prevf = ''
98 98 for f, n, fl in it:
99 99 files.append(f)
100 100 stem = os.path.commonprefix([prevf, f])
101 101 stemlen = min(len(stem), 255)
102 102 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
103 103 prevf = f
104 104 _checkforbidden(files)
105 105 return ''.join(lines)
106 106
107 107 class _lazymanifest(dict):
108 108 """This is the pure implementation of lazymanifest.
109 109
110 110 It has not been optimized *at all* and is not lazy.
111 111 """
112 112
113 113 def __init__(self, data):
114 114 dict.__init__(self)
115 115 for f, n, fl in _parse(data):
116 116 self[f] = n, fl
117 117
118 118 def __setitem__(self, k, v):
119 119 node, flag = v
120 120 assert node is not None
121 121 if len(node) > 21:
122 122 node = node[:21] # match c implementation behavior
123 123 dict.__setitem__(self, k, (node, flag))
124 124
125 125 def __iter__(self):
126 126 return iter(sorted(dict.keys(self)))
127 127
128 128 def iterkeys(self):
129 129 return iter(sorted(dict.keys(self)))
130 130
131 131 def iterentries(self):
132 132 return ((f, e[0], e[1]) for f, e in sorted(self.iteritems()))
133 133
134 134 def copy(self):
135 135 c = _lazymanifest('')
136 136 c.update(self)
137 137 return c
138 138
139 139 def diff(self, m2, clean=False):
140 140 '''Finds changes between the current manifest and m2.'''
141 141 diff = {}
142 142
143 143 for fn, e1 in self.iteritems():
144 144 if fn not in m2:
145 145 diff[fn] = e1, (None, '')
146 146 else:
147 147 e2 = m2[fn]
148 148 if e1 != e2:
149 149 diff[fn] = e1, e2
150 150 elif clean:
151 151 diff[fn] = None
152 152
153 153 for fn, e2 in m2.iteritems():
154 154 if fn not in self:
155 155 diff[fn] = (None, ''), e2
156 156
157 157 return diff
158 158
159 159 def filtercopy(self, filterfn):
160 160 c = _lazymanifest('')
161 161 for f, n, fl in self.iterentries():
162 162 if filterfn(f):
163 163 c[f] = n, fl
164 164 return c
165 165
166 166 def text(self):
167 167 """Get the full data of this manifest as a bytestring."""
168 168 return _textv1(self.iterentries())
169 169
170 170 try:
171 171 _lazymanifest = parsers.lazymanifest
172 172 except AttributeError:
173 173 pass
174 174
175 175 class manifestdict(object):
176 176 def __init__(self, data=''):
177 177 if data.startswith('\0'):
178 178 #_lazymanifest can not parse v2
179 179 self._lm = _lazymanifest('')
180 180 for f, n, fl in _parsev2(data):
181 181 self._lm[f] = n, fl
182 182 else:
183 183 self._lm = _lazymanifest(data)
184 184
185 185 def __getitem__(self, key):
186 186 return self._lm[key][0]
187 187
188 188 def find(self, key):
189 189 return self._lm[key]
190 190
191 191 def __len__(self):
192 192 return len(self._lm)
193 193
194 194 def __setitem__(self, key, node):
195 195 self._lm[key] = node, self.flags(key, '')
196 196
197 197 def __contains__(self, key):
198 198 return key in self._lm
199 199
200 200 def __delitem__(self, key):
201 201 del self._lm[key]
202 202
203 203 def __iter__(self):
204 204 return self._lm.__iter__()
205 205
206 206 def iterkeys(self):
207 207 return self._lm.iterkeys()
208 208
209 209 def keys(self):
210 210 return list(self.iterkeys())
211 211
212 212 def filesnotin(self, m2):
213 213 '''Set of files in this manifest that are not in the other'''
214 214 files = set(self)
215 215 files.difference_update(m2)
216 216 return files
217 217
218 218 @propertycache
219 219 def _dirs(self):
220 220 return util.dirs(self)
221 221
222 222 def dirs(self):
223 223 return self._dirs
224 224
225 225 def hasdir(self, dir):
226 226 return dir in self._dirs
227 227
228 228 def _filesfastpath(self, match):
229 229 '''Checks whether we can correctly and quickly iterate over matcher
230 230 files instead of over manifest files.'''
231 231 files = match.files()
232 232 return (len(files) < 100 and (match.isexact() or
233 233 (match.prefix() and all(fn in self for fn in files))))
234 234
235 235 def walk(self, match):
236 236 '''Generates matching file names.
237 237
238 238 Equivalent to manifest.matches(match).iterkeys(), but without creating
239 239 an entirely new manifest.
240 240
241 241 It also reports nonexistent files by marking them bad with match.bad().
242 242 '''
243 243 if match.always():
244 244 for f in iter(self):
245 245 yield f
246 246 return
247 247
248 248 fset = set(match.files())
249 249
250 250 # avoid the entire walk if we're only looking for specific files
251 251 if self._filesfastpath(match):
252 252 for fn in sorted(fset):
253 253 yield fn
254 254 return
255 255
256 256 for fn in self:
257 257 if fn in fset:
258 258 # specified pattern is the exact name
259 259 fset.remove(fn)
260 260 if match(fn):
261 261 yield fn
262 262
263 263 # for dirstate.walk, files=['.'] means "walk the whole tree".
264 264 # follow that here, too
265 265 fset.discard('.')
266 266
267 267 for fn in sorted(fset):
268 268 if not self.hasdir(fn):
269 269 match.bad(fn, None)
270 270
271 271 def matches(self, match):
272 272 '''generate a new manifest filtered by the match argument'''
273 273 if match.always():
274 274 return self.copy()
275 275
276 276 if self._filesfastpath(match):
277 277 m = manifestdict()
278 278 lm = self._lm
279 279 for fn in match.files():
280 280 if fn in lm:
281 281 m._lm[fn] = lm[fn]
282 282 return m
283 283
284 284 m = manifestdict()
285 285 m._lm = self._lm.filtercopy(match)
286 286 return m
287 287
288 288 def diff(self, m2, clean=False):
289 289 '''Finds changes between the current manifest and m2.
290 290
291 291 Args:
292 292 m2: the manifest to which this manifest should be compared.
293 293 clean: if true, include files unchanged between these manifests
294 294 with a None value in the returned dictionary.
295 295
296 296 The result is returned as a dict with filename as key and
297 297 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
298 298 nodeid in the current/other manifest and fl1/fl2 is the flag
299 299 in the current/other manifest. Where the file does not exist,
300 300 the nodeid will be None and the flags will be the empty
301 301 string.
302 302 '''
303 303 return self._lm.diff(m2._lm, clean)
304 304
305 305 def setflag(self, key, flag):
306 306 self._lm[key] = self[key], flag
307 307
308 308 def get(self, key, default=None):
309 309 try:
310 310 return self._lm[key][0]
311 311 except KeyError:
312 312 return default
313 313
314 314 def flags(self, key, default=''):
315 315 try:
316 316 return self._lm[key][1]
317 317 except KeyError:
318 318 return default
319 319
320 320 def copy(self):
321 321 c = manifestdict()
322 322 c._lm = self._lm.copy()
323 323 return c
324 324
325 325 def iteritems(self):
326 326 return (x[:2] for x in self._lm.iterentries())
327 327
328 def iterentries(self):
329 return self._lm.iterentries()
330
328 331 def text(self, usemanifestv2=False):
329 332 if usemanifestv2:
330 333 return _textv2(self._lm.iterentries())
331 334 else:
332 335 # use (probably) native version for v1
333 336 return self._lm.text()
334 337
335 338 def fastdelta(self, base, changes):
336 339 """Given a base manifest text as an array.array and a list of changes
337 340 relative to that text, compute a delta that can be used by revlog.
338 341 """
339 342 delta = []
340 343 dstart = None
341 344 dend = None
342 345 dline = [""]
343 346 start = 0
344 347 # zero copy representation of base as a buffer
345 348 addbuf = util.buffer(base)
346 349
347 350 changes = list(changes)
348 351 if len(changes) < 1000:
349 352 # start with a readonly loop that finds the offset of
350 353 # each line and creates the deltas
351 354 for f, todelete in changes:
352 355 # bs will either be the index of the item or the insert point
353 356 start, end = _msearch(addbuf, f, start)
354 357 if not todelete:
355 358 h, fl = self._lm[f]
356 359 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
357 360 else:
358 361 if start == end:
359 362 # item we want to delete was not found, error out
360 363 raise AssertionError(
361 364 _("failed to remove %s from manifest") % f)
362 365 l = ""
363 366 if dstart is not None and dstart <= start and dend >= start:
364 367 if dend < end:
365 368 dend = end
366 369 if l:
367 370 dline.append(l)
368 371 else:
369 372 if dstart is not None:
370 373 delta.append([dstart, dend, "".join(dline)])
371 374 dstart = start
372 375 dend = end
373 376 dline = [l]
374 377
375 378 if dstart is not None:
376 379 delta.append([dstart, dend, "".join(dline)])
377 380 # apply the delta to the base, and get a delta for addrevision
378 381 deltatext, arraytext = _addlistdelta(base, delta)
379 382 else:
380 383 # For large changes, it's much cheaper to just build the text and
381 384 # diff it.
382 385 arraytext = array.array('c', self.text())
383 386 deltatext = mdiff.textdiff(base, arraytext)
384 387
385 388 return arraytext, deltatext
386 389
387 390 def _msearch(m, s, lo=0, hi=None):
388 391 '''return a tuple (start, end) that says where to find s within m.
389 392
390 393 If the string is found m[start:end] are the line containing
391 394 that string. If start == end the string was not found and
392 395 they indicate the proper sorted insertion point.
393 396
394 397 m should be a buffer or a string
395 398 s is a string'''
396 399 def advance(i, c):
397 400 while i < lenm and m[i] != c:
398 401 i += 1
399 402 return i
400 403 if not s:
401 404 return (lo, lo)
402 405 lenm = len(m)
403 406 if not hi:
404 407 hi = lenm
405 408 while lo < hi:
406 409 mid = (lo + hi) // 2
407 410 start = mid
408 411 while start > 0 and m[start - 1] != '\n':
409 412 start -= 1
410 413 end = advance(start, '\0')
411 414 if m[start:end] < s:
412 415 # we know that after the null there are 40 bytes of sha1
413 416 # this translates to the bisect lo = mid + 1
414 417 lo = advance(end + 40, '\n') + 1
415 418 else:
416 419 # this translates to the bisect hi = mid
417 420 hi = start
418 421 end = advance(lo, '\0')
419 422 found = m[lo:end]
420 423 if s == found:
421 424 # we know that after the null there are 40 bytes of sha1
422 425 end = advance(end + 40, '\n')
423 426 return (lo, end + 1)
424 427 else:
425 428 return (lo, lo)
426 429
427 430 def _checkforbidden(l):
428 431 """Check filenames for illegal characters."""
429 432 for f in l:
430 433 if '\n' in f or '\r' in f:
431 434 raise error.RevlogError(
432 435 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
433 436
434 437
435 438 # apply the changes collected during the bisect loop to our addlist
436 439 # return a delta suitable for addrevision
437 440 def _addlistdelta(addlist, x):
438 441 # for large addlist arrays, building a new array is cheaper
439 442 # than repeatedly modifying the existing one
440 443 currentposition = 0
441 444 newaddlist = array.array('c')
442 445
443 446 for start, end, content in x:
444 447 newaddlist += addlist[currentposition:start]
445 448 if content:
446 449 newaddlist += array.array('c', content)
447 450
448 451 currentposition = end
449 452
450 453 newaddlist += addlist[currentposition:]
451 454
452 455 deltatext = "".join(struct.pack(">lll", start, end, len(content))
453 456 + content for start, end, content in x)
454 457 return deltatext, newaddlist
455 458
456 459 def _splittopdir(f):
457 460 if '/' in f:
458 461 dir, subpath = f.split('/', 1)
459 462 return dir + '/', subpath
460 463 else:
461 464 return '', f
462 465
463 466 _noop = lambda s: None
464 467
465 468 class treemanifest(object):
466 469 def __init__(self, dir='', text=''):
467 470 self._dir = dir
468 471 self._node = revlog.nullid
469 472 self._loadfunc = _noop
470 473 self._copyfunc = _noop
471 474 self._dirty = False
472 475 self._dirs = {}
473 476 # Using _lazymanifest here is a little slower than plain old dicts
474 477 self._files = {}
475 478 self._flags = {}
476 479 if text:
477 480 def readsubtree(subdir, subm):
478 481 raise AssertionError('treemanifest constructor only accepts '
479 482 'flat manifests')
480 483 self.parse(text, readsubtree)
481 484 self._dirty = True # Mark flat manifest dirty after parsing
482 485
483 486 def _subpath(self, path):
484 487 return self._dir + path
485 488
486 489 def __len__(self):
487 490 self._load()
488 491 size = len(self._files)
489 492 for m in self._dirs.values():
490 493 size += m.__len__()
491 494 return size
492 495
493 496 def _isempty(self):
494 497 self._load() # for consistency; already loaded by all callers
495 498 return (not self._files and (not self._dirs or
496 499 all(m._isempty() for m in self._dirs.values())))
497 500
498 501 def __repr__(self):
499 502 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
500 503 (self._dir, revlog.hex(self._node),
501 504 bool(self._loadfunc is _noop),
502 505 self._dirty, id(self)))
503 506
504 507 def dir(self):
505 508 '''The directory that this tree manifest represents, including a
506 509 trailing '/'. Empty string for the repo root directory.'''
507 510 return self._dir
508 511
509 512 def node(self):
510 513 '''This node of this instance. nullid for unsaved instances. Should
511 514 be updated when the instance is read or written from a revlog.
512 515 '''
513 516 assert not self._dirty
514 517 return self._node
515 518
516 519 def setnode(self, node):
517 520 self._node = node
518 521 self._dirty = False
519 522
520 523 def iteritems(self):
521 524 self._load()
522 525 for p, n in sorted(self._dirs.items() + self._files.items()):
523 526 if p in self._files:
524 527 yield self._subpath(p), n
525 528 else:
526 529 for f, sn in n.iteritems():
527 530 yield f, sn
528 531
529 532 def iterkeys(self):
530 533 self._load()
531 534 for p in sorted(self._dirs.keys() + self._files.keys()):
532 535 if p in self._files:
533 536 yield self._subpath(p)
534 537 else:
535 538 for f in self._dirs[p].iterkeys():
536 539 yield f
537 540
538 541 def keys(self):
539 542 return list(self.iterkeys())
540 543
541 544 def __iter__(self):
542 545 return self.iterkeys()
543 546
544 547 def __contains__(self, f):
545 548 if f is None:
546 549 return False
547 550 self._load()
548 551 dir, subpath = _splittopdir(f)
549 552 if dir:
550 553 if dir not in self._dirs:
551 554 return False
552 555 return self._dirs[dir].__contains__(subpath)
553 556 else:
554 557 return f in self._files
555 558
556 559 def get(self, f, default=None):
557 560 self._load()
558 561 dir, subpath = _splittopdir(f)
559 562 if dir:
560 563 if dir not in self._dirs:
561 564 return default
562 565 return self._dirs[dir].get(subpath, default)
563 566 else:
564 567 return self._files.get(f, default)
565 568
566 569 def __getitem__(self, f):
567 570 self._load()
568 571 dir, subpath = _splittopdir(f)
569 572 if dir:
570 573 return self._dirs[dir].__getitem__(subpath)
571 574 else:
572 575 return self._files[f]
573 576
574 577 def flags(self, f):
575 578 self._load()
576 579 dir, subpath = _splittopdir(f)
577 580 if dir:
578 581 if dir not in self._dirs:
579 582 return ''
580 583 return self._dirs[dir].flags(subpath)
581 584 else:
582 585 if f in self._dirs:
583 586 return ''
584 587 return self._flags.get(f, '')
585 588
586 589 def find(self, f):
587 590 self._load()
588 591 dir, subpath = _splittopdir(f)
589 592 if dir:
590 593 return self._dirs[dir].find(subpath)
591 594 else:
592 595 return self._files[f], self._flags.get(f, '')
593 596
594 597 def __delitem__(self, f):
595 598 self._load()
596 599 dir, subpath = _splittopdir(f)
597 600 if dir:
598 601 self._dirs[dir].__delitem__(subpath)
599 602 # If the directory is now empty, remove it
600 603 if self._dirs[dir]._isempty():
601 604 del self._dirs[dir]
602 605 else:
603 606 del self._files[f]
604 607 if f in self._flags:
605 608 del self._flags[f]
606 609 self._dirty = True
607 610
608 611 def __setitem__(self, f, n):
609 612 assert n is not None
610 613 self._load()
611 614 dir, subpath = _splittopdir(f)
612 615 if dir:
613 616 if dir not in self._dirs:
614 617 self._dirs[dir] = treemanifest(self._subpath(dir))
615 618 self._dirs[dir].__setitem__(subpath, n)
616 619 else:
617 620 self._files[f] = n[:21] # to match manifestdict's behavior
618 621 self._dirty = True
619 622
620 623 def _load(self):
621 624 if self._loadfunc is not _noop:
622 625 lf, self._loadfunc = self._loadfunc, _noop
623 626 lf(self)
624 627 elif self._copyfunc is not _noop:
625 628 cf, self._copyfunc = self._copyfunc, _noop
626 629 cf(self)
627 630
628 631 def setflag(self, f, flags):
629 632 """Set the flags (symlink, executable) for path f."""
630 633 assert 't' not in flags
631 634 self._load()
632 635 dir, subpath = _splittopdir(f)
633 636 if dir:
634 637 if dir not in self._dirs:
635 638 self._dirs[dir] = treemanifest(self._subpath(dir))
636 639 self._dirs[dir].setflag(subpath, flags)
637 640 else:
638 641 self._flags[f] = flags
639 642 self._dirty = True
640 643
641 644 def copy(self):
642 645 copy = treemanifest(self._dir)
643 646 copy._node = self._node
644 647 copy._dirty = self._dirty
645 648 if self._copyfunc is _noop:
646 649 def _copyfunc(s):
647 650 self._load()
648 651 for d in self._dirs:
649 652 s._dirs[d] = self._dirs[d].copy()
650 653 s._files = dict.copy(self._files)
651 654 s._flags = dict.copy(self._flags)
652 655 if self._loadfunc is _noop:
653 656 _copyfunc(copy)
654 657 else:
655 658 copy._copyfunc = _copyfunc
656 659 else:
657 660 copy._copyfunc = self._copyfunc
658 661 return copy
659 662
660 663 def filesnotin(self, m2):
661 664 '''Set of files in this manifest that are not in the other'''
662 665 files = set()
663 666 def _filesnotin(t1, t2):
664 667 if t1._node == t2._node and not t1._dirty and not t2._dirty:
665 668 return
666 669 t1._load()
667 670 t2._load()
668 671 for d, m1 in t1._dirs.iteritems():
669 672 if d in t2._dirs:
670 673 m2 = t2._dirs[d]
671 674 _filesnotin(m1, m2)
672 675 else:
673 676 files.update(m1.iterkeys())
674 677
675 678 for fn in t1._files.iterkeys():
676 679 if fn not in t2._files:
677 680 files.add(t1._subpath(fn))
678 681
679 682 _filesnotin(self, m2)
680 683 return files
681 684
682 685 @propertycache
683 686 def _alldirs(self):
684 687 return util.dirs(self)
685 688
686 689 def dirs(self):
687 690 return self._alldirs
688 691
689 692 def hasdir(self, dir):
690 693 self._load()
691 694 topdir, subdir = _splittopdir(dir)
692 695 if topdir:
693 696 if topdir in self._dirs:
694 697 return self._dirs[topdir].hasdir(subdir)
695 698 return False
696 699 return (dir + '/') in self._dirs
697 700
698 701 def walk(self, match):
699 702 '''Generates matching file names.
700 703
701 704 Equivalent to manifest.matches(match).iterkeys(), but without creating
702 705 an entirely new manifest.
703 706
704 707 It also reports nonexistent files by marking them bad with match.bad().
705 708 '''
706 709 if match.always():
707 710 for f in iter(self):
708 711 yield f
709 712 return
710 713
711 714 fset = set(match.files())
712 715
713 716 for fn in self._walk(match):
714 717 if fn in fset:
715 718 # specified pattern is the exact name
716 719 fset.remove(fn)
717 720 yield fn
718 721
719 722 # for dirstate.walk, files=['.'] means "walk the whole tree".
720 723 # follow that here, too
721 724 fset.discard('.')
722 725
723 726 for fn in sorted(fset):
724 727 if not self.hasdir(fn):
725 728 match.bad(fn, None)
726 729
727 730 def _walk(self, match):
728 731 '''Recursively generates matching file names for walk().'''
729 732 if not match.visitdir(self._dir[:-1] or '.'):
730 733 return
731 734
732 735 # yield this dir's files and walk its submanifests
733 736 self._load()
734 737 for p in sorted(self._dirs.keys() + self._files.keys()):
735 738 if p in self._files:
736 739 fullp = self._subpath(p)
737 740 if match(fullp):
738 741 yield fullp
739 742 else:
740 743 for f in self._dirs[p]._walk(match):
741 744 yield f
742 745
743 746 def matches(self, match):
744 747 '''generate a new manifest filtered by the match argument'''
745 748 if match.always():
746 749 return self.copy()
747 750
748 751 return self._matches(match)
749 752
750 753 def _matches(self, match):
751 754 '''recursively generate a new manifest filtered by the match argument.
752 755 '''
753 756
754 757 visit = match.visitdir(self._dir[:-1] or '.')
755 758 if visit == 'all':
756 759 return self.copy()
757 760 ret = treemanifest(self._dir)
758 761 if not visit:
759 762 return ret
760 763
761 764 self._load()
762 765 for fn in self._files:
763 766 fullp = self._subpath(fn)
764 767 if not match(fullp):
765 768 continue
766 769 ret._files[fn] = self._files[fn]
767 770 if fn in self._flags:
768 771 ret._flags[fn] = self._flags[fn]
769 772
770 773 for dir, subm in self._dirs.iteritems():
771 774 m = subm._matches(match)
772 775 if not m._isempty():
773 776 ret._dirs[dir] = m
774 777
775 778 if not ret._isempty():
776 779 ret._dirty = True
777 780 return ret
778 781
779 782 def diff(self, m2, clean=False):
780 783 '''Finds changes between the current manifest and m2.
781 784
782 785 Args:
783 786 m2: the manifest to which this manifest should be compared.
784 787 clean: if true, include files unchanged between these manifests
785 788 with a None value in the returned dictionary.
786 789
787 790 The result is returned as a dict with filename as key and
788 791 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
789 792 nodeid in the current/other manifest and fl1/fl2 is the flag
790 793 in the current/other manifest. Where the file does not exist,
791 794 the nodeid will be None and the flags will be the empty
792 795 string.
793 796 '''
794 797 result = {}
795 798 emptytree = treemanifest()
796 799 def _diff(t1, t2):
797 800 if t1._node == t2._node and not t1._dirty and not t2._dirty:
798 801 return
799 802 t1._load()
800 803 t2._load()
801 804 for d, m1 in t1._dirs.iteritems():
802 805 m2 = t2._dirs.get(d, emptytree)
803 806 _diff(m1, m2)
804 807
805 808 for d, m2 in t2._dirs.iteritems():
806 809 if d not in t1._dirs:
807 810 _diff(emptytree, m2)
808 811
809 812 for fn, n1 in t1._files.iteritems():
810 813 fl1 = t1._flags.get(fn, '')
811 814 n2 = t2._files.get(fn, None)
812 815 fl2 = t2._flags.get(fn, '')
813 816 if n1 != n2 or fl1 != fl2:
814 817 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
815 818 elif clean:
816 819 result[t1._subpath(fn)] = None
817 820
818 821 for fn, n2 in t2._files.iteritems():
819 822 if fn not in t1._files:
820 823 fl2 = t2._flags.get(fn, '')
821 824 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
822 825
823 826 _diff(self, m2)
824 827 return result
825 828
826 829 def unmodifiedsince(self, m2):
827 830 return not self._dirty and not m2._dirty and self._node == m2._node
828 831
829 832 def parse(self, text, readsubtree):
830 833 for f, n, fl in _parse(text):
831 834 if fl == 't':
832 835 f = f + '/'
833 836 self._dirs[f] = readsubtree(self._subpath(f), n)
834 837 elif '/' in f:
835 838 # This is a flat manifest, so use __setitem__ and setflag rather
836 839 # than assigning directly to _files and _flags, so we can
837 840 # assign a path in a subdirectory, and to mark dirty (compared
838 841 # to nullid).
839 842 self[f] = n
840 843 if fl:
841 844 self.setflag(f, fl)
842 845 else:
843 846 # Assigning to _files and _flags avoids marking as dirty,
844 847 # and should be a little faster.
845 848 self._files[f] = n
846 849 if fl:
847 850 self._flags[f] = fl
848 851
849 852 def text(self, usemanifestv2=False):
850 853 """Get the full data of this manifest as a bytestring."""
851 854 self._load()
852 855 flags = self.flags
853 856 return _text(((f, self[f], flags(f)) for f in self.keys()),
854 857 usemanifestv2)
855 858
856 859 def dirtext(self, usemanifestv2=False):
857 860 """Get the full data of this directory as a bytestring. Make sure that
858 861 any submanifests have been written first, so their nodeids are correct.
859 862 """
860 863 self._load()
861 864 flags = self.flags
862 865 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
863 866 files = [(f, self._files[f], flags(f)) for f in self._files]
864 867 return _text(sorted(dirs + files), usemanifestv2)
865 868
866 869 def read(self, gettext, readsubtree):
867 870 def _load_for_read(s):
868 871 s.parse(gettext(), readsubtree)
869 872 s._dirty = False
870 873 self._loadfunc = _load_for_read
871 874
872 875 def writesubtrees(self, m1, m2, writesubtree):
873 876 self._load() # for consistency; should never have any effect here
874 877 emptytree = treemanifest()
875 878 for d, subm in self._dirs.iteritems():
876 879 subp1 = m1._dirs.get(d, emptytree)._node
877 880 subp2 = m2._dirs.get(d, emptytree)._node
878 881 if subp1 == revlog.nullid:
879 882 subp1, subp2 = subp2, subp1
880 883 writesubtree(subm, subp1, subp2)
881 884
882 885 class manifest(revlog.revlog):
883 886 def __init__(self, opener, dir='', dirlogcache=None):
884 887 '''The 'dir' and 'dirlogcache' arguments are for internal use by
885 888 manifest.manifest only. External users should create a root manifest
886 889 log with manifest.manifest(opener) and call dirlog() on it.
887 890 '''
888 891 # During normal operations, we expect to deal with not more than four
889 892 # revs at a time (such as during commit --amend). When rebasing large
890 893 # stacks of commits, the number can go up, hence the config knob below.
891 894 cachesize = 4
892 895 usetreemanifest = False
893 896 usemanifestv2 = False
894 897 opts = getattr(opener, 'options', None)
895 898 if opts is not None:
896 899 cachesize = opts.get('manifestcachesize', cachesize)
897 900 usetreemanifest = opts.get('treemanifest', usetreemanifest)
898 901 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
899 902 self._mancache = util.lrucachedict(cachesize)
900 903 self._treeinmem = usetreemanifest
901 904 self._treeondisk = usetreemanifest
902 905 self._usemanifestv2 = usemanifestv2
903 906 indexfile = "00manifest.i"
904 907 if dir:
905 908 assert self._treeondisk
906 909 if not dir.endswith('/'):
907 910 dir = dir + '/'
908 911 indexfile = "meta/" + dir + "00manifest.i"
909 912 revlog.revlog.__init__(self, opener, indexfile)
910 913 self._dir = dir
911 914 # The dirlogcache is kept on the root manifest log
912 915 if dir:
913 916 self._dirlogcache = dirlogcache
914 917 else:
915 918 self._dirlogcache = {'': self}
916 919
917 920 def _newmanifest(self, data=''):
918 921 if self._treeinmem:
919 922 return treemanifest(self._dir, data)
920 923 return manifestdict(data)
921 924
922 925 def dirlog(self, dir):
923 assert self._treeondisk
926 if dir:
927 assert self._treeondisk
924 928 if dir not in self._dirlogcache:
925 929 self._dirlogcache[dir] = manifest(self.opener, dir,
926 930 self._dirlogcache)
927 931 return self._dirlogcache[dir]
928 932
929 933 def _slowreaddelta(self, node):
930 934 r0 = self.deltaparent(self.rev(node))
931 935 m0 = self.read(self.node(r0))
932 936 m1 = self.read(node)
933 937 md = self._newmanifest()
934 938 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
935 939 if n1:
936 940 md[f] = n1
937 941 if fl1:
938 942 md.setflag(f, fl1)
939 943 return md
940 944
941 945 def readdelta(self, node):
942 946 if self._usemanifestv2 or self._treeondisk:
943 947 return self._slowreaddelta(node)
944 948 r = self.rev(node)
945 949 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
946 950 return self._newmanifest(d)
947 951
952 def readshallowdelta(self, node):
953 '''For flat manifests, this is the same as readdelta(). For
954 treemanifests, this will read the delta for this revlog's directory,
955 without recursively reading subdirectory manifests. Instead, any
956 subdirectory entry will be reported as it appears in the manifests, i.e.
957 the subdirectory will be reported among files and distinguished only by
958 its 't' flag.'''
959 if not self._treeondisk:
960 return self.readdelta(node)
961 if self._usemanifestv2:
962 raise error.Abort(
963 "readshallowdelta() not implemented for manifestv2")
964 r = self.rev(node)
965 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
966 return manifestdict(d)
967
948 968 def readfast(self, node):
949 969 '''use the faster of readdelta or read
950 970
951 971 This will return a manifest which is either only the files
952 972 added/modified relative to p1, or all files in the
953 973 manifest. Which one is returned depends on the codepath used
954 974 to retrieve the data.
955 975 '''
956 976 r = self.rev(node)
957 977 deltaparent = self.deltaparent(r)
958 978 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
959 979 return self.readdelta(node)
960 980 return self.read(node)
961 981
962 982 def read(self, node):
963 983 if node == revlog.nullid:
964 984 return self._newmanifest() # don't upset local cache
965 985 if node in self._mancache:
966 986 return self._mancache[node][0]
967 987 if self._treeondisk:
968 988 def gettext():
969 989 return self.revision(node)
970 990 def readsubtree(dir, subm):
971 991 return self.dirlog(dir).read(subm)
972 992 m = self._newmanifest()
973 993 m.read(gettext, readsubtree)
974 994 m.setnode(node)
975 995 arraytext = None
976 996 else:
977 997 text = self.revision(node)
978 998 m = self._newmanifest(text)
979 999 arraytext = array.array('c', text)
980 1000 self._mancache[node] = (m, arraytext)
981 1001 return m
982 1002
983 1003 def find(self, node, f):
984 1004 '''look up entry for a single file efficiently.
985 1005 return (node, flags) pair if found, (None, None) if not.'''
986 1006 m = self.read(node)
987 1007 try:
988 1008 return m.find(f)
989 1009 except KeyError:
990 1010 return None, None
991 1011
992 1012 def add(self, m, transaction, link, p1, p2, added, removed):
993 1013 if (p1 in self._mancache and not self._treeinmem
994 1014 and not self._usemanifestv2):
995 1015 # If our first parent is in the manifest cache, we can
996 1016 # compute a delta here using properties we know about the
997 1017 # manifest up-front, which may save time later for the
998 1018 # revlog layer.
999 1019
1000 1020 _checkforbidden(added)
1001 1021 # combine the changed lists into one sorted iterator
1002 1022 work = heapq.merge([(x, False) for x in added],
1003 1023 [(x, True) for x in removed])
1004 1024
1005 1025 arraytext, deltatext = m.fastdelta(self._mancache[p1][1], work)
1006 1026 cachedelta = self.rev(p1), deltatext
1007 1027 text = util.buffer(arraytext)
1008 1028 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1009 1029 else:
1010 1030 # The first parent manifest isn't already loaded, so we'll
1011 1031 # just encode a fulltext of the manifest and pass that
1012 1032 # through to the revlog layer, and let it handle the delta
1013 1033 # process.
1014 1034 if self._treeondisk:
1015 1035 m1 = self.read(p1)
1016 1036 m2 = self.read(p2)
1017 1037 n = self._addtree(m, transaction, link, m1, m2)
1018 1038 arraytext = None
1019 1039 else:
1020 1040 text = m.text(self._usemanifestv2)
1021 1041 n = self.addrevision(text, transaction, link, p1, p2)
1022 1042 arraytext = array.array('c', text)
1023 1043
1024 1044 self._mancache[n] = (m, arraytext)
1025 1045
1026 1046 return n
1027 1047
1028 1048 def _addtree(self, m, transaction, link, m1, m2):
1029 1049 # If the manifest is unchanged compared to one parent,
1030 1050 # don't write a new revision
1031 1051 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1032 1052 return m.node()
1033 1053 def writesubtree(subm, subp1, subp2):
1034 1054 sublog = self.dirlog(subm.dir())
1035 1055 sublog.add(subm, transaction, link, subp1, subp2, None, None)
1036 1056 m.writesubtrees(m1, m2, writesubtree)
1037 1057 text = m.dirtext(self._usemanifestv2)
1038 1058 # Double-check whether contents are unchanged to one parent
1039 1059 if text == m1.dirtext(self._usemanifestv2):
1040 1060 n = m1.node()
1041 1061 elif text == m2.dirtext(self._usemanifestv2):
1042 1062 n = m2.node()
1043 1063 else:
1044 1064 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1045 1065 # Save nodeid so parent manifest can calculate its nodeid
1046 1066 m.setnode(n)
1047 1067 return n
1048 1068
1049 1069 def clearcaches(self):
1050 1070 super(manifest, self).clearcaches()
1051 1071 self._mancache.clear()
1052 1072 self._dirlogcache = {'': self}
@@ -1,381 +1,408 b''
1 1 # verify.py - repository integrity checking for Mercurial
2 2 #
3 3 # Copyright 2006, 2007 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 os
11 11
12 12 from .i18n import _
13 13 from .node import (
14 14 nullid,
15 15 short,
16 16 )
17 17
18 18 from . import (
19 19 error,
20 20 revlog,
21 21 util,
22 22 )
23 23
24 24 def verify(repo):
25 25 with repo.lock():
26 26 return verifier(repo).verify()
27 27
28 28 def _normpath(f):
29 29 # under hg < 2.4, convert didn't sanitize paths properly, so a
30 30 # converted repo may contain repeated slashes
31 31 while '//' in f:
32 32 f = f.replace('//', '/')
33 33 return f
34 34
35 35 def _validpath(repo, path):
36 36 """Returns False if a path should NOT be treated as part of a repo.
37 37
38 38 For all in-core cases, this returns True, as we have no way for a
39 39 path to be mentioned in the history but not actually be
40 40 relevant. For narrow clones, this is important because many
41 41 filelogs will be missing, and changelog entries may mention
42 42 modified files that are outside the narrow scope.
43 43 """
44 44 return True
45 45
46 46 class verifier(object):
47 47 def __init__(self, repo):
48 48 self.repo = repo.unfiltered()
49 49 self.ui = repo.ui
50 50 self.badrevs = set()
51 51 self.errors = 0
52 52 self.warnings = 0
53 53 self.havecl = len(repo.changelog) > 0
54 54 self.havemf = len(repo.manifest) > 0
55 55 self.revlogv1 = repo.changelog.version != revlog.REVLOGV0
56 56 self.lrugetctx = util.lrucachefunc(repo.changectx)
57 57 self.refersmf = False
58 58 self.fncachewarned = False
59 59
60 60 def warn(self, msg):
61 61 self.ui.warn(msg + "\n")
62 62 self.warnings += 1
63 63
64 64 def err(self, linkrev, msg, filename=None):
65 65 if linkrev is not None:
66 66 self.badrevs.add(linkrev)
67 67 else:
68 68 linkrev = '?'
69 69 msg = "%s: %s" % (linkrev, msg)
70 70 if filename:
71 71 msg = "%s@%s" % (filename, msg)
72 72 self.ui.warn(" " + msg + "\n")
73 73 self.errors += 1
74 74
75 75 def exc(self, linkrev, msg, inst, filename=None):
76 76 if not str(inst):
77 77 inst = repr(inst)
78 78 self.err(linkrev, "%s: %s" % (msg, inst), filename)
79 79
80 80 def checklog(self, obj, name, linkrev):
81 81 if not len(obj) and (self.havecl or self.havemf):
82 82 self.err(linkrev, _("empty or missing %s") % name)
83 83 return
84 84
85 85 d = obj.checksize()
86 86 if d[0]:
87 87 self.err(None, _("data length off by %d bytes") % d[0], name)
88 88 if d[1]:
89 89 self.err(None, _("index contains %d extra bytes") % d[1], name)
90 90
91 91 if obj.version != revlog.REVLOGV0:
92 92 if not self.revlogv1:
93 93 self.warn(_("warning: `%s' uses revlog format 1") % name)
94 94 elif self.revlogv1:
95 95 self.warn(_("warning: `%s' uses revlog format 0") % name)
96 96
97 97 def checkentry(self, obj, i, node, seen, linkrevs, f):
98 98 lr = obj.linkrev(obj.rev(node))
99 99 if lr < 0 or (self.havecl and lr not in linkrevs):
100 100 if lr < 0 or lr >= len(self.repo.changelog):
101 101 msg = _("rev %d points to nonexistent changeset %d")
102 102 else:
103 103 msg = _("rev %d points to unexpected changeset %d")
104 104 self.err(None, msg % (i, lr), f)
105 105 if linkrevs:
106 106 if f and len(linkrevs) > 1:
107 107 try:
108 108 # attempt to filter down to real linkrevs
109 109 linkrevs = [l for l in linkrevs
110 110 if self.lrugetctx(l)[f].filenode() == node]
111 111 except Exception:
112 112 pass
113 113 self.warn(_(" (expected %s)") % " ".join(map(str, linkrevs)))
114 114 lr = None # can't be trusted
115 115
116 116 try:
117 117 p1, p2 = obj.parents(node)
118 118 if p1 not in seen and p1 != nullid:
119 119 self.err(lr, _("unknown parent 1 %s of %s") %
120 120 (short(p1), short(node)), f)
121 121 if p2 not in seen and p2 != nullid:
122 122 self.err(lr, _("unknown parent 2 %s of %s") %
123 123 (short(p2), short(node)), f)
124 124 except Exception as inst:
125 125 self.exc(lr, _("checking parents of %s") % short(node), inst, f)
126 126
127 127 if node in seen:
128 128 self.err(lr, _("duplicate revision %d (%d)") % (i, seen[node]), f)
129 129 seen[node] = i
130 130 return lr
131 131
132 132 def verify(self):
133 133 repo = self.repo
134 134
135 135 ui = repo.ui
136 136
137 137 if not repo.url().startswith('file:'):
138 138 raise error.Abort(_("cannot verify bundle or remote repos"))
139 139
140 140 if os.path.exists(repo.sjoin("journal")):
141 141 ui.warn(_("abandoned transaction found - run hg recover\n"))
142 142
143 143 if ui.verbose or not self.revlogv1:
144 144 ui.status(_("repository uses revlog format %d\n") %
145 145 (self.revlogv1 and 1 or 0))
146 146
147 147 mflinkrevs, filelinkrevs = self._verifychangelog()
148 148
149 149 filenodes = self._verifymanifest(mflinkrevs)
150 150 del mflinkrevs
151 151
152 152 self._crosscheckfiles(filelinkrevs, filenodes)
153 153
154 154 totalfiles, filerevisions = self._verifyfiles(filenodes, filelinkrevs)
155 155
156 156 ui.status(_("%d files, %d changesets, %d total revisions\n") %
157 157 (totalfiles, len(repo.changelog), filerevisions))
158 158 if self.warnings:
159 159 ui.warn(_("%d warnings encountered!\n") % self.warnings)
160 160 if self.fncachewarned:
161 161 ui.warn(_('hint: run "hg debugrebuildfncache" to recover from '
162 162 'corrupt fncache\n'))
163 163 if self.errors:
164 164 ui.warn(_("%d integrity errors encountered!\n") % self.errors)
165 165 if self.badrevs:
166 166 ui.warn(_("(first damaged changeset appears to be %d)\n")
167 167 % min(self.badrevs))
168 168 return 1
169 169
170 170 def _verifychangelog(self):
171 171 ui = self.ui
172 172 repo = self.repo
173 173 cl = repo.changelog
174 174
175 175 ui.status(_("checking changesets\n"))
176 176 mflinkrevs = {}
177 177 filelinkrevs = {}
178 178 seen = {}
179 179 self.checklog(cl, "changelog", 0)
180 180 total = len(repo)
181 181 for i in repo:
182 182 ui.progress(_('checking'), i, total=total, unit=_('changesets'))
183 183 n = cl.node(i)
184 184 self.checkentry(cl, i, n, seen, [i], "changelog")
185 185
186 186 try:
187 187 changes = cl.read(n)
188 188 if changes[0] != nullid:
189 189 mflinkrevs.setdefault(changes[0], []).append(i)
190 190 self.refersmf = True
191 191 for f in changes[3]:
192 192 if _validpath(repo, f):
193 193 filelinkrevs.setdefault(_normpath(f), []).append(i)
194 194 except Exception as inst:
195 195 self.refersmf = True
196 196 self.exc(i, _("unpacking changeset %s") % short(n), inst)
197 197 ui.progress(_('checking'), None)
198 198 return mflinkrevs, filelinkrevs
199 199
200 def _verifymanifest(self, mflinkrevs):
200 def _verifymanifest(self, mflinkrevs, dir=""):
201 201 repo = self.repo
202 202 ui = self.ui
203 mf = self.repo.manifest
203 mf = self.repo.manifest.dirlog(dir)
204 204
205 ui.status(_("checking manifests\n"))
205 if not dir:
206 self.ui.status(_("checking manifests\n"))
207
206 208 filenodes = {}
209 subdirnodes = {}
207 210 seen = {}
208 211 label = "manifest"
212 if dir:
213 label = dir
209 214 if self.refersmf:
210 215 # Do not check manifest if there are only changelog entries with
211 216 # null manifests.
212 217 self.checklog(mf, label, 0)
213 218 total = len(mf)
214 219 for i in mf:
215 ui.progress(_('checking'), i, total=total, unit=_('manifests'))
220 if not dir:
221 ui.progress(_('checking'), i, total=total, unit=_('manifests'))
216 222 n = mf.node(i)
217 223 lr = self.checkentry(mf, i, n, seen, mflinkrevs.get(n, []), label)
218 224 if n in mflinkrevs:
219 225 del mflinkrevs[n]
226 elif dir:
227 self.err(lr, _("%s not in parent-directory manifest") %
228 short(n), label)
220 229 else:
221 230 self.err(lr, _("%s not in changesets") % short(n), label)
222 231
223 232 try:
224 for f, fn in mf.readdelta(n).iteritems():
233 for f, fn, fl in mf.readshallowdelta(n).iterentries():
225 234 if not f:
226 self.err(lr, _("file without name in manifest"))
227 elif f != "/dev/null": # ignore this in very old repos
228 if _validpath(repo, f):
229 filenodes.setdefault(
230 _normpath(f), {}).setdefault(fn, lr)
235 self.err(lr, _("entry without name in manifest"))
236 elif f == "/dev/null": # ignore this in very old repos
237 continue
238 fullpath = dir + _normpath(f)
239 if not _validpath(repo, fullpath):
240 continue
241 if fl == 't':
242 subdirnodes.setdefault(fullpath + '/', {}).setdefault(
243 fn, []).append(lr)
244 else:
245 filenodes.setdefault(fullpath, {}).setdefault(fn, lr)
231 246 except Exception as inst:
232 247 self.exc(lr, _("reading delta %s") % short(n), inst, label)
233 ui.progress(_('checking'), None)
248 if not dir:
249 ui.progress(_('checking'), None)
234 250
235 251 if self.havemf:
236 252 for c, m in sorted([(c, m) for m in mflinkrevs
237 253 for c in mflinkrevs[m]]):
238 self.err(c, _("changeset refers to unknown revision %s") %
239 short(m), label)
254 if dir:
255 self.err(c, _("parent-directory manifest refers to unknown "
256 "revision %s") % short(m), label)
257 else:
258 self.err(c, _("changeset refers to unknown revision %s") %
259 short(m), label)
260
261 if not dir and subdirnodes:
262 self.ui.status(_("checking directory manifests\n"))
263 for subdir, linkrevs in subdirnodes.iteritems():
264 subdirfilenodes = self._verifymanifest(linkrevs, subdir)
265 for f, onefilenodes in subdirfilenodes.iteritems():
266 filenodes.setdefault(f, {}).update(onefilenodes)
240 267
241 268 return filenodes
242 269
243 270 def _crosscheckfiles(self, filelinkrevs, filenodes):
244 271 repo = self.repo
245 272 ui = self.ui
246 273 ui.status(_("crosschecking files in changesets and manifests\n"))
247 274
248 275 total = len(filelinkrevs) + len(filenodes)
249 276 count = 0
250 277 if self.havemf:
251 278 for f in sorted(filelinkrevs):
252 279 count += 1
253 280 ui.progress(_('crosschecking'), count, total=total)
254 281 if f not in filenodes:
255 282 lr = filelinkrevs[f][0]
256 283 self.err(lr, _("in changeset but not in manifest"), f)
257 284
258 285 if self.havecl:
259 286 for f in sorted(filenodes):
260 287 count += 1
261 288 ui.progress(_('crosschecking'), count, total=total)
262 289 if f not in filelinkrevs:
263 290 try:
264 291 fl = repo.file(f)
265 292 lr = min([fl.linkrev(fl.rev(n)) for n in filenodes[f]])
266 293 except Exception:
267 294 lr = None
268 295 self.err(lr, _("in manifest but not in changeset"), f)
269 296
270 297 ui.progress(_('crosschecking'), None)
271 298
272 299 def _verifyfiles(self, filenodes, filelinkrevs):
273 300 repo = self.repo
274 301 ui = self.ui
275 302 lrugetctx = self.lrugetctx
276 303 revlogv1 = self.revlogv1
277 304 havemf = self.havemf
278 305 ui.status(_("checking files\n"))
279 306
280 307 storefiles = set()
281 308 for f, f2, size in repo.store.datafiles():
282 309 if not f:
283 310 self.err(None, _("cannot decode filename '%s'") % f2)
284 311 elif (size > 0 or not revlogv1) and f.startswith('data/'):
285 312 storefiles.add(_normpath(f))
286 313
287 314 files = sorted(set(filenodes) | set(filelinkrevs))
288 315 total = len(files)
289 316 revisions = 0
290 317 for i, f in enumerate(files):
291 318 ui.progress(_('checking'), i, item=f, total=total)
292 319 try:
293 320 linkrevs = filelinkrevs[f]
294 321 except KeyError:
295 322 # in manifest but not in changelog
296 323 linkrevs = []
297 324
298 325 if linkrevs:
299 326 lr = linkrevs[0]
300 327 else:
301 328 lr = None
302 329
303 330 try:
304 331 fl = repo.file(f)
305 332 except error.RevlogError as e:
306 333 self.err(lr, _("broken revlog! (%s)") % e, f)
307 334 continue
308 335
309 336 for ff in fl.files():
310 337 try:
311 338 storefiles.remove(ff)
312 339 except KeyError:
313 340 self.warn(_(" warning: revlog '%s' not in fncache!") % ff)
314 341 self.fncachewarned = True
315 342
316 343 self.checklog(fl, f, lr)
317 344 seen = {}
318 345 rp = None
319 346 for i in fl:
320 347 revisions += 1
321 348 n = fl.node(i)
322 349 lr = self.checkentry(fl, i, n, seen, linkrevs, f)
323 350 if f in filenodes:
324 351 if havemf and n not in filenodes[f]:
325 352 self.err(lr, _("%s not in manifests") % (short(n)), f)
326 353 else:
327 354 del filenodes[f][n]
328 355
329 356 # verify contents
330 357 try:
331 358 l = len(fl.read(n))
332 359 rp = fl.renamed(n)
333 360 if l != fl.size(i):
334 361 if len(fl.revision(n)) != fl.size(i):
335 362 self.err(lr, _("unpacked size is %s, %s expected") %
336 363 (l, fl.size(i)), f)
337 364 except error.CensoredNodeError:
338 365 # experimental config: censor.policy
339 366 if ui.config("censor", "policy", "abort") == "abort":
340 367 self.err(lr, _("censored file data"), f)
341 368 except Exception as inst:
342 369 self.exc(lr, _("unpacking %s") % short(n), inst, f)
343 370
344 371 # check renames
345 372 try:
346 373 if rp:
347 374 if lr is not None and ui.verbose:
348 375 ctx = lrugetctx(lr)
349 376 found = False
350 377 for pctx in ctx.parents():
351 378 if rp[0] in pctx:
352 379 found = True
353 380 break
354 381 if not found:
355 382 self.warn(_("warning: copy source of '%s' not"
356 383 " in parents of %s") % (f, ctx))
357 384 fl2 = repo.file(rp[0])
358 385 if not len(fl2):
359 386 self.err(lr, _("empty or missing copy source "
360 387 "revlog %s:%s") % (rp[0], short(rp[1])), f)
361 388 elif rp[1] == nullid:
362 389 ui.note(_("warning: %s@%s: copy source"
363 390 " revision is nullid %s:%s\n")
364 391 % (f, lr, rp[0], short(rp[1])))
365 392 else:
366 393 fl2.rev(rp[1])
367 394 except Exception as inst:
368 395 self.exc(lr, _("checking rename of %s") % short(n), inst, f)
369 396
370 397 # cross-check
371 398 if f in filenodes:
372 399 fns = [(lr, n) for n, lr in filenodes[f].iteritems()]
373 400 for lr, node in sorted(fns):
374 401 self.err(lr, _("manifest refers to unknown revision %s") %
375 402 short(node), f)
376 403 ui.progress(_('checking'), None)
377 404
378 405 for f in storefiles:
379 406 self.warn(_("warning: orphan revlog '%s'") % f)
380 407
381 408 return len(files), revisions
@@ -1,666 +1,724 b''
1 1 #require killdaemons
2 2
3 3 $ cat << EOF >> $HGRCPATH
4 4 > [format]
5 5 > usegeneraldelta=yes
6 6 > [ui]
7 7 > ssh=python "$TESTDIR/dummyssh"
8 8 > EOF
9 9
10 10 Set up repo
11 11
12 12 $ hg --config experimental.treemanifest=True init repo
13 13 $ cd repo
14 14
15 15 Requirements get set on init
16 16
17 17 $ grep treemanifest .hg/requires
18 18 treemanifest
19 19
20 20 Without directories, looks like any other repo
21 21
22 22 $ echo 0 > a
23 23 $ echo 0 > b
24 24 $ hg ci -Aqm initial
25 25 $ hg debugdata -m 0
26 26 a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
27 27 b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
28 28
29 29 Submanifest is stored in separate revlog
30 30
31 31 $ mkdir dir1
32 32 $ echo 1 > dir1/a
33 33 $ echo 1 > dir1/b
34 34 $ echo 1 > e
35 35 $ hg ci -Aqm 'add dir1'
36 36 $ hg debugdata -m 1
37 37 a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
38 38 b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
39 39 dir1\x008b3ffd73f901e83304c83d33132c8e774ceac44et (esc)
40 40 e\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
41 41 $ hg debugdata --dir dir1 0
42 42 a\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
43 43 b\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
44 44
45 45 Can add nested directories
46 46
47 47 $ mkdir dir1/dir1
48 48 $ echo 2 > dir1/dir1/a
49 49 $ echo 2 > dir1/dir1/b
50 50 $ mkdir dir1/dir2
51 51 $ echo 2 > dir1/dir2/a
52 52 $ echo 2 > dir1/dir2/b
53 53 $ hg ci -Aqm 'add dir1/dir1'
54 54 $ hg files -r .
55 55 a
56 56 b
57 57 dir1/a (glob)
58 58 dir1/b (glob)
59 59 dir1/dir1/a (glob)
60 60 dir1/dir1/b (glob)
61 61 dir1/dir2/a (glob)
62 62 dir1/dir2/b (glob)
63 63 e
64 64
65 65 Revision is not created for unchanged directory
66 66
67 67 $ mkdir dir2
68 68 $ echo 3 > dir2/a
69 69 $ hg add dir2
70 70 adding dir2/a (glob)
71 71 $ hg debugindex --dir dir1 > before
72 72 $ hg ci -qm 'add dir2'
73 73 $ hg debugindex --dir dir1 > after
74 74 $ diff before after
75 75 $ rm before after
76 76
77 77 Removing directory does not create an revlog entry
78 78
79 79 $ hg rm dir1/dir1
80 80 removing dir1/dir1/a (glob)
81 81 removing dir1/dir1/b (glob)
82 82 $ hg debugindex --dir dir1/dir1 > before
83 83 $ hg ci -qm 'remove dir1/dir1'
84 84 $ hg debugindex --dir dir1/dir1 > after
85 85 $ diff before after
86 86 $ rm before after
87 87
88 88 Check that hg files (calls treemanifest.walk()) works
89 89 without loading all directory revlogs
90 90
91 91 $ hg co 'desc("add dir2")'
92 92 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
93 93 $ mv .hg/store/meta/dir2 .hg/store/meta/dir2-backup
94 94 $ hg files -r . dir1
95 95 dir1/a (glob)
96 96 dir1/b (glob)
97 97 dir1/dir1/a (glob)
98 98 dir1/dir1/b (glob)
99 99 dir1/dir2/a (glob)
100 100 dir1/dir2/b (glob)
101 101
102 102 Check that status between revisions works (calls treemanifest.matches())
103 103 without loading all directory revlogs
104 104
105 105 $ hg status --rev 'desc("add dir1")' --rev . dir1
106 106 A dir1/dir1/a
107 107 A dir1/dir1/b
108 108 A dir1/dir2/a
109 109 A dir1/dir2/b
110 110 $ mv .hg/store/meta/dir2-backup .hg/store/meta/dir2
111 111
112 112 Merge creates 2-parent revision of directory revlog
113 113
114 114 $ echo 5 > dir1/a
115 115 $ hg ci -Aqm 'modify dir1/a'
116 116 $ hg co '.^'
117 117 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
118 118 $ echo 6 > dir1/b
119 119 $ hg ci -Aqm 'modify dir1/b'
120 120 $ hg merge 'desc("modify dir1/a")'
121 121 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
122 122 (branch merge, don't forget to commit)
123 123 $ hg ci -m 'conflict-free merge involving dir1/'
124 124 $ cat dir1/a
125 125 5
126 126 $ cat dir1/b
127 127 6
128 128 $ hg debugindex --dir dir1
129 129 rev offset length delta linkrev nodeid p1 p2
130 130 0 0 54 -1 1 8b3ffd73f901 000000000000 000000000000
131 131 1 54 68 0 2 68e9d057c5a8 8b3ffd73f901 000000000000
132 132 2 122 12 1 4 4698198d2624 68e9d057c5a8 000000000000
133 133 3 134 55 1 5 44844058ccce 68e9d057c5a8 000000000000
134 134 4 189 55 1 6 bf3d9b744927 68e9d057c5a8 000000000000
135 135 5 244 55 4 7 dde7c0af2a03 bf3d9b744927 44844058ccce
136 136
137 137 Merge keeping directory from parent 1 does not create revlog entry. (Note that
138 138 dir1's manifest does change, but only because dir1/a's filelog changes.)
139 139
140 140 $ hg co 'desc("add dir2")'
141 141 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
142 142 $ echo 8 > dir2/a
143 143 $ hg ci -m 'modify dir2/a'
144 144 created new head
145 145
146 146 $ hg debugindex --dir dir2 > before
147 147 $ hg merge 'desc("modify dir1/a")'
148 148 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
149 149 (branch merge, don't forget to commit)
150 150 $ hg revert -r 'desc("modify dir2/a")' .
151 151 reverting dir1/a (glob)
152 152 $ hg ci -m 'merge, keeping parent 1'
153 153 $ hg debugindex --dir dir2 > after
154 154 $ diff before after
155 155 $ rm before after
156 156
157 157 Merge keeping directory from parent 2 does not create revlog entry. (Note that
158 158 dir2's manifest does change, but only because dir2/a's filelog changes.)
159 159
160 160 $ hg co 'desc("modify dir2/a")'
161 161 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
162 162 $ hg debugindex --dir dir1 > before
163 163 $ hg merge 'desc("modify dir1/a")'
164 164 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
165 165 (branch merge, don't forget to commit)
166 166 $ hg revert -r 'desc("modify dir1/a")' .
167 167 reverting dir2/a (glob)
168 168 $ hg ci -m 'merge, keeping parent 2'
169 169 created new head
170 170 $ hg debugindex --dir dir1 > after
171 171 $ diff before after
172 172 $ rm before after
173 173
174 174 Create flat source repo for tests with mixed flat/tree manifests
175 175
176 176 $ cd ..
177 177 $ hg init repo-flat
178 178 $ cd repo-flat
179 179
180 180 Create a few commits with flat manifest
181 181
182 182 $ echo 0 > a
183 183 $ echo 0 > b
184 184 $ echo 0 > e
185 185 $ for d in dir1 dir1/dir1 dir1/dir2 dir2
186 186 > do
187 187 > mkdir $d
188 188 > echo 0 > $d/a
189 189 > echo 0 > $d/b
190 190 > done
191 191 $ hg ci -Aqm initial
192 192
193 193 $ echo 1 > a
194 194 $ echo 1 > dir1/a
195 195 $ echo 1 > dir1/dir1/a
196 196 $ hg ci -Aqm 'modify on branch 1'
197 197
198 198 $ hg co 0
199 199 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
200 200 $ echo 2 > b
201 201 $ echo 2 > dir1/b
202 202 $ echo 2 > dir1/dir1/b
203 203 $ hg ci -Aqm 'modify on branch 2'
204 204
205 205 $ hg merge 1
206 206 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
207 207 (branch merge, don't forget to commit)
208 208 $ hg ci -m 'merge of flat manifests to new flat manifest'
209 209
210 210 $ hg serve -p $HGPORT -d --pid-file=hg.pid --errorlog=errors.log
211 211 $ cat hg.pid >> $DAEMON_PIDS
212 212
213 213 Create clone with tree manifests enabled
214 214
215 215 $ cd ..
216 216 $ hg clone --config experimental.treemanifest=1 \
217 217 > http://localhost:$HGPORT repo-mixed -r 1
218 218 adding changesets
219 219 adding manifests
220 220 adding file changes
221 221 added 2 changesets with 14 changes to 11 files
222 222 updating to branch default
223 223 11 files updated, 0 files merged, 0 files removed, 0 files unresolved
224 224 $ cd repo-mixed
225 225 $ test -d .hg/store/meta
226 226 [1]
227 227 $ grep treemanifest .hg/requires
228 228 treemanifest
229 229
230 230 Should be possible to push updates from flat to tree manifest repo
231 231
232 232 $ hg -R ../repo-flat push ssh://user@dummy/repo-mixed
233 233 pushing to ssh://user@dummy/repo-mixed
234 234 searching for changes
235 235 remote: adding changesets
236 236 remote: adding manifests
237 237 remote: adding file changes
238 238 remote: added 2 changesets with 3 changes to 3 files
239 239
240 240 Commit should store revlog per directory
241 241
242 242 $ hg co 1
243 243 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
244 244 $ echo 3 > a
245 245 $ echo 3 > dir1/a
246 246 $ echo 3 > dir1/dir1/a
247 247 $ hg ci -m 'first tree'
248 248 created new head
249 249 $ find .hg/store/meta | sort
250 250 .hg/store/meta
251 251 .hg/store/meta/dir1
252 252 .hg/store/meta/dir1/00manifest.i
253 253 .hg/store/meta/dir1/dir1
254 254 .hg/store/meta/dir1/dir1/00manifest.i
255 255 .hg/store/meta/dir1/dir2
256 256 .hg/store/meta/dir1/dir2/00manifest.i
257 257 .hg/store/meta/dir2
258 258 .hg/store/meta/dir2/00manifest.i
259 259
260 260 Merge of two trees
261 261
262 262 $ hg co 2
263 263 6 files updated, 0 files merged, 0 files removed, 0 files unresolved
264 264 $ hg merge 1
265 265 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
266 266 (branch merge, don't forget to commit)
267 267 $ hg ci -m 'merge of flat manifests to new tree manifest'
268 268 created new head
269 269 $ hg diff -r 3
270 270
271 271 Parent of tree root manifest should be flat manifest, and two for merge
272 272
273 273 $ hg debugindex -m
274 274 rev offset length delta linkrev nodeid p1 p2
275 275 0 0 80 -1 0 40536115ed9e 000000000000 000000000000
276 276 1 80 83 0 1 f3376063c255 40536115ed9e 000000000000
277 277 2 163 89 0 2 5d9b9da231a2 40536115ed9e 000000000000
278 278 3 252 83 2 3 d17d663cbd8a 5d9b9da231a2 f3376063c255
279 279 4 335 124 1 4 51e32a8c60ee f3376063c255 000000000000
280 280 5 459 126 2 5 cc5baa78b230 5d9b9da231a2 f3376063c255
281 281
282 282
283 283 Status across flat/tree boundary should work
284 284
285 285 $ hg status --rev '.^' --rev .
286 286 M a
287 287 M dir1/a
288 288 M dir1/dir1/a
289 289
290 290
291 291 Turning off treemanifest config has no effect
292 292
293 293 $ hg debugindex --dir dir1
294 294 rev offset length delta linkrev nodeid p1 p2
295 295 0 0 127 -1 4 064927a0648a 000000000000 000000000000
296 296 1 127 111 0 5 25ecb8cb8618 000000000000 000000000000
297 297 $ echo 2 > dir1/a
298 298 $ hg --config experimental.treemanifest=False ci -qm 'modify dir1/a'
299 299 $ hg debugindex --dir dir1
300 300 rev offset length delta linkrev nodeid p1 p2
301 301 0 0 127 -1 4 064927a0648a 000000000000 000000000000
302 302 1 127 111 0 5 25ecb8cb8618 000000000000 000000000000
303 303 2 238 55 1 6 5b16163a30c6 25ecb8cb8618 000000000000
304 304
305 305 Stripping and recovering changes should work
306 306
307 307 $ hg st --change tip
308 308 M dir1/a
309 309 $ hg --config extensions.strip= strip tip
310 310 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
311 311 saved backup bundle to $TESTTMP/repo-mixed/.hg/strip-backup/51cfd7b1e13b-78a2f3ed-backup.hg (glob)
312 312 $ hg unbundle -q .hg/strip-backup/*
313 313 $ hg st --change tip
314 314 M dir1/a
315 315
316 316 Shelving and unshelving should work
317 317
318 318 $ echo foo >> dir1/a
319 319 $ hg --config extensions.shelve= shelve
320 320 shelved as default
321 321 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
322 322 $ hg --config extensions.shelve= unshelve
323 323 unshelving change 'default'
324 324 $ hg diff --nodates
325 325 diff -r 708a273da119 dir1/a
326 326 --- a/dir1/a
327 327 +++ b/dir1/a
328 328 @@ -1,1 +1,2 @@
329 329 1
330 330 +foo
331 331
332 332 Pushing from treemanifest repo to an empty repo makes that a treemanifest repo
333 333
334 334 $ cd ..
335 335 $ hg init empty-repo
336 336 $ cat << EOF >> empty-repo/.hg/hgrc
337 337 > [experimental]
338 338 > changegroup3=yes
339 339 > EOF
340 340 $ grep treemanifest empty-repo/.hg/requires
341 341 [1]
342 342 $ hg push -R repo -r 0 empty-repo
343 343 pushing to empty-repo
344 344 searching for changes
345 345 adding changesets
346 346 adding manifests
347 347 adding file changes
348 348 added 1 changesets with 2 changes to 2 files
349 349 $ grep treemanifest empty-repo/.hg/requires
350 350 treemanifest
351 351
352 352 Pushing to an empty repo works
353 353
354 354 $ hg --config experimental.treemanifest=1 init clone
355 355 $ grep treemanifest clone/.hg/requires
356 356 treemanifest
357 357 $ hg push -R repo clone
358 358 pushing to clone
359 359 searching for changes
360 360 adding changesets
361 361 adding manifests
362 362 adding file changes
363 363 added 11 changesets with 15 changes to 10 files (+3 heads)
364 364 $ grep treemanifest clone/.hg/requires
365 365 treemanifest
366 366
367 367 Create deeper repo with tree manifests.
368 368
369 369 $ hg --config experimental.treemanifest=True init deeprepo
370 370 $ cd deeprepo
371 371
372 372 $ mkdir .A
373 373 $ mkdir b
374 374 $ mkdir b/bar
375 375 $ mkdir b/bar/orange
376 376 $ mkdir b/bar/orange/fly
377 377 $ mkdir b/foo
378 378 $ mkdir b/foo/apple
379 379 $ mkdir b/foo/apple/bees
380 380
381 381 $ touch .A/one.txt
382 382 $ touch .A/two.txt
383 383 $ touch b/bar/fruits.txt
384 384 $ touch b/bar/orange/fly/gnat.py
385 385 $ touch b/bar/orange/fly/housefly.txt
386 386 $ touch b/foo/apple/bees/flower.py
387 387 $ touch c.txt
388 388 $ touch d.py
389 389
390 390 $ hg ci -Aqm 'initial'
391 391
392 392 We'll see that visitdir works by removing some treemanifest revlogs and running
393 393 the files command with various parameters.
394 394
395 395 Test files from the root.
396 396
397 397 $ hg files -r .
398 398 .A/one.txt (glob)
399 399 .A/two.txt (glob)
400 400 b/bar/fruits.txt (glob)
401 401 b/bar/orange/fly/gnat.py (glob)
402 402 b/bar/orange/fly/housefly.txt (glob)
403 403 b/foo/apple/bees/flower.py (glob)
404 404 c.txt
405 405 d.py
406 406
407 407 Excludes with a glob should not exclude everything from the glob's root
408 408
409 409 $ hg files -r . -X 'b/fo?' b
410 410 b/bar/fruits.txt (glob)
411 411 b/bar/orange/fly/gnat.py (glob)
412 412 b/bar/orange/fly/housefly.txt (glob)
413 413 $ cp -r .hg/store .hg/store-copy
414 414
415 415 Test files for a subdirectory.
416 416
417 417 $ rm -r .hg/store/meta/~2e_a
418 418 $ hg files -r . b
419 419 b/bar/fruits.txt (glob)
420 420 b/bar/orange/fly/gnat.py (glob)
421 421 b/bar/orange/fly/housefly.txt (glob)
422 422 b/foo/apple/bees/flower.py (glob)
423 423 $ cp -r .hg/store-copy/* .hg/store
424 424
425 425 Test files with just includes and excludes.
426 426
427 427 $ rm -r .hg/store/meta/~2e_a
428 428 $ rm -r .hg/store/meta/b/bar/orange/fly
429 429 $ rm -r .hg/store/meta/b/foo/apple/bees
430 430 $ hg files -r . -I path:b/bar -X path:b/bar/orange/fly -I path:b/foo -X path:b/foo/apple/bees
431 431 b/bar/fruits.txt (glob)
432 432 $ cp -r .hg/store-copy/* .hg/store
433 433
434 434 Test files for a subdirectory, excluding a directory within it.
435 435
436 436 $ rm -r .hg/store/meta/~2e_a
437 437 $ rm -r .hg/store/meta/b/foo
438 438 $ hg files -r . -X path:b/foo b
439 439 b/bar/fruits.txt (glob)
440 440 b/bar/orange/fly/gnat.py (glob)
441 441 b/bar/orange/fly/housefly.txt (glob)
442 442 $ cp -r .hg/store-copy/* .hg/store
443 443
444 444 Test files for a sub directory, including only a directory within it, and
445 445 including an unrelated directory.
446 446
447 447 $ rm -r .hg/store/meta/~2e_a
448 448 $ rm -r .hg/store/meta/b/foo
449 449 $ hg files -r . -I path:b/bar/orange -I path:a b
450 450 b/bar/orange/fly/gnat.py (glob)
451 451 b/bar/orange/fly/housefly.txt (glob)
452 452 $ cp -r .hg/store-copy/* .hg/store
453 453
454 454 Test files for a pattern, including a directory, and excluding a directory
455 455 within that.
456 456
457 457 $ rm -r .hg/store/meta/~2e_a
458 458 $ rm -r .hg/store/meta/b/foo
459 459 $ rm -r .hg/store/meta/b/bar/orange
460 460 $ hg files -r . glob:**.txt -I path:b/bar -X path:b/bar/orange
461 461 b/bar/fruits.txt (glob)
462 462 $ cp -r .hg/store-copy/* .hg/store
463 463
464 464 Add some more changes to the deep repo
465 465 $ echo narf >> b/bar/fruits.txt
466 466 $ hg ci -m narf
467 467 $ echo troz >> b/bar/orange/fly/gnat.py
468 468 $ hg ci -m troz
469 469
470 470 Verify works
471 471 $ hg verify
472 472 checking changesets
473 473 checking manifests
474 checking directory manifests
474 475 crosschecking files in changesets and manifests
475 476 checking files
476 477 8 files, 3 changesets, 10 total revisions
477 478
478 479 Dirlogs are included in fncache
479 480 $ grep meta/.A/00manifest.i .hg/store/fncache
480 481 meta/.A/00manifest.i
481 482
482 483 Rebuilt fncache includes dirlogs
483 484 $ rm .hg/store/fncache
484 485 $ hg debugrebuildfncache
485 486 adding data/.A/one.txt.i
486 487 adding data/.A/two.txt.i
487 488 adding data/b/bar/fruits.txt.i
488 489 adding data/b/bar/orange/fly/gnat.py.i
489 490 adding data/b/bar/orange/fly/housefly.txt.i
490 491 adding data/b/foo/apple/bees/flower.py.i
491 492 adding data/c.txt.i
492 493 adding data/d.py.i
493 494 adding meta/.A/00manifest.i
494 495 adding meta/b/00manifest.i
495 496 adding meta/b/bar/00manifest.i
496 497 adding meta/b/bar/orange/00manifest.i
497 498 adding meta/b/bar/orange/fly/00manifest.i
498 499 adding meta/b/foo/00manifest.i
499 500 adding meta/b/foo/apple/00manifest.i
500 501 adding meta/b/foo/apple/bees/00manifest.i
501 502 16 items added, 0 removed from fncache
502 503
503 504 Finish first server
504 505 $ killdaemons.py
505 506
507 Back up the recently added revlogs
508 $ cp -r .hg/store .hg/store-newcopy
509
510 Verify reports missing dirlog
511 $ rm .hg/store/meta/b/00manifest.*
512 $ hg verify
513 checking changesets
514 checking manifests
515 checking directory manifests
516 0: empty or missing b/
517 b/@0: parent-directory manifest refers to unknown revision 67688a370455
518 b/@1: parent-directory manifest refers to unknown revision f38e85d334c5
519 b/@2: parent-directory manifest refers to unknown revision 99c9792fd4b0
520 crosschecking files in changesets and manifests
521 b/bar/fruits.txt@0: in changeset but not in manifest
522 b/bar/orange/fly/gnat.py@0: in changeset but not in manifest
523 b/bar/orange/fly/housefly.txt@0: in changeset but not in manifest
524 b/foo/apple/bees/flower.py@0: in changeset but not in manifest
525 checking files
526 8 files, 3 changesets, 10 total revisions
527 8 integrity errors encountered!
528 (first damaged changeset appears to be 0)
529 [1]
530 $ cp -rT .hg/store-newcopy .hg/store
531
532 Verify reports missing dirlog entry
533 $ mv -f .hg/store-copy/meta/b/00manifest.* .hg/store/meta/b/
534 $ hg verify
535 checking changesets
536 checking manifests
537 checking directory manifests
538 b/@1: parent-directory manifest refers to unknown revision f38e85d334c5
539 b/@2: parent-directory manifest refers to unknown revision 99c9792fd4b0
540 b/bar/@?: rev 1 points to unexpected changeset 1
541 b/bar/@?: 5e03c4ee5e4a not in parent-directory manifest
542 b/bar/@?: rev 2 points to unexpected changeset 2
543 b/bar/@?: 1b16940d66d6 not in parent-directory manifest
544 b/bar/orange/@?: rev 1 points to unexpected changeset 2
545 (expected None)
546 b/bar/orange/fly/@?: rev 1 points to unexpected changeset 2
547 (expected None)
548 crosschecking files in changesets and manifests
549 checking files
550 8 files, 3 changesets, 10 total revisions
551 2 warnings encountered!
552 8 integrity errors encountered!
553 (first damaged changeset appears to be 1)
554 [1]
555 $ cp -rT .hg/store-newcopy .hg/store
556
506 557 Test cloning a treemanifest repo over http.
507 558 $ hg serve -p $HGPORT -d --pid-file=hg.pid --errorlog=errors.log
508 559 $ cat hg.pid >> $DAEMON_PIDS
509 560 $ cd ..
510 561 We can clone even with the knob turned off and we'll get a treemanifest repo.
511 562 $ hg clone --config experimental.treemanifest=False \
512 563 > --config experimental.changegroup3=True \
513 564 > http://localhost:$HGPORT deepclone
514 565 requesting all changes
515 566 adding changesets
516 567 adding manifests
517 568 adding file changes
518 569 added 3 changesets with 10 changes to 8 files
519 570 updating to branch default
520 571 8 files updated, 0 files merged, 0 files removed, 0 files unresolved
521 572 No server errors.
522 573 $ cat deeprepo/errors.log
523 574 requires got updated to include treemanifest
524 575 $ cat deepclone/.hg/requires | grep treemanifest
525 576 treemanifest
526 577 Tree manifest revlogs exist.
527 578 $ find deepclone/.hg/store/meta | sort
528 579 deepclone/.hg/store/meta
529 580 deepclone/.hg/store/meta/b
530 581 deepclone/.hg/store/meta/b/00manifest.i
531 582 deepclone/.hg/store/meta/b/bar
532 583 deepclone/.hg/store/meta/b/bar/00manifest.i
533 584 deepclone/.hg/store/meta/b/bar/orange
534 585 deepclone/.hg/store/meta/b/bar/orange/00manifest.i
535 586 deepclone/.hg/store/meta/b/bar/orange/fly
536 587 deepclone/.hg/store/meta/b/bar/orange/fly/00manifest.i
537 588 deepclone/.hg/store/meta/b/foo
538 589 deepclone/.hg/store/meta/b/foo/00manifest.i
539 590 deepclone/.hg/store/meta/b/foo/apple
540 591 deepclone/.hg/store/meta/b/foo/apple/00manifest.i
541 592 deepclone/.hg/store/meta/b/foo/apple/bees
542 593 deepclone/.hg/store/meta/b/foo/apple/bees/00manifest.i
543 594 deepclone/.hg/store/meta/~2e_a
544 595 deepclone/.hg/store/meta/~2e_a/00manifest.i
545 596 Verify passes.
546 597 $ cd deepclone
547 598 $ hg verify
548 599 checking changesets
549 600 checking manifests
601 checking directory manifests
550 602 crosschecking files in changesets and manifests
551 603 checking files
552 604 8 files, 3 changesets, 10 total revisions
553 605 $ cd ..
554 606
555 607 Create clones using old repo formats to use in later tests
556 608 $ hg clone --config format.usestore=False \
557 609 > --config experimental.changegroup3=True \
558 610 > http://localhost:$HGPORT deeprepo-basicstore
559 611 requesting all changes
560 612 adding changesets
561 613 adding manifests
562 614 adding file changes
563 615 added 3 changesets with 10 changes to 8 files
564 616 updating to branch default
565 617 8 files updated, 0 files merged, 0 files removed, 0 files unresolved
566 618 $ cd deeprepo-basicstore
567 619 $ grep store .hg/requires
568 620 [1]
569 621 $ hg serve -p $HGPORT1 -d --pid-file=hg.pid --errorlog=errors.log
570 622 $ cat hg.pid >> $DAEMON_PIDS
571 623 $ cd ..
572 624 $ hg clone --config format.usefncache=False \
573 625 > --config experimental.changegroup3=True \
574 626 > http://localhost:$HGPORT deeprepo-encodedstore
575 627 requesting all changes
576 628 adding changesets
577 629 adding manifests
578 630 adding file changes
579 631 added 3 changesets with 10 changes to 8 files
580 632 updating to branch default
581 633 8 files updated, 0 files merged, 0 files removed, 0 files unresolved
582 634 $ cd deeprepo-encodedstore
583 635 $ grep fncache .hg/requires
584 636 [1]
585 637 $ hg serve -p $HGPORT2 -d --pid-file=hg.pid --errorlog=errors.log
586 638 $ cat hg.pid >> $DAEMON_PIDS
587 639 $ cd ..
588 640
589 641 Local clone with basicstore
590 642 $ hg clone -U deeprepo-basicstore local-clone-basicstore
591 643 $ hg -R local-clone-basicstore verify
592 644 checking changesets
593 645 checking manifests
646 checking directory manifests
594 647 crosschecking files in changesets and manifests
595 648 checking files
596 649 8 files, 3 changesets, 10 total revisions
597 650
598 651 Local clone with encodedstore
599 652 $ hg clone -U deeprepo-encodedstore local-clone-encodedstore
600 653 $ hg -R local-clone-encodedstore verify
601 654 checking changesets
602 655 checking manifests
656 checking directory manifests
603 657 crosschecking files in changesets and manifests
604 658 checking files
605 659 8 files, 3 changesets, 10 total revisions
606 660
607 661 Local clone with fncachestore
608 662 $ hg clone -U deeprepo local-clone-fncachestore
609 663 $ hg -R local-clone-fncachestore verify
610 664 checking changesets
611 665 checking manifests
666 checking directory manifests
612 667 crosschecking files in changesets and manifests
613 668 checking files
614 669 8 files, 3 changesets, 10 total revisions
615 670
616 671 Stream clone with basicstore
617 672 $ hg clone --config experimental.changegroup3=True --uncompressed -U \
618 673 > http://localhost:$HGPORT1 stream-clone-basicstore
619 674 streaming all changes
620 675 18 files to transfer, * of data (glob)
621 676 transferred * in * seconds (*) (glob)
622 677 searching for changes
623 678 no changes found
624 679 $ hg -R stream-clone-basicstore verify
625 680 checking changesets
626 681 checking manifests
682 checking directory manifests
627 683 crosschecking files in changesets and manifests
628 684 checking files
629 685 8 files, 3 changesets, 10 total revisions
630 686
631 687 Stream clone with encodedstore
632 688 $ hg clone --config experimental.changegroup3=True --uncompressed -U \
633 689 > http://localhost:$HGPORT2 stream-clone-encodedstore
634 690 streaming all changes
635 691 18 files to transfer, * of data (glob)
636 692 transferred * in * seconds (*) (glob)
637 693 searching for changes
638 694 no changes found
639 695 $ hg -R stream-clone-encodedstore verify
640 696 checking changesets
641 697 checking manifests
698 checking directory manifests
642 699 crosschecking files in changesets and manifests
643 700 checking files
644 701 8 files, 3 changesets, 10 total revisions
645 702
646 703 Stream clone with fncachestore
647 704 $ hg clone --config experimental.changegroup3=True --uncompressed -U \
648 705 > http://localhost:$HGPORT stream-clone-fncachestore
649 706 streaming all changes
650 707 18 files to transfer, * of data (glob)
651 708 transferred * in * seconds (*) (glob)
652 709 searching for changes
653 710 no changes found
654 711 $ hg -R stream-clone-fncachestore verify
655 712 checking changesets
656 713 checking manifests
714 checking directory manifests
657 715 crosschecking files in changesets and manifests
658 716 checking files
659 717 8 files, 3 changesets, 10 total revisions
660 718
661 719 Packed bundle
662 720 $ hg -R deeprepo debugcreatestreamclonebundle repo-packed.hg
663 721 writing 3349 bytes for 18 files
664 722 bundle requirements: generaldelta, revlogv1, treemanifest
665 723 $ hg debugbundle --spec repo-packed.hg
666 724 none-packed1;requirements%3Dgeneraldelta%2Crevlogv1%2Ctreemanifest
General Comments 0
You need to be logged in to leave comments. Login now