##// END OF EJS Templates
manifest: add match argument to diff and filesnotin...
Durham Goode -
r31255:959ebff3 default
parent child Browse files
Show More
@@ -1,1582 +1,1599 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 lazymanifestiter(object):
108 108 def __init__(self, lm):
109 109 self.pos = 0
110 110 self.lm = lm
111 111
112 112 def __iter__(self):
113 113 return self
114 114
115 115 def next(self):
116 116 try:
117 117 data, pos = self.lm._get(self.pos)
118 118 except IndexError:
119 119 raise StopIteration
120 120 if pos == -1:
121 121 self.pos += 1
122 122 return data[0]
123 123 self.pos += 1
124 124 zeropos = data.find('\x00', pos)
125 125 return data[pos:zeropos]
126 126
127 127 class lazymanifestiterentries(object):
128 128 def __init__(self, lm):
129 129 self.lm = lm
130 130 self.pos = 0
131 131
132 132 def __iter__(self):
133 133 return self
134 134
135 135 def next(self):
136 136 try:
137 137 data, pos = self.lm._get(self.pos)
138 138 except IndexError:
139 139 raise StopIteration
140 140 if pos == -1:
141 141 self.pos += 1
142 142 return data
143 143 zeropos = data.find('\x00', pos)
144 144 hashval = unhexlify(data, self.lm.extrainfo[self.pos],
145 145 zeropos + 1, 40)
146 146 flags = self.lm._getflags(data, self.pos, zeropos)
147 147 self.pos += 1
148 148 return (data[pos:zeropos], hashval, flags)
149 149
150 150 def unhexlify(data, extra, pos, length):
151 151 s = data[pos:pos + length].decode('hex')
152 152 if extra:
153 153 s += chr(extra & 0xff)
154 154 return s
155 155
156 156 def _cmp(a, b):
157 157 return (a > b) - (a < b)
158 158
159 159 class _lazymanifest(object):
160 160 def __init__(self, data, positions=None, extrainfo=None, extradata=None):
161 161 if positions is None:
162 162 self.positions = self.findlines(data)
163 163 self.extrainfo = [0] * len(self.positions)
164 164 self.data = data
165 165 self.extradata = []
166 166 else:
167 167 self.positions = positions[:]
168 168 self.extrainfo = extrainfo[:]
169 169 self.extradata = extradata[:]
170 170 self.data = data
171 171
172 172 def findlines(self, data):
173 173 if not data:
174 174 return []
175 175 pos = data.find("\n")
176 176 if pos == -1 or data[-1] != '\n':
177 177 raise ValueError("Manifest did not end in a newline.")
178 178 positions = [0]
179 179 prev = data[:data.find('\x00')]
180 180 while pos < len(data) - 1 and pos != -1:
181 181 positions.append(pos + 1)
182 182 nexts = data[pos + 1:data.find('\x00', pos + 1)]
183 183 if nexts < prev:
184 184 raise ValueError("Manifest lines not in sorted order.")
185 185 prev = nexts
186 186 pos = data.find("\n", pos + 1)
187 187 return positions
188 188
189 189 def _get(self, index):
190 190 # get the position encoded in pos:
191 191 # positive number is an index in 'data'
192 192 # negative number is in extrapieces
193 193 pos = self.positions[index]
194 194 if pos >= 0:
195 195 return self.data, pos
196 196 return self.extradata[-pos - 1], -1
197 197
198 198 def _getkey(self, pos):
199 199 if pos >= 0:
200 200 return self.data[pos:self.data.find('\x00', pos + 1)]
201 201 return self.extradata[-pos - 1][0]
202 202
203 203 def bsearch(self, key):
204 204 first = 0
205 205 last = len(self.positions) - 1
206 206
207 207 while first <= last:
208 208 midpoint = (first + last)//2
209 209 nextpos = self.positions[midpoint]
210 210 candidate = self._getkey(nextpos)
211 211 r = _cmp(key, candidate)
212 212 if r == 0:
213 213 return midpoint
214 214 else:
215 215 if r < 0:
216 216 last = midpoint - 1
217 217 else:
218 218 first = midpoint + 1
219 219 return -1
220 220
221 221 def bsearch2(self, key):
222 222 # same as the above, but will always return the position
223 223 # done for performance reasons
224 224 first = 0
225 225 last = len(self.positions) - 1
226 226
227 227 while first <= last:
228 228 midpoint = (first + last)//2
229 229 nextpos = self.positions[midpoint]
230 230 candidate = self._getkey(nextpos)
231 231 r = _cmp(key, candidate)
232 232 if r == 0:
233 233 return (midpoint, True)
234 234 else:
235 235 if r < 0:
236 236 last = midpoint - 1
237 237 else:
238 238 first = midpoint + 1
239 239 return (first, False)
240 240
241 241 def __contains__(self, key):
242 242 return self.bsearch(key) != -1
243 243
244 244 def _getflags(self, data, needle, pos):
245 245 start = pos + 41
246 246 end = data.find("\n", start)
247 247 if end == -1:
248 248 end = len(data) - 1
249 249 if start == end:
250 250 return ''
251 251 return self.data[start:end]
252 252
253 253 def __getitem__(self, key):
254 254 if not isinstance(key, str):
255 255 raise TypeError("getitem: manifest keys must be a string.")
256 256 needle = self.bsearch(key)
257 257 if needle == -1:
258 258 raise KeyError
259 259 data, pos = self._get(needle)
260 260 if pos == -1:
261 261 return (data[1], data[2])
262 262 zeropos = data.find('\x00', pos)
263 263 assert 0 <= needle <= len(self.positions)
264 264 assert len(self.extrainfo) == len(self.positions)
265 265 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, 40)
266 266 flags = self._getflags(data, needle, zeropos)
267 267 return (hashval, flags)
268 268
269 269 def __delitem__(self, key):
270 270 needle, found = self.bsearch2(key)
271 271 if not found:
272 272 raise KeyError
273 273 cur = self.positions[needle]
274 274 self.positions = self.positions[:needle] + self.positions[needle + 1:]
275 275 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1:]
276 276 if cur >= 0:
277 277 self.data = self.data[:cur] + '\x00' + self.data[cur + 1:]
278 278
279 279 def __setitem__(self, key, value):
280 280 if not isinstance(key, str):
281 281 raise TypeError("setitem: manifest keys must be a string.")
282 282 if not isinstance(value, tuple) or len(value) != 2:
283 283 raise TypeError("Manifest values must be a tuple of (node, flags).")
284 284 hashval = value[0]
285 285 if not isinstance(hashval, str) or not 20 <= len(hashval) <= 22:
286 286 raise TypeError("node must be a 20-byte string")
287 287 flags = value[1]
288 288 if len(hashval) == 22:
289 289 hashval = hashval[:-1]
290 290 if not isinstance(flags, str) or len(flags) > 1:
291 291 raise TypeError("flags must a 0 or 1 byte string, got %r", flags)
292 292 needle, found = self.bsearch2(key)
293 293 if found:
294 294 # put the item
295 295 pos = self.positions[needle]
296 296 if pos < 0:
297 297 self.extradata[-pos - 1] = (key, hashval, value[1])
298 298 else:
299 299 # just don't bother
300 300 self.extradata.append((key, hashval, value[1]))
301 301 self.positions[needle] = -len(self.extradata)
302 302 else:
303 303 # not found, put it in with extra positions
304 304 self.extradata.append((key, hashval, value[1]))
305 305 self.positions = (self.positions[:needle] + [-len(self.extradata)]
306 306 + self.positions[needle:])
307 307 self.extrainfo = (self.extrainfo[:needle] + [0] +
308 308 self.extrainfo[needle:])
309 309
310 310 def copy(self):
311 311 # XXX call _compact like in C?
312 312 return _lazymanifest(self.data, self.positions, self.extrainfo,
313 313 self.extradata)
314 314
315 315 def _compact(self):
316 316 # hopefully not called TOO often
317 317 if len(self.extradata) == 0:
318 318 return
319 319 l = []
320 320 last_cut = 0
321 321 i = 0
322 322 offset = 0
323 323 self.extrainfo = [0] * len(self.positions)
324 324 while i < len(self.positions):
325 325 if self.positions[i] >= 0:
326 326 cur = self.positions[i]
327 327 last_cut = cur
328 328 while True:
329 329 self.positions[i] = offset
330 330 i += 1
331 331 if i == len(self.positions) or self.positions[i] < 0:
332 332 break
333 333 offset += self.positions[i] - cur
334 334 cur = self.positions[i]
335 335 end_cut = self.data.find('\n', cur)
336 336 if end_cut != -1:
337 337 end_cut += 1
338 338 offset += end_cut - cur
339 339 l.append(self.data[last_cut:end_cut])
340 340 else:
341 341 while i < len(self.positions) and self.positions[i] < 0:
342 342 cur = self.positions[i]
343 343 t = self.extradata[-cur - 1]
344 344 l.append(self._pack(t))
345 345 self.positions[i] = offset
346 346 if len(t[1]) > 20:
347 347 self.extrainfo[i] = ord(t[1][21])
348 348 offset += len(l[-1])
349 349 i += 1
350 350 self.data = ''.join(l)
351 351 self.extradata = []
352 352
353 353 def _pack(self, d):
354 354 return d[0] + '\x00' + d[1][:20].encode('hex') + d[2] + '\n'
355 355
356 356 def text(self):
357 357 self._compact()
358 358 return self.data
359 359
360 360 def diff(self, m2, clean=False):
361 361 '''Finds changes between the current manifest and m2.'''
362 362 # XXX think whether efficiency matters here
363 363 diff = {}
364 364
365 365 for fn, e1, flags in self.iterentries():
366 366 if fn not in m2:
367 367 diff[fn] = (e1, flags), (None, '')
368 368 else:
369 369 e2 = m2[fn]
370 370 if (e1, flags) != e2:
371 371 diff[fn] = (e1, flags), e2
372 372 elif clean:
373 373 diff[fn] = None
374 374
375 375 for fn, e2, flags in m2.iterentries():
376 376 if fn not in self:
377 377 diff[fn] = (None, ''), (e2, flags)
378 378
379 379 return diff
380 380
381 381 def iterentries(self):
382 382 return lazymanifestiterentries(self)
383 383
384 384 def iterkeys(self):
385 385 return lazymanifestiter(self)
386 386
387 387 def __iter__(self):
388 388 return lazymanifestiter(self)
389 389
390 390 def __len__(self):
391 391 return len(self.positions)
392 392
393 393 def filtercopy(self, filterfn):
394 394 # XXX should be optimized
395 395 c = _lazymanifest('')
396 396 for f, n, fl in self.iterentries():
397 397 if filterfn(f):
398 398 c[f] = n, fl
399 399 return c
400 400
401 401 try:
402 402 _lazymanifest = parsers.lazymanifest
403 403 except AttributeError:
404 404 pass
405 405
406 406 class manifestdict(object):
407 407 def __init__(self, data=''):
408 408 if data.startswith('\0'):
409 409 #_lazymanifest can not parse v2
410 410 self._lm = _lazymanifest('')
411 411 for f, n, fl in _parsev2(data):
412 412 self._lm[f] = n, fl
413 413 else:
414 414 self._lm = _lazymanifest(data)
415 415
416 416 def __getitem__(self, key):
417 417 return self._lm[key][0]
418 418
419 419 def find(self, key):
420 420 return self._lm[key]
421 421
422 422 def __len__(self):
423 423 return len(self._lm)
424 424
425 425 def __nonzero__(self):
426 426 # nonzero is covered by the __len__ function, but implementing it here
427 427 # makes it easier for extensions to override.
428 428 return len(self._lm) != 0
429 429
430 430 def __setitem__(self, key, node):
431 431 self._lm[key] = node, self.flags(key, '')
432 432
433 433 def __contains__(self, key):
434 434 return key in self._lm
435 435
436 436 def __delitem__(self, key):
437 437 del self._lm[key]
438 438
439 439 def __iter__(self):
440 440 return self._lm.__iter__()
441 441
442 442 def iterkeys(self):
443 443 return self._lm.iterkeys()
444 444
445 445 def keys(self):
446 446 return list(self.iterkeys())
447 447
448 def filesnotin(self, m2):
448 def filesnotin(self, m2, match=None):
449 449 '''Set of files in this manifest that are not in the other'''
450 if match:
451 m1 = self.matches(match)
452 m2 = m2.matches(match)
453 return m1.filesnotin(m2)
450 454 diff = self.diff(m2)
451 455 files = set(filepath
452 456 for filepath, hashflags in diff.iteritems()
453 457 if hashflags[1][0] is None)
454 458 return files
455 459
456 460 @propertycache
457 461 def _dirs(self):
458 462 return util.dirs(self)
459 463
460 464 def dirs(self):
461 465 return self._dirs
462 466
463 467 def hasdir(self, dir):
464 468 return dir in self._dirs
465 469
466 470 def _filesfastpath(self, match):
467 471 '''Checks whether we can correctly and quickly iterate over matcher
468 472 files instead of over manifest files.'''
469 473 files = match.files()
470 474 return (len(files) < 100 and (match.isexact() or
471 475 (match.prefix() and all(fn in self for fn in files))))
472 476
473 477 def walk(self, match):
474 478 '''Generates matching file names.
475 479
476 480 Equivalent to manifest.matches(match).iterkeys(), but without creating
477 481 an entirely new manifest.
478 482
479 483 It also reports nonexistent files by marking them bad with match.bad().
480 484 '''
481 485 if match.always():
482 486 for f in iter(self):
483 487 yield f
484 488 return
485 489
486 490 fset = set(match.files())
487 491
488 492 # avoid the entire walk if we're only looking for specific files
489 493 if self._filesfastpath(match):
490 494 for fn in sorted(fset):
491 495 yield fn
492 496 return
493 497
494 498 for fn in self:
495 499 if fn in fset:
496 500 # specified pattern is the exact name
497 501 fset.remove(fn)
498 502 if match(fn):
499 503 yield fn
500 504
501 505 # for dirstate.walk, files=['.'] means "walk the whole tree".
502 506 # follow that here, too
503 507 fset.discard('.')
504 508
505 509 for fn in sorted(fset):
506 510 if not self.hasdir(fn):
507 511 match.bad(fn, None)
508 512
509 513 def matches(self, match):
510 514 '''generate a new manifest filtered by the match argument'''
511 515 if match.always():
512 516 return self.copy()
513 517
514 518 if self._filesfastpath(match):
515 519 m = manifestdict()
516 520 lm = self._lm
517 521 for fn in match.files():
518 522 if fn in lm:
519 523 m._lm[fn] = lm[fn]
520 524 return m
521 525
522 526 m = manifestdict()
523 527 m._lm = self._lm.filtercopy(match)
524 528 return m
525 529
526 def diff(self, m2, clean=False):
530 def diff(self, m2, match=None, clean=False):
527 531 '''Finds changes between the current manifest and m2.
528 532
529 533 Args:
530 534 m2: the manifest to which this manifest should be compared.
531 535 clean: if true, include files unchanged between these manifests
532 536 with a None value in the returned dictionary.
533 537
534 538 The result is returned as a dict with filename as key and
535 539 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
536 540 nodeid in the current/other manifest and fl1/fl2 is the flag
537 541 in the current/other manifest. Where the file does not exist,
538 542 the nodeid will be None and the flags will be the empty
539 543 string.
540 544 '''
545 if match:
546 m1 = self.matches(match)
547 m2 = m2.matches(match)
548 return m1.diff(m2, clean=clean)
541 549 return self._lm.diff(m2._lm, clean)
542 550
543 551 def setflag(self, key, flag):
544 552 self._lm[key] = self[key], flag
545 553
546 554 def get(self, key, default=None):
547 555 try:
548 556 return self._lm[key][0]
549 557 except KeyError:
550 558 return default
551 559
552 560 def flags(self, key, default=''):
553 561 try:
554 562 return self._lm[key][1]
555 563 except KeyError:
556 564 return default
557 565
558 566 def copy(self):
559 567 c = manifestdict()
560 568 c._lm = self._lm.copy()
561 569 return c
562 570
563 571 def iteritems(self):
564 572 return (x[:2] for x in self._lm.iterentries())
565 573
566 574 def iterentries(self):
567 575 return self._lm.iterentries()
568 576
569 577 def text(self, usemanifestv2=False):
570 578 if usemanifestv2:
571 579 return _textv2(self._lm.iterentries())
572 580 else:
573 581 # use (probably) native version for v1
574 582 return self._lm.text()
575 583
576 584 def fastdelta(self, base, changes):
577 585 """Given a base manifest text as an array.array and a list of changes
578 586 relative to that text, compute a delta that can be used by revlog.
579 587 """
580 588 delta = []
581 589 dstart = None
582 590 dend = None
583 591 dline = [""]
584 592 start = 0
585 593 # zero copy representation of base as a buffer
586 594 addbuf = util.buffer(base)
587 595
588 596 changes = list(changes)
589 597 if len(changes) < 1000:
590 598 # start with a readonly loop that finds the offset of
591 599 # each line and creates the deltas
592 600 for f, todelete in changes:
593 601 # bs will either be the index of the item or the insert point
594 602 start, end = _msearch(addbuf, f, start)
595 603 if not todelete:
596 604 h, fl = self._lm[f]
597 605 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
598 606 else:
599 607 if start == end:
600 608 # item we want to delete was not found, error out
601 609 raise AssertionError(
602 610 _("failed to remove %s from manifest") % f)
603 611 l = ""
604 612 if dstart is not None and dstart <= start and dend >= start:
605 613 if dend < end:
606 614 dend = end
607 615 if l:
608 616 dline.append(l)
609 617 else:
610 618 if dstart is not None:
611 619 delta.append([dstart, dend, "".join(dline)])
612 620 dstart = start
613 621 dend = end
614 622 dline = [l]
615 623
616 624 if dstart is not None:
617 625 delta.append([dstart, dend, "".join(dline)])
618 626 # apply the delta to the base, and get a delta for addrevision
619 627 deltatext, arraytext = _addlistdelta(base, delta)
620 628 else:
621 629 # For large changes, it's much cheaper to just build the text and
622 630 # diff it.
623 631 arraytext = array.array('c', self.text())
624 632 deltatext = mdiff.textdiff(base, arraytext)
625 633
626 634 return arraytext, deltatext
627 635
628 636 def _msearch(m, s, lo=0, hi=None):
629 637 '''return a tuple (start, end) that says where to find s within m.
630 638
631 639 If the string is found m[start:end] are the line containing
632 640 that string. If start == end the string was not found and
633 641 they indicate the proper sorted insertion point.
634 642
635 643 m should be a buffer or a string
636 644 s is a string'''
637 645 def advance(i, c):
638 646 while i < lenm and m[i] != c:
639 647 i += 1
640 648 return i
641 649 if not s:
642 650 return (lo, lo)
643 651 lenm = len(m)
644 652 if not hi:
645 653 hi = lenm
646 654 while lo < hi:
647 655 mid = (lo + hi) // 2
648 656 start = mid
649 657 while start > 0 and m[start - 1] != '\n':
650 658 start -= 1
651 659 end = advance(start, '\0')
652 660 if m[start:end] < s:
653 661 # we know that after the null there are 40 bytes of sha1
654 662 # this translates to the bisect lo = mid + 1
655 663 lo = advance(end + 40, '\n') + 1
656 664 else:
657 665 # this translates to the bisect hi = mid
658 666 hi = start
659 667 end = advance(lo, '\0')
660 668 found = m[lo:end]
661 669 if s == found:
662 670 # we know that after the null there are 40 bytes of sha1
663 671 end = advance(end + 40, '\n')
664 672 return (lo, end + 1)
665 673 else:
666 674 return (lo, lo)
667 675
668 676 def _checkforbidden(l):
669 677 """Check filenames for illegal characters."""
670 678 for f in l:
671 679 if '\n' in f or '\r' in f:
672 680 raise error.RevlogError(
673 681 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
674 682
675 683
676 684 # apply the changes collected during the bisect loop to our addlist
677 685 # return a delta suitable for addrevision
678 686 def _addlistdelta(addlist, x):
679 687 # for large addlist arrays, building a new array is cheaper
680 688 # than repeatedly modifying the existing one
681 689 currentposition = 0
682 690 newaddlist = array.array('c')
683 691
684 692 for start, end, content in x:
685 693 newaddlist += addlist[currentposition:start]
686 694 if content:
687 695 newaddlist += array.array('c', content)
688 696
689 697 currentposition = end
690 698
691 699 newaddlist += addlist[currentposition:]
692 700
693 701 deltatext = "".join(struct.pack(">lll", start, end, len(content))
694 702 + content for start, end, content in x)
695 703 return deltatext, newaddlist
696 704
697 705 def _splittopdir(f):
698 706 if '/' in f:
699 707 dir, subpath = f.split('/', 1)
700 708 return dir + '/', subpath
701 709 else:
702 710 return '', f
703 711
704 712 _noop = lambda s: None
705 713
706 714 class treemanifest(object):
707 715 def __init__(self, dir='', text=''):
708 716 self._dir = dir
709 717 self._node = revlog.nullid
710 718 self._loadfunc = _noop
711 719 self._copyfunc = _noop
712 720 self._dirty = False
713 721 self._dirs = {}
714 722 # Using _lazymanifest here is a little slower than plain old dicts
715 723 self._files = {}
716 724 self._flags = {}
717 725 if text:
718 726 def readsubtree(subdir, subm):
719 727 raise AssertionError('treemanifest constructor only accepts '
720 728 'flat manifests')
721 729 self.parse(text, readsubtree)
722 730 self._dirty = True # Mark flat manifest dirty after parsing
723 731
724 732 def _subpath(self, path):
725 733 return self._dir + path
726 734
727 735 def __len__(self):
728 736 self._load()
729 737 size = len(self._files)
730 738 for m in self._dirs.values():
731 739 size += m.__len__()
732 740 return size
733 741
734 742 def _isempty(self):
735 743 self._load() # for consistency; already loaded by all callers
736 744 return (not self._files and (not self._dirs or
737 745 all(m._isempty() for m in self._dirs.values())))
738 746
739 747 def __repr__(self):
740 748 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
741 749 (self._dir, revlog.hex(self._node),
742 750 bool(self._loadfunc is _noop),
743 751 self._dirty, id(self)))
744 752
745 753 def dir(self):
746 754 '''The directory that this tree manifest represents, including a
747 755 trailing '/'. Empty string for the repo root directory.'''
748 756 return self._dir
749 757
750 758 def node(self):
751 759 '''This node of this instance. nullid for unsaved instances. Should
752 760 be updated when the instance is read or written from a revlog.
753 761 '''
754 762 assert not self._dirty
755 763 return self._node
756 764
757 765 def setnode(self, node):
758 766 self._node = node
759 767 self._dirty = False
760 768
761 769 def iterentries(self):
762 770 self._load()
763 771 for p, n in sorted(self._dirs.items() + self._files.items()):
764 772 if p in self._files:
765 773 yield self._subpath(p), n, self._flags.get(p, '')
766 774 else:
767 775 for x in n.iterentries():
768 776 yield x
769 777
770 778 def iteritems(self):
771 779 self._load()
772 780 for p, n in sorted(self._dirs.items() + self._files.items()):
773 781 if p in self._files:
774 782 yield self._subpath(p), n
775 783 else:
776 784 for f, sn in n.iteritems():
777 785 yield f, sn
778 786
779 787 def iterkeys(self):
780 788 self._load()
781 789 for p in sorted(self._dirs.keys() + self._files.keys()):
782 790 if p in self._files:
783 791 yield self._subpath(p)
784 792 else:
785 793 for f in self._dirs[p].iterkeys():
786 794 yield f
787 795
788 796 def keys(self):
789 797 return list(self.iterkeys())
790 798
791 799 def __iter__(self):
792 800 return self.iterkeys()
793 801
794 802 def __contains__(self, f):
795 803 if f is None:
796 804 return False
797 805 self._load()
798 806 dir, subpath = _splittopdir(f)
799 807 if dir:
800 808 if dir not in self._dirs:
801 809 return False
802 810 return self._dirs[dir].__contains__(subpath)
803 811 else:
804 812 return f in self._files
805 813
806 814 def get(self, f, default=None):
807 815 self._load()
808 816 dir, subpath = _splittopdir(f)
809 817 if dir:
810 818 if dir not in self._dirs:
811 819 return default
812 820 return self._dirs[dir].get(subpath, default)
813 821 else:
814 822 return self._files.get(f, default)
815 823
816 824 def __getitem__(self, f):
817 825 self._load()
818 826 dir, subpath = _splittopdir(f)
819 827 if dir:
820 828 return self._dirs[dir].__getitem__(subpath)
821 829 else:
822 830 return self._files[f]
823 831
824 832 def flags(self, f):
825 833 self._load()
826 834 dir, subpath = _splittopdir(f)
827 835 if dir:
828 836 if dir not in self._dirs:
829 837 return ''
830 838 return self._dirs[dir].flags(subpath)
831 839 else:
832 840 if f in self._dirs:
833 841 return ''
834 842 return self._flags.get(f, '')
835 843
836 844 def find(self, f):
837 845 self._load()
838 846 dir, subpath = _splittopdir(f)
839 847 if dir:
840 848 return self._dirs[dir].find(subpath)
841 849 else:
842 850 return self._files[f], self._flags.get(f, '')
843 851
844 852 def __delitem__(self, f):
845 853 self._load()
846 854 dir, subpath = _splittopdir(f)
847 855 if dir:
848 856 self._dirs[dir].__delitem__(subpath)
849 857 # If the directory is now empty, remove it
850 858 if self._dirs[dir]._isempty():
851 859 del self._dirs[dir]
852 860 else:
853 861 del self._files[f]
854 862 if f in self._flags:
855 863 del self._flags[f]
856 864 self._dirty = True
857 865
858 866 def __setitem__(self, f, n):
859 867 assert n is not None
860 868 self._load()
861 869 dir, subpath = _splittopdir(f)
862 870 if dir:
863 871 if dir not in self._dirs:
864 872 self._dirs[dir] = treemanifest(self._subpath(dir))
865 873 self._dirs[dir].__setitem__(subpath, n)
866 874 else:
867 875 self._files[f] = n[:21] # to match manifestdict's behavior
868 876 self._dirty = True
869 877
870 878 def _load(self):
871 879 if self._loadfunc is not _noop:
872 880 lf, self._loadfunc = self._loadfunc, _noop
873 881 lf(self)
874 882 elif self._copyfunc is not _noop:
875 883 cf, self._copyfunc = self._copyfunc, _noop
876 884 cf(self)
877 885
878 886 def setflag(self, f, flags):
879 887 """Set the flags (symlink, executable) for path f."""
880 888 self._load()
881 889 dir, subpath = _splittopdir(f)
882 890 if dir:
883 891 if dir not in self._dirs:
884 892 self._dirs[dir] = treemanifest(self._subpath(dir))
885 893 self._dirs[dir].setflag(subpath, flags)
886 894 else:
887 895 self._flags[f] = flags
888 896 self._dirty = True
889 897
890 898 def copy(self):
891 899 copy = treemanifest(self._dir)
892 900 copy._node = self._node
893 901 copy._dirty = self._dirty
894 902 if self._copyfunc is _noop:
895 903 def _copyfunc(s):
896 904 self._load()
897 905 for d in self._dirs:
898 906 s._dirs[d] = self._dirs[d].copy()
899 907 s._files = dict.copy(self._files)
900 908 s._flags = dict.copy(self._flags)
901 909 if self._loadfunc is _noop:
902 910 _copyfunc(copy)
903 911 else:
904 912 copy._copyfunc = _copyfunc
905 913 else:
906 914 copy._copyfunc = self._copyfunc
907 915 return copy
908 916
909 def filesnotin(self, m2):
917 def filesnotin(self, m2, match=None):
910 918 '''Set of files in this manifest that are not in the other'''
919 if match:
920 m1 = self.matches(match)
921 m2 = m2.matches(match)
922 return m1.filesnotin(m2)
923
911 924 files = set()
912 925 def _filesnotin(t1, t2):
913 926 if t1._node == t2._node and not t1._dirty and not t2._dirty:
914 927 return
915 928 t1._load()
916 929 t2._load()
917 930 for d, m1 in t1._dirs.iteritems():
918 931 if d in t2._dirs:
919 932 m2 = t2._dirs[d]
920 933 _filesnotin(m1, m2)
921 934 else:
922 935 files.update(m1.iterkeys())
923 936
924 937 for fn in t1._files.iterkeys():
925 938 if fn not in t2._files:
926 939 files.add(t1._subpath(fn))
927 940
928 941 _filesnotin(self, m2)
929 942 return files
930 943
931 944 @propertycache
932 945 def _alldirs(self):
933 946 return util.dirs(self)
934 947
935 948 def dirs(self):
936 949 return self._alldirs
937 950
938 951 def hasdir(self, dir):
939 952 self._load()
940 953 topdir, subdir = _splittopdir(dir)
941 954 if topdir:
942 955 if topdir in self._dirs:
943 956 return self._dirs[topdir].hasdir(subdir)
944 957 return False
945 958 return (dir + '/') in self._dirs
946 959
947 960 def walk(self, match):
948 961 '''Generates matching file names.
949 962
950 963 Equivalent to manifest.matches(match).iterkeys(), but without creating
951 964 an entirely new manifest.
952 965
953 966 It also reports nonexistent files by marking them bad with match.bad().
954 967 '''
955 968 if match.always():
956 969 for f in iter(self):
957 970 yield f
958 971 return
959 972
960 973 fset = set(match.files())
961 974
962 975 for fn in self._walk(match):
963 976 if fn in fset:
964 977 # specified pattern is the exact name
965 978 fset.remove(fn)
966 979 yield fn
967 980
968 981 # for dirstate.walk, files=['.'] means "walk the whole tree".
969 982 # follow that here, too
970 983 fset.discard('.')
971 984
972 985 for fn in sorted(fset):
973 986 if not self.hasdir(fn):
974 987 match.bad(fn, None)
975 988
976 989 def _walk(self, match):
977 990 '''Recursively generates matching file names for walk().'''
978 991 if not match.visitdir(self._dir[:-1] or '.'):
979 992 return
980 993
981 994 # yield this dir's files and walk its submanifests
982 995 self._load()
983 996 for p in sorted(self._dirs.keys() + self._files.keys()):
984 997 if p in self._files:
985 998 fullp = self._subpath(p)
986 999 if match(fullp):
987 1000 yield fullp
988 1001 else:
989 1002 for f in self._dirs[p]._walk(match):
990 1003 yield f
991 1004
992 1005 def matches(self, match):
993 1006 '''generate a new manifest filtered by the match argument'''
994 1007 if match.always():
995 1008 return self.copy()
996 1009
997 1010 return self._matches(match)
998 1011
999 1012 def _matches(self, match):
1000 1013 '''recursively generate a new manifest filtered by the match argument.
1001 1014 '''
1002 1015
1003 1016 visit = match.visitdir(self._dir[:-1] or '.')
1004 1017 if visit == 'all':
1005 1018 return self.copy()
1006 1019 ret = treemanifest(self._dir)
1007 1020 if not visit:
1008 1021 return ret
1009 1022
1010 1023 self._load()
1011 1024 for fn in self._files:
1012 1025 fullp = self._subpath(fn)
1013 1026 if not match(fullp):
1014 1027 continue
1015 1028 ret._files[fn] = self._files[fn]
1016 1029 if fn in self._flags:
1017 1030 ret._flags[fn] = self._flags[fn]
1018 1031
1019 1032 for dir, subm in self._dirs.iteritems():
1020 1033 m = subm._matches(match)
1021 1034 if not m._isempty():
1022 1035 ret._dirs[dir] = m
1023 1036
1024 1037 if not ret._isempty():
1025 1038 ret._dirty = True
1026 1039 return ret
1027 1040
1028 def diff(self, m2, clean=False):
1041 def diff(self, m2, match=None, clean=False):
1029 1042 '''Finds changes between the current manifest and m2.
1030 1043
1031 1044 Args:
1032 1045 m2: the manifest to which this manifest should be compared.
1033 1046 clean: if true, include files unchanged between these manifests
1034 1047 with a None value in the returned dictionary.
1035 1048
1036 1049 The result is returned as a dict with filename as key and
1037 1050 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1038 1051 nodeid in the current/other manifest and fl1/fl2 is the flag
1039 1052 in the current/other manifest. Where the file does not exist,
1040 1053 the nodeid will be None and the flags will be the empty
1041 1054 string.
1042 1055 '''
1056 if match:
1057 m1 = self.matches(match)
1058 m2 = m2.matches(match)
1059 return m1.diff(m2, clean=clean)
1043 1060 result = {}
1044 1061 emptytree = treemanifest()
1045 1062 def _diff(t1, t2):
1046 1063 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1047 1064 return
1048 1065 t1._load()
1049 1066 t2._load()
1050 1067 for d, m1 in t1._dirs.iteritems():
1051 1068 m2 = t2._dirs.get(d, emptytree)
1052 1069 _diff(m1, m2)
1053 1070
1054 1071 for d, m2 in t2._dirs.iteritems():
1055 1072 if d not in t1._dirs:
1056 1073 _diff(emptytree, m2)
1057 1074
1058 1075 for fn, n1 in t1._files.iteritems():
1059 1076 fl1 = t1._flags.get(fn, '')
1060 1077 n2 = t2._files.get(fn, None)
1061 1078 fl2 = t2._flags.get(fn, '')
1062 1079 if n1 != n2 or fl1 != fl2:
1063 1080 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1064 1081 elif clean:
1065 1082 result[t1._subpath(fn)] = None
1066 1083
1067 1084 for fn, n2 in t2._files.iteritems():
1068 1085 if fn not in t1._files:
1069 1086 fl2 = t2._flags.get(fn, '')
1070 1087 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1071 1088
1072 1089 _diff(self, m2)
1073 1090 return result
1074 1091
1075 1092 def unmodifiedsince(self, m2):
1076 1093 return not self._dirty and not m2._dirty and self._node == m2._node
1077 1094
1078 1095 def parse(self, text, readsubtree):
1079 1096 for f, n, fl in _parse(text):
1080 1097 if fl == 't':
1081 1098 f = f + '/'
1082 1099 self._dirs[f] = readsubtree(self._subpath(f), n)
1083 1100 elif '/' in f:
1084 1101 # This is a flat manifest, so use __setitem__ and setflag rather
1085 1102 # than assigning directly to _files and _flags, so we can
1086 1103 # assign a path in a subdirectory, and to mark dirty (compared
1087 1104 # to nullid).
1088 1105 self[f] = n
1089 1106 if fl:
1090 1107 self.setflag(f, fl)
1091 1108 else:
1092 1109 # Assigning to _files and _flags avoids marking as dirty,
1093 1110 # and should be a little faster.
1094 1111 self._files[f] = n
1095 1112 if fl:
1096 1113 self._flags[f] = fl
1097 1114
1098 1115 def text(self, usemanifestv2=False):
1099 1116 """Get the full data of this manifest as a bytestring."""
1100 1117 self._load()
1101 1118 return _text(self.iterentries(), usemanifestv2)
1102 1119
1103 1120 def dirtext(self, usemanifestv2=False):
1104 1121 """Get the full data of this directory as a bytestring. Make sure that
1105 1122 any submanifests have been written first, so their nodeids are correct.
1106 1123 """
1107 1124 self._load()
1108 1125 flags = self.flags
1109 1126 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1110 1127 files = [(f, self._files[f], flags(f)) for f in self._files]
1111 1128 return _text(sorted(dirs + files), usemanifestv2)
1112 1129
1113 1130 def read(self, gettext, readsubtree):
1114 1131 def _load_for_read(s):
1115 1132 s.parse(gettext(), readsubtree)
1116 1133 s._dirty = False
1117 1134 self._loadfunc = _load_for_read
1118 1135
1119 1136 def writesubtrees(self, m1, m2, writesubtree):
1120 1137 self._load() # for consistency; should never have any effect here
1121 1138 m1._load()
1122 1139 m2._load()
1123 1140 emptytree = treemanifest()
1124 1141 for d, subm in self._dirs.iteritems():
1125 1142 subp1 = m1._dirs.get(d, emptytree)._node
1126 1143 subp2 = m2._dirs.get(d, emptytree)._node
1127 1144 if subp1 == revlog.nullid:
1128 1145 subp1, subp2 = subp2, subp1
1129 1146 writesubtree(subm, subp1, subp2)
1130 1147
1131 1148 class manifestrevlog(revlog.revlog):
1132 1149 '''A revlog that stores manifest texts. This is responsible for caching the
1133 1150 full-text manifest contents.
1134 1151 '''
1135 1152 def __init__(self, opener, dir='', dirlogcache=None, indexfile=None):
1136 1153 """Constructs a new manifest revlog
1137 1154
1138 1155 `indexfile` - used by extensions to have two manifests at once, like
1139 1156 when transitioning between flatmanifeset and treemanifests.
1140 1157 """
1141 1158 # During normal operations, we expect to deal with not more than four
1142 1159 # revs at a time (such as during commit --amend). When rebasing large
1143 1160 # stacks of commits, the number can go up, hence the config knob below.
1144 1161 cachesize = 4
1145 1162 usetreemanifest = False
1146 1163 usemanifestv2 = False
1147 1164 opts = getattr(opener, 'options', None)
1148 1165 if opts is not None:
1149 1166 cachesize = opts.get('manifestcachesize', cachesize)
1150 1167 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1151 1168 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
1152 1169
1153 1170 self._treeondisk = usetreemanifest
1154 1171 self._usemanifestv2 = usemanifestv2
1155 1172
1156 1173 self._fulltextcache = util.lrucachedict(cachesize)
1157 1174
1158 1175 if dir:
1159 1176 assert self._treeondisk, 'opts is %r' % opts
1160 1177 if not dir.endswith('/'):
1161 1178 dir = dir + '/'
1162 1179
1163 1180 if indexfile is None:
1164 1181 indexfile = '00manifest.i'
1165 1182 if dir:
1166 1183 indexfile = "meta/" + dir + indexfile
1167 1184
1168 1185 self._dir = dir
1169 1186 # The dirlogcache is kept on the root manifest log
1170 1187 if dir:
1171 1188 self._dirlogcache = dirlogcache
1172 1189 else:
1173 1190 self._dirlogcache = {'': self}
1174 1191
1175 1192 super(manifestrevlog, self).__init__(opener, indexfile,
1176 1193 checkambig=bool(dir))
1177 1194
1178 1195 @property
1179 1196 def fulltextcache(self):
1180 1197 return self._fulltextcache
1181 1198
1182 1199 def clearcaches(self):
1183 1200 super(manifestrevlog, self).clearcaches()
1184 1201 self._fulltextcache.clear()
1185 1202 self._dirlogcache = {'': self}
1186 1203
1187 1204 def dirlog(self, dir):
1188 1205 if dir:
1189 1206 assert self._treeondisk
1190 1207 if dir not in self._dirlogcache:
1191 1208 self._dirlogcache[dir] = manifestrevlog(self.opener, dir,
1192 1209 self._dirlogcache)
1193 1210 return self._dirlogcache[dir]
1194 1211
1195 1212 def add(self, m, transaction, link, p1, p2, added, removed, readtree=None):
1196 1213 if (p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta')
1197 1214 and not self._usemanifestv2):
1198 1215 # If our first parent is in the manifest cache, we can
1199 1216 # compute a delta here using properties we know about the
1200 1217 # manifest up-front, which may save time later for the
1201 1218 # revlog layer.
1202 1219
1203 1220 _checkforbidden(added)
1204 1221 # combine the changed lists into one sorted iterator
1205 1222 work = heapq.merge([(x, False) for x in added],
1206 1223 [(x, True) for x in removed])
1207 1224
1208 1225 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1209 1226 cachedelta = self.rev(p1), deltatext
1210 1227 text = util.buffer(arraytext)
1211 1228 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1212 1229 else:
1213 1230 # The first parent manifest isn't already loaded, so we'll
1214 1231 # just encode a fulltext of the manifest and pass that
1215 1232 # through to the revlog layer, and let it handle the delta
1216 1233 # process.
1217 1234 if self._treeondisk:
1218 1235 assert readtree, "readtree must be set for treemanifest writes"
1219 1236 m1 = readtree(self._dir, p1)
1220 1237 m2 = readtree(self._dir, p2)
1221 1238 n = self._addtree(m, transaction, link, m1, m2, readtree)
1222 1239 arraytext = None
1223 1240 else:
1224 1241 text = m.text(self._usemanifestv2)
1225 1242 n = self.addrevision(text, transaction, link, p1, p2)
1226 1243 arraytext = array.array('c', text)
1227 1244
1228 1245 if arraytext is not None:
1229 1246 self.fulltextcache[n] = arraytext
1230 1247
1231 1248 return n
1232 1249
1233 1250 def _addtree(self, m, transaction, link, m1, m2, readtree):
1234 1251 # If the manifest is unchanged compared to one parent,
1235 1252 # don't write a new revision
1236 1253 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1237 1254 return m.node()
1238 1255 def writesubtree(subm, subp1, subp2):
1239 1256 sublog = self.dirlog(subm.dir())
1240 1257 sublog.add(subm, transaction, link, subp1, subp2, None, None,
1241 1258 readtree=readtree)
1242 1259 m.writesubtrees(m1, m2, writesubtree)
1243 1260 text = m.dirtext(self._usemanifestv2)
1244 1261 # Double-check whether contents are unchanged to one parent
1245 1262 if text == m1.dirtext(self._usemanifestv2):
1246 1263 n = m1.node()
1247 1264 elif text == m2.dirtext(self._usemanifestv2):
1248 1265 n = m2.node()
1249 1266 else:
1250 1267 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1251 1268 # Save nodeid so parent manifest can calculate its nodeid
1252 1269 m.setnode(n)
1253 1270 return n
1254 1271
1255 1272 class manifestlog(object):
1256 1273 """A collection class representing the collection of manifest snapshots
1257 1274 referenced by commits in the repository.
1258 1275
1259 1276 In this situation, 'manifest' refers to the abstract concept of a snapshot
1260 1277 of the list of files in the given commit. Consumers of the output of this
1261 1278 class do not care about the implementation details of the actual manifests
1262 1279 they receive (i.e. tree or flat or lazily loaded, etc)."""
1263 1280 def __init__(self, opener, repo):
1264 1281 usetreemanifest = False
1265 1282 cachesize = 4
1266 1283
1267 1284 opts = getattr(opener, 'options', None)
1268 1285 if opts is not None:
1269 1286 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1270 1287 cachesize = opts.get('manifestcachesize', cachesize)
1271 1288 self._treeinmem = usetreemanifest
1272 1289
1273 1290 self._oldmanifest = repo._constructmanifest()
1274 1291 self._revlog = self._oldmanifest
1275 1292
1276 1293 # A cache of the manifestctx or treemanifestctx for each directory
1277 1294 self._dirmancache = {}
1278 1295 self._dirmancache[''] = util.lrucachedict(cachesize)
1279 1296
1280 1297 self.cachesize = cachesize
1281 1298
1282 1299 def __getitem__(self, node):
1283 1300 """Retrieves the manifest instance for the given node. Throws a
1284 1301 LookupError if not found.
1285 1302 """
1286 1303 return self.get('', node)
1287 1304
1288 1305 def get(self, dir, node, verify=True):
1289 1306 """Retrieves the manifest instance for the given node. Throws a
1290 1307 LookupError if not found.
1291 1308
1292 1309 `verify` - if True an exception will be thrown if the node is not in
1293 1310 the revlog
1294 1311 """
1295 1312 if node in self._dirmancache.get(dir, ()):
1296 1313 cachemf = self._dirmancache[dir][node]
1297 1314 # The old manifest may put non-ctx manifests in the cache, so
1298 1315 # skip those since they don't implement the full api.
1299 1316 if (isinstance(cachemf, manifestctx) or
1300 1317 isinstance(cachemf, treemanifestctx)):
1301 1318 return cachemf
1302 1319
1303 1320 if dir:
1304 1321 if self._revlog._treeondisk:
1305 1322 if verify:
1306 1323 dirlog = self._revlog.dirlog(dir)
1307 1324 if node not in dirlog.nodemap:
1308 1325 raise LookupError(node, dirlog.indexfile,
1309 1326 _('no node'))
1310 1327 m = treemanifestctx(self, dir, node)
1311 1328 else:
1312 1329 raise error.Abort(
1313 1330 _("cannot ask for manifest directory '%s' in a flat "
1314 1331 "manifest") % dir)
1315 1332 else:
1316 1333 if verify:
1317 1334 if node not in self._revlog.nodemap:
1318 1335 raise LookupError(node, self._revlog.indexfile,
1319 1336 _('no node'))
1320 1337 if self._treeinmem:
1321 1338 m = treemanifestctx(self, '', node)
1322 1339 else:
1323 1340 m = manifestctx(self, node)
1324 1341
1325 1342 if node != revlog.nullid:
1326 1343 mancache = self._dirmancache.get(dir)
1327 1344 if not mancache:
1328 1345 mancache = util.lrucachedict(self.cachesize)
1329 1346 self._dirmancache[dir] = mancache
1330 1347 mancache[node] = m
1331 1348 return m
1332 1349
1333 1350 def clearcaches(self):
1334 1351 self._dirmancache.clear()
1335 1352 self._revlog.clearcaches()
1336 1353
1337 1354 class memmanifestctx(object):
1338 1355 def __init__(self, manifestlog):
1339 1356 self._manifestlog = manifestlog
1340 1357 self._manifestdict = manifestdict()
1341 1358
1342 1359 def _revlog(self):
1343 1360 return self._manifestlog._revlog
1344 1361
1345 1362 def new(self):
1346 1363 return memmanifestctx(self._manifestlog)
1347 1364
1348 1365 def copy(self):
1349 1366 memmf = memmanifestctx(self._manifestlog)
1350 1367 memmf._manifestdict = self.read().copy()
1351 1368 return memmf
1352 1369
1353 1370 def read(self):
1354 1371 return self._manifestdict
1355 1372
1356 1373 def write(self, transaction, link, p1, p2, added, removed):
1357 1374 return self._revlog().add(self._manifestdict, transaction, link, p1, p2,
1358 1375 added, removed)
1359 1376
1360 1377 class manifestctx(object):
1361 1378 """A class representing a single revision of a manifest, including its
1362 1379 contents, its parent revs, and its linkrev.
1363 1380 """
1364 1381 def __init__(self, manifestlog, node):
1365 1382 self._manifestlog = manifestlog
1366 1383 self._data = None
1367 1384
1368 1385 self._node = node
1369 1386
1370 1387 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1371 1388 # but let's add it later when something needs it and we can load it
1372 1389 # lazily.
1373 1390 #self.p1, self.p2 = revlog.parents(node)
1374 1391 #rev = revlog.rev(node)
1375 1392 #self.linkrev = revlog.linkrev(rev)
1376 1393
1377 1394 def _revlog(self):
1378 1395 return self._manifestlog._revlog
1379 1396
1380 1397 def node(self):
1381 1398 return self._node
1382 1399
1383 1400 def new(self):
1384 1401 return memmanifestctx(self._manifestlog)
1385 1402
1386 1403 def copy(self):
1387 1404 memmf = memmanifestctx(self._manifestlog)
1388 1405 memmf._manifestdict = self.read().copy()
1389 1406 return memmf
1390 1407
1391 1408 @propertycache
1392 1409 def parents(self):
1393 1410 return self._revlog().parents(self._node)
1394 1411
1395 1412 def read(self):
1396 1413 if self._data is None:
1397 1414 if self._node == revlog.nullid:
1398 1415 self._data = manifestdict()
1399 1416 else:
1400 1417 rl = self._revlog()
1401 1418 text = rl.revision(self._node)
1402 1419 arraytext = array.array('c', text)
1403 1420 rl._fulltextcache[self._node] = arraytext
1404 1421 self._data = manifestdict(text)
1405 1422 return self._data
1406 1423
1407 1424 def readfast(self, shallow=False):
1408 1425 '''Calls either readdelta or read, based on which would be less work.
1409 1426 readdelta is called if the delta is against the p1, and therefore can be
1410 1427 read quickly.
1411 1428
1412 1429 If `shallow` is True, nothing changes since this is a flat manifest.
1413 1430 '''
1414 1431 rl = self._revlog()
1415 1432 r = rl.rev(self._node)
1416 1433 deltaparent = rl.deltaparent(r)
1417 1434 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1418 1435 return self.readdelta()
1419 1436 return self.read()
1420 1437
1421 1438 def readdelta(self, shallow=False):
1422 1439 '''Returns a manifest containing just the entries that are present
1423 1440 in this manifest, but not in its p1 manifest. This is efficient to read
1424 1441 if the revlog delta is already p1.
1425 1442
1426 1443 Changing the value of `shallow` has no effect on flat manifests.
1427 1444 '''
1428 1445 revlog = self._revlog()
1429 1446 if revlog._usemanifestv2:
1430 1447 # Need to perform a slow delta
1431 1448 r0 = revlog.deltaparent(revlog.rev(self._node))
1432 1449 m0 = self._manifestlog[revlog.node(r0)].read()
1433 1450 m1 = self.read()
1434 1451 md = manifestdict()
1435 1452 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1436 1453 if n1:
1437 1454 md[f] = n1
1438 1455 if fl1:
1439 1456 md.setflag(f, fl1)
1440 1457 return md
1441 1458
1442 1459 r = revlog.rev(self._node)
1443 1460 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1444 1461 return manifestdict(d)
1445 1462
1446 1463 def find(self, key):
1447 1464 return self.read().find(key)
1448 1465
1449 1466 class memtreemanifestctx(object):
1450 1467 def __init__(self, manifestlog, dir=''):
1451 1468 self._manifestlog = manifestlog
1452 1469 self._dir = dir
1453 1470 self._treemanifest = treemanifest()
1454 1471
1455 1472 def _revlog(self):
1456 1473 return self._manifestlog._revlog
1457 1474
1458 1475 def new(self, dir=''):
1459 1476 return memtreemanifestctx(self._manifestlog, dir=dir)
1460 1477
1461 1478 def copy(self):
1462 1479 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1463 1480 memmf._treemanifest = self._treemanifest.copy()
1464 1481 return memmf
1465 1482
1466 1483 def read(self):
1467 1484 return self._treemanifest
1468 1485
1469 1486 def write(self, transaction, link, p1, p2, added, removed):
1470 1487 def readtree(dir, node):
1471 1488 return self._manifestlog.get(dir, node).read()
1472 1489 return self._revlog().add(self._treemanifest, transaction, link, p1, p2,
1473 1490 added, removed, readtree=readtree)
1474 1491
1475 1492 class treemanifestctx(object):
1476 1493 def __init__(self, manifestlog, dir, node):
1477 1494 self._manifestlog = manifestlog
1478 1495 self._dir = dir
1479 1496 self._data = None
1480 1497
1481 1498 self._node = node
1482 1499
1483 1500 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1484 1501 # we can instantiate treemanifestctx objects for directories we don't
1485 1502 # have on disk.
1486 1503 #self.p1, self.p2 = revlog.parents(node)
1487 1504 #rev = revlog.rev(node)
1488 1505 #self.linkrev = revlog.linkrev(rev)
1489 1506
1490 1507 def _revlog(self):
1491 1508 return self._manifestlog._revlog.dirlog(self._dir)
1492 1509
1493 1510 def read(self):
1494 1511 if self._data is None:
1495 1512 rl = self._revlog()
1496 1513 if self._node == revlog.nullid:
1497 1514 self._data = treemanifest()
1498 1515 elif rl._treeondisk:
1499 1516 m = treemanifest(dir=self._dir)
1500 1517 def gettext():
1501 1518 return rl.revision(self._node)
1502 1519 def readsubtree(dir, subm):
1503 1520 # Set verify to False since we need to be able to create
1504 1521 # subtrees for trees that don't exist on disk.
1505 1522 return self._manifestlog.get(dir, subm, verify=False).read()
1506 1523 m.read(gettext, readsubtree)
1507 1524 m.setnode(self._node)
1508 1525 self._data = m
1509 1526 else:
1510 1527 text = rl.revision(self._node)
1511 1528 arraytext = array.array('c', text)
1512 1529 rl.fulltextcache[self._node] = arraytext
1513 1530 self._data = treemanifest(dir=self._dir, text=text)
1514 1531
1515 1532 return self._data
1516 1533
1517 1534 def node(self):
1518 1535 return self._node
1519 1536
1520 1537 def new(self, dir=''):
1521 1538 return memtreemanifestctx(self._manifestlog, dir=dir)
1522 1539
1523 1540 def copy(self):
1524 1541 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1525 1542 memmf._treemanifest = self.read().copy()
1526 1543 return memmf
1527 1544
1528 1545 @propertycache
1529 1546 def parents(self):
1530 1547 return self._revlog().parents(self._node)
1531 1548
1532 1549 def readdelta(self, shallow=False):
1533 1550 '''Returns a manifest containing just the entries that are present
1534 1551 in this manifest, but not in its p1 manifest. This is efficient to read
1535 1552 if the revlog delta is already p1.
1536 1553
1537 1554 If `shallow` is True, this will read the delta for this directory,
1538 1555 without recursively reading subdirectory manifests. Instead, any
1539 1556 subdirectory entry will be reported as it appears in the manifest, i.e.
1540 1557 the subdirectory will be reported among files and distinguished only by
1541 1558 its 't' flag.
1542 1559 '''
1543 1560 revlog = self._revlog()
1544 1561 if shallow and not revlog._usemanifestv2:
1545 1562 r = revlog.rev(self._node)
1546 1563 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1547 1564 return manifestdict(d)
1548 1565 else:
1549 1566 # Need to perform a slow delta
1550 1567 r0 = revlog.deltaparent(revlog.rev(self._node))
1551 1568 m0 = self._manifestlog.get(self._dir, revlog.node(r0)).read()
1552 1569 m1 = self.read()
1553 1570 md = treemanifest(dir=self._dir)
1554 1571 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1555 1572 if n1:
1556 1573 md[f] = n1
1557 1574 if fl1:
1558 1575 md.setflag(f, fl1)
1559 1576 return md
1560 1577
1561 1578 def readfast(self, shallow=False):
1562 1579 '''Calls either readdelta or read, based on which would be less work.
1563 1580 readdelta is called if the delta is against the p1, and therefore can be
1564 1581 read quickly.
1565 1582
1566 1583 If `shallow` is True, it only returns the entries from this manifest,
1567 1584 and not any submanifests.
1568 1585 '''
1569 1586 rl = self._revlog()
1570 1587 r = rl.rev(self._node)
1571 1588 deltaparent = rl.deltaparent(r)
1572 1589 if (deltaparent != revlog.nullrev and
1573 1590 deltaparent in rl.parentrevs(r)):
1574 1591 return self.readdelta(shallow=shallow)
1575 1592
1576 1593 if shallow:
1577 1594 return manifestdict(rl.revision(self._node))
1578 1595 else:
1579 1596 return self.read()
1580 1597
1581 1598 def find(self, key):
1582 1599 return self.read().find(key)
@@ -1,471 +1,471 b''
1 1 from __future__ import absolute_import
2 2
3 3 import binascii
4 4 import itertools
5 5 import silenttestrunner
6 6 import unittest
7 7
8 8 from mercurial import (
9 9 manifest as manifestmod,
10 10 match as matchmod,
11 11 )
12 12
13 13 EMTPY_MANIFEST = ''
14 14 EMTPY_MANIFEST_V2 = '\0\n'
15 15
16 16 HASH_1 = '1' * 40
17 17 BIN_HASH_1 = binascii.unhexlify(HASH_1)
18 18 HASH_2 = 'f' * 40
19 19 BIN_HASH_2 = binascii.unhexlify(HASH_2)
20 20 HASH_3 = '1234567890abcdef0987654321deadbeef0fcafe'
21 21 BIN_HASH_3 = binascii.unhexlify(HASH_3)
22 22 A_SHORT_MANIFEST = (
23 23 'bar/baz/qux.py\0%(hash2)s%(flag2)s\n'
24 24 'foo\0%(hash1)s%(flag1)s\n'
25 25 ) % {'hash1': HASH_1,
26 26 'flag1': '',
27 27 'hash2': HASH_2,
28 28 'flag2': 'l',
29 29 }
30 30
31 31 # Same data as A_SHORT_MANIFEST
32 32 A_SHORT_MANIFEST_V2 = (
33 33 '\0\n'
34 34 '\x00bar/baz/qux.py\0%(flag2)s\n%(hash2)s\n'
35 35 '\x00foo\0%(flag1)s\n%(hash1)s\n'
36 36 ) % {'hash1': BIN_HASH_1,
37 37 'flag1': '',
38 38 'hash2': BIN_HASH_2,
39 39 'flag2': 'l',
40 40 }
41 41
42 42 # Same data as A_SHORT_MANIFEST
43 43 A_METADATA_MANIFEST = (
44 44 '\0foo\0bar\n'
45 45 '\x00bar/baz/qux.py\0%(flag2)s\0foo\0bar\n%(hash2)s\n' # flag and metadata
46 46 '\x00foo\0%(flag1)s\0foo\n%(hash1)s\n' # no flag, but metadata
47 47 ) % {'hash1': BIN_HASH_1,
48 48 'flag1': '',
49 49 'hash2': BIN_HASH_2,
50 50 'flag2': 'l',
51 51 }
52 52
53 53 A_STEM_COMPRESSED_MANIFEST = (
54 54 '\0\n'
55 55 '\x00bar/baz/qux.py\0%(flag2)s\n%(hash2)s\n'
56 56 '\x04qux/foo.py\0%(flag1)s\n%(hash1)s\n' # simple case of 4 stem chars
57 57 '\x0az.py\0%(flag1)s\n%(hash1)s\n' # tricky newline = 10 stem characters
58 58 '\x00%(verylongdir)sx/x\0\n%(hash1)s\n'
59 59 '\xffx/y\0\n%(hash2)s\n' # more than 255 stem chars
60 60 ) % {'hash1': BIN_HASH_1,
61 61 'flag1': '',
62 62 'hash2': BIN_HASH_2,
63 63 'flag2': 'l',
64 64 'verylongdir': 255 * 'x',
65 65 }
66 66
67 67 A_DEEPER_MANIFEST = (
68 68 'a/b/c/bar.py\0%(hash3)s%(flag1)s\n'
69 69 'a/b/c/bar.txt\0%(hash1)s%(flag1)s\n'
70 70 'a/b/c/foo.py\0%(hash3)s%(flag1)s\n'
71 71 'a/b/c/foo.txt\0%(hash2)s%(flag2)s\n'
72 72 'a/b/d/baz.py\0%(hash3)s%(flag1)s\n'
73 73 'a/b/d/qux.py\0%(hash1)s%(flag2)s\n'
74 74 'a/b/d/ten.txt\0%(hash3)s%(flag2)s\n'
75 75 'a/b/dog.py\0%(hash3)s%(flag1)s\n'
76 76 'a/b/fish.py\0%(hash2)s%(flag1)s\n'
77 77 'a/c/london.py\0%(hash3)s%(flag2)s\n'
78 78 'a/c/paper.txt\0%(hash2)s%(flag2)s\n'
79 79 'a/c/paris.py\0%(hash2)s%(flag1)s\n'
80 80 'a/d/apple.py\0%(hash3)s%(flag1)s\n'
81 81 'a/d/pizza.py\0%(hash3)s%(flag2)s\n'
82 82 'a/green.py\0%(hash1)s%(flag2)s\n'
83 83 'a/purple.py\0%(hash2)s%(flag1)s\n'
84 84 'app.py\0%(hash3)s%(flag1)s\n'
85 85 'readme.txt\0%(hash2)s%(flag1)s\n'
86 86 ) % {'hash1': HASH_1,
87 87 'flag1': '',
88 88 'hash2': HASH_2,
89 89 'flag2': 'l',
90 90 'hash3': HASH_3,
91 91 }
92 92
93 93 HUGE_MANIFEST_ENTRIES = 200001
94 94
95 95 A_HUGE_MANIFEST = ''.join(sorted(
96 96 'file%d\0%s%s\n' % (i, h, f) for i, h, f in
97 97 itertools.izip(xrange(200001),
98 98 itertools.cycle((HASH_1, HASH_2)),
99 99 itertools.cycle(('', 'x', 'l')))))
100 100
101 101 class basemanifesttests(object):
102 102 def parsemanifest(self, text):
103 103 raise NotImplementedError('parsemanifest not implemented by test case')
104 104
105 105 def assertIn(self, thing, container, msg=None):
106 106 # assertIn new in 2.7, use it if available, otherwise polyfill
107 107 sup = getattr(unittest.TestCase, 'assertIn', False)
108 108 if sup:
109 109 return sup(self, thing, container, msg=msg)
110 110 if not msg:
111 111 msg = 'Expected %r in %r' % (thing, container)
112 112 self.assert_(thing in container, msg)
113 113
114 114 def testEmptyManifest(self):
115 115 m = self.parsemanifest(EMTPY_MANIFEST)
116 116 self.assertEqual(0, len(m))
117 117 self.assertEqual([], list(m))
118 118
119 119 def testEmptyManifestv2(self):
120 120 m = self.parsemanifest(EMTPY_MANIFEST_V2)
121 121 self.assertEqual(0, len(m))
122 122 self.assertEqual([], list(m))
123 123
124 124 def testManifest(self):
125 125 m = self.parsemanifest(A_SHORT_MANIFEST)
126 126 self.assertEqual(['bar/baz/qux.py', 'foo'], list(m))
127 127 self.assertEqual(BIN_HASH_2, m['bar/baz/qux.py'])
128 128 self.assertEqual('l', m.flags('bar/baz/qux.py'))
129 129 self.assertEqual(BIN_HASH_1, m['foo'])
130 130 self.assertEqual('', m.flags('foo'))
131 131 self.assertRaises(KeyError, lambda : m['wat'])
132 132
133 133 def testParseManifestV2(self):
134 134 m1 = self.parsemanifest(A_SHORT_MANIFEST)
135 135 m2 = self.parsemanifest(A_SHORT_MANIFEST_V2)
136 136 # Should have same content as A_SHORT_MANIFEST
137 137 self.assertEqual(m1.text(), m2.text())
138 138
139 139 def testParseManifestMetadata(self):
140 140 # Metadata is for future-proofing and should be accepted but ignored
141 141 m = self.parsemanifest(A_METADATA_MANIFEST)
142 142 self.assertEqual(A_SHORT_MANIFEST, m.text())
143 143
144 144 def testParseManifestStemCompression(self):
145 145 m = self.parsemanifest(A_STEM_COMPRESSED_MANIFEST)
146 146 self.assertIn('bar/baz/qux.py', m)
147 147 self.assertIn('bar/qux/foo.py', m)
148 148 self.assertIn('bar/qux/foz.py', m)
149 149 self.assertIn(256 * 'x' + '/x', m)
150 150 self.assertIn(256 * 'x' + '/y', m)
151 151 self.assertEqual(A_STEM_COMPRESSED_MANIFEST, m.text(usemanifestv2=True))
152 152
153 153 def testTextV2(self):
154 154 m1 = self.parsemanifest(A_SHORT_MANIFEST)
155 155 v2text = m1.text(usemanifestv2=True)
156 156 self.assertEqual(A_SHORT_MANIFEST_V2, v2text)
157 157
158 158 def testSetItem(self):
159 159 want = BIN_HASH_1
160 160
161 161 m = self.parsemanifest(EMTPY_MANIFEST)
162 162 m['a'] = want
163 163 self.assertIn('a', m)
164 164 self.assertEqual(want, m['a'])
165 165 self.assertEqual('a\0' + HASH_1 + '\n', m.text())
166 166
167 167 m = self.parsemanifest(A_SHORT_MANIFEST)
168 168 m['a'] = want
169 169 self.assertEqual(want, m['a'])
170 170 self.assertEqual('a\0' + HASH_1 + '\n' + A_SHORT_MANIFEST,
171 171 m.text())
172 172
173 173 def testSetFlag(self):
174 174 want = 'x'
175 175
176 176 m = self.parsemanifest(EMTPY_MANIFEST)
177 177 # first add a file; a file-less flag makes no sense
178 178 m['a'] = BIN_HASH_1
179 179 m.setflag('a', want)
180 180 self.assertEqual(want, m.flags('a'))
181 181 self.assertEqual('a\0' + HASH_1 + want + '\n', m.text())
182 182
183 183 m = self.parsemanifest(A_SHORT_MANIFEST)
184 184 # first add a file; a file-less flag makes no sense
185 185 m['a'] = BIN_HASH_1
186 186 m.setflag('a', want)
187 187 self.assertEqual(want, m.flags('a'))
188 188 self.assertEqual('a\0' + HASH_1 + want + '\n' + A_SHORT_MANIFEST,
189 189 m.text())
190 190
191 191 def testCopy(self):
192 192 m = self.parsemanifest(A_SHORT_MANIFEST)
193 193 m['a'] = BIN_HASH_1
194 194 m2 = m.copy()
195 195 del m
196 196 del m2 # make sure we don't double free() anything
197 197
198 198 def testCompaction(self):
199 199 unhex = binascii.unhexlify
200 200 h1, h2 = unhex(HASH_1), unhex(HASH_2)
201 201 m = self.parsemanifest(A_SHORT_MANIFEST)
202 202 m['alpha'] = h1
203 203 m['beta'] = h2
204 204 del m['foo']
205 205 want = 'alpha\0%s\nbar/baz/qux.py\0%sl\nbeta\0%s\n' % (
206 206 HASH_1, HASH_2, HASH_2)
207 207 self.assertEqual(want, m.text())
208 208 self.assertEqual(3, len(m))
209 209 self.assertEqual(['alpha', 'bar/baz/qux.py', 'beta'], list(m))
210 210 self.assertEqual(h1, m['alpha'])
211 211 self.assertEqual(h2, m['bar/baz/qux.py'])
212 212 self.assertEqual(h2, m['beta'])
213 213 self.assertEqual('', m.flags('alpha'))
214 214 self.assertEqual('l', m.flags('bar/baz/qux.py'))
215 215 self.assertEqual('', m.flags('beta'))
216 216 self.assertRaises(KeyError, lambda : m['foo'])
217 217
218 218 def testSetGetNodeSuffix(self):
219 219 clean = self.parsemanifest(A_SHORT_MANIFEST)
220 220 m = self.parsemanifest(A_SHORT_MANIFEST)
221 221 h = m['foo']
222 222 f = m.flags('foo')
223 223 want = h + 'a'
224 224 # Merge code wants to set 21-byte fake hashes at times
225 225 m['foo'] = want
226 226 self.assertEqual(want, m['foo'])
227 227 self.assertEqual([('bar/baz/qux.py', BIN_HASH_2),
228 228 ('foo', BIN_HASH_1 + 'a')],
229 229 list(m.iteritems()))
230 230 # Sometimes it even tries a 22-byte fake hash, but we can
231 231 # return 21 and it'll work out
232 232 m['foo'] = want + '+'
233 233 self.assertEqual(want, m['foo'])
234 234 # make sure the suffix survives a copy
235 235 match = matchmod.match('', '', ['re:foo'])
236 236 m2 = m.matches(match)
237 237 self.assertEqual(want, m2['foo'])
238 238 self.assertEqual(1, len(m2))
239 239 m2 = m.copy()
240 240 self.assertEqual(want, m2['foo'])
241 241 # suffix with iteration
242 242 self.assertEqual([('bar/baz/qux.py', BIN_HASH_2),
243 243 ('foo', want)],
244 244 list(m.iteritems()))
245 245
246 246 # shows up in diff
247 247 self.assertEqual({'foo': ((want, f), (h, ''))}, m.diff(clean))
248 248 self.assertEqual({'foo': ((h, ''), (want, f))}, clean.diff(m))
249 249
250 250 def testMatchException(self):
251 251 m = self.parsemanifest(A_SHORT_MANIFEST)
252 252 match = matchmod.match('', '', ['re:.*'])
253 253 def filt(path):
254 254 if path == 'foo':
255 255 assert False
256 256 return True
257 257 match.matchfn = filt
258 258 self.assertRaises(AssertionError, m.matches, match)
259 259
260 260 def testRemoveItem(self):
261 261 m = self.parsemanifest(A_SHORT_MANIFEST)
262 262 del m['foo']
263 263 self.assertRaises(KeyError, lambda : m['foo'])
264 264 self.assertEqual(1, len(m))
265 265 self.assertEqual(1, len(list(m)))
266 266 # now restore and make sure everything works right
267 267 m['foo'] = 'a' * 20
268 268 self.assertEqual(2, len(m))
269 269 self.assertEqual(2, len(list(m)))
270 270
271 271 def testManifestDiff(self):
272 272 MISSING = (None, '')
273 273 addl = 'z-only-in-left\0' + HASH_1 + '\n'
274 274 addr = 'z-only-in-right\0' + HASH_2 + 'x\n'
275 275 left = self.parsemanifest(
276 276 A_SHORT_MANIFEST.replace(HASH_1, HASH_3 + 'x') + addl)
277 277 right = self.parsemanifest(A_SHORT_MANIFEST + addr)
278 278 want = {
279 279 'foo': ((BIN_HASH_3, 'x'),
280 280 (BIN_HASH_1, '')),
281 281 'z-only-in-left': ((BIN_HASH_1, ''), MISSING),
282 282 'z-only-in-right': (MISSING, (BIN_HASH_2, 'x')),
283 283 }
284 284 self.assertEqual(want, left.diff(right))
285 285
286 286 want = {
287 287 'bar/baz/qux.py': (MISSING, (BIN_HASH_2, 'l')),
288 288 'foo': (MISSING, (BIN_HASH_3, 'x')),
289 289 'z-only-in-left': (MISSING, (BIN_HASH_1, '')),
290 290 }
291 291 self.assertEqual(want, self.parsemanifest(EMTPY_MANIFEST).diff(left))
292 292
293 293 want = {
294 294 'bar/baz/qux.py': ((BIN_HASH_2, 'l'), MISSING),
295 295 'foo': ((BIN_HASH_3, 'x'), MISSING),
296 296 'z-only-in-left': ((BIN_HASH_1, ''), MISSING),
297 297 }
298 298 self.assertEqual(want, left.diff(self.parsemanifest(EMTPY_MANIFEST)))
299 299 copy = right.copy()
300 300 del copy['z-only-in-right']
301 301 del right['foo']
302 302 want = {
303 303 'foo': (MISSING, (BIN_HASH_1, '')),
304 304 'z-only-in-right': ((BIN_HASH_2, 'x'), MISSING),
305 305 }
306 306 self.assertEqual(want, right.diff(copy))
307 307
308 308 short = self.parsemanifest(A_SHORT_MANIFEST)
309 309 pruned = short.copy()
310 310 del pruned['foo']
311 311 want = {
312 312 'foo': ((BIN_HASH_1, ''), MISSING),
313 313 }
314 314 self.assertEqual(want, short.diff(pruned))
315 315 want = {
316 316 'foo': (MISSING, (BIN_HASH_1, '')),
317 317 }
318 318 self.assertEqual(want, pruned.diff(short))
319 319 want = {
320 320 'bar/baz/qux.py': None,
321 321 'foo': (MISSING, (BIN_HASH_1, '')),
322 322 }
323 self.assertEqual(want, pruned.diff(short, True))
323 self.assertEqual(want, pruned.diff(short, clean=True))
324 324
325 325 def testReversedLines(self):
326 326 backwards = ''.join(
327 327 l + '\n' for l in reversed(A_SHORT_MANIFEST.split('\n')) if l)
328 328 try:
329 329 self.parsemanifest(backwards)
330 330 self.fail('Should have raised ValueError')
331 331 except ValueError as v:
332 332 self.assertIn('Manifest lines not in sorted order.', str(v))
333 333
334 334 def testNoTerminalNewline(self):
335 335 try:
336 336 self.parsemanifest(A_SHORT_MANIFEST + 'wat')
337 337 self.fail('Should have raised ValueError')
338 338 except ValueError as v:
339 339 self.assertIn('Manifest did not end in a newline.', str(v))
340 340
341 341 def testNoNewLineAtAll(self):
342 342 try:
343 343 self.parsemanifest('wat')
344 344 self.fail('Should have raised ValueError')
345 345 except ValueError as v:
346 346 self.assertIn('Manifest did not end in a newline.', str(v))
347 347
348 348 def testHugeManifest(self):
349 349 m = self.parsemanifest(A_HUGE_MANIFEST)
350 350 self.assertEqual(HUGE_MANIFEST_ENTRIES, len(m))
351 351 self.assertEqual(len(m), len(list(m)))
352 352
353 353 def testMatchesMetadata(self):
354 354 '''Tests matches() for a few specific files to make sure that both
355 355 the set of files as well as their flags and nodeids are correct in
356 356 the resulting manifest.'''
357 357 m = self.parsemanifest(A_HUGE_MANIFEST)
358 358
359 359 match = matchmod.match('/', '',
360 360 ['file1', 'file200', 'file300'], exact=True)
361 361 m2 = m.matches(match)
362 362
363 363 w = ('file1\0%sx\n'
364 364 'file200\0%sl\n'
365 365 'file300\0%s\n') % (HASH_2, HASH_1, HASH_1)
366 366 self.assertEqual(w, m2.text())
367 367
368 368 def testMatchesNonexistentFile(self):
369 369 '''Tests matches() for a small set of specific files, including one
370 370 nonexistent file to make sure in only matches against existing files.
371 371 '''
372 372 m = self.parsemanifest(A_DEEPER_MANIFEST)
373 373
374 374 match = matchmod.match('/', '',
375 375 ['a/b/c/bar.txt', 'a/b/d/qux.py', 'readme.txt', 'nonexistent'],
376 376 exact=True)
377 377 m2 = m.matches(match)
378 378
379 379 self.assertEqual(
380 380 ['a/b/c/bar.txt', 'a/b/d/qux.py', 'readme.txt'],
381 381 m2.keys())
382 382
383 383 def testMatchesNonexistentDirectory(self):
384 384 '''Tests matches() for a relpath match on a directory that doesn't
385 385 actually exist.'''
386 386 m = self.parsemanifest(A_DEEPER_MANIFEST)
387 387
388 388 match = matchmod.match('/', '', ['a/f'], default='relpath')
389 389 m2 = m.matches(match)
390 390
391 391 self.assertEqual([], m2.keys())
392 392
393 393 def testMatchesExactLarge(self):
394 394 '''Tests matches() for files matching a large list of exact files.
395 395 '''
396 396 m = self.parsemanifest(A_HUGE_MANIFEST)
397 397
398 398 flist = m.keys()[80:300]
399 399 match = matchmod.match('/', '', flist, exact=True)
400 400 m2 = m.matches(match)
401 401
402 402 self.assertEqual(flist, m2.keys())
403 403
404 404 def testMatchesFull(self):
405 405 '''Tests matches() for what should be a full match.'''
406 406 m = self.parsemanifest(A_DEEPER_MANIFEST)
407 407
408 408 match = matchmod.match('/', '', [''])
409 409 m2 = m.matches(match)
410 410
411 411 self.assertEqual(m.keys(), m2.keys())
412 412
413 413 def testMatchesDirectory(self):
414 414 '''Tests matches() on a relpath match on a directory, which should
415 415 match against all files within said directory.'''
416 416 m = self.parsemanifest(A_DEEPER_MANIFEST)
417 417
418 418 match = matchmod.match('/', '', ['a/b'], default='relpath')
419 419 m2 = m.matches(match)
420 420
421 421 self.assertEqual([
422 422 'a/b/c/bar.py', 'a/b/c/bar.txt', 'a/b/c/foo.py', 'a/b/c/foo.txt',
423 423 'a/b/d/baz.py', 'a/b/d/qux.py', 'a/b/d/ten.txt', 'a/b/dog.py',
424 424 'a/b/fish.py'], m2.keys())
425 425
426 426 def testMatchesExactPath(self):
427 427 '''Tests matches() on an exact match on a directory, which should
428 428 result in an empty manifest because you can't perform an exact match
429 429 against a directory.'''
430 430 m = self.parsemanifest(A_DEEPER_MANIFEST)
431 431
432 432 match = matchmod.match('/', '', ['a/b'], exact=True)
433 433 m2 = m.matches(match)
434 434
435 435 self.assertEqual([], m2.keys())
436 436
437 437 def testMatchesCwd(self):
438 438 '''Tests matches() on a relpath match with the current directory ('.')
439 439 when not in the root directory.'''
440 440 m = self.parsemanifest(A_DEEPER_MANIFEST)
441 441
442 442 match = matchmod.match('/', 'a/b', ['.'], default='relpath')
443 443 m2 = m.matches(match)
444 444
445 445 self.assertEqual([
446 446 'a/b/c/bar.py', 'a/b/c/bar.txt', 'a/b/c/foo.py', 'a/b/c/foo.txt',
447 447 'a/b/d/baz.py', 'a/b/d/qux.py', 'a/b/d/ten.txt', 'a/b/dog.py',
448 448 'a/b/fish.py'], m2.keys())
449 449
450 450 def testMatchesWithPattern(self):
451 451 '''Tests matches() for files matching a pattern that reside
452 452 deeper than the specified directory.'''
453 453 m = self.parsemanifest(A_DEEPER_MANIFEST)
454 454
455 455 match = matchmod.match('/', '', ['a/b/*/*.txt'])
456 456 m2 = m.matches(match)
457 457
458 458 self.assertEqual(
459 459 ['a/b/c/bar.txt', 'a/b/c/foo.txt', 'a/b/d/ten.txt'],
460 460 m2.keys())
461 461
462 462 class testmanifestdict(unittest.TestCase, basemanifesttests):
463 463 def parsemanifest(self, text):
464 464 return manifestmod.manifestdict(text)
465 465
466 466 class testtreemanifest(unittest.TestCase, basemanifesttests):
467 467 def parsemanifest(self, text):
468 468 return manifestmod.treemanifest('', text)
469 469
470 470 if __name__ == '__main__':
471 471 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now