##// END OF EJS Templates
match: have visitdir() consider includes and excludes...
Drew Gottlieb -
r25231:8545bd38 default
parent child Browse files
Show More
@@ -1,587 +1,610
1 1 # match.py - filename matching
2 2 #
3 3 # Copyright 2008, 2009 Matt Mackall <mpm@selenic.com> and others
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 import re
9 9 import util, pathutil
10 10 from i18n import _
11 11
12 12 propertycache = util.propertycache
13 13
14 14 def _rematcher(regex):
15 15 '''compile the regexp with the best available regexp engine and return a
16 16 matcher function'''
17 17 m = util.re.compile(regex)
18 18 try:
19 19 # slightly faster, provided by facebook's re2 bindings
20 20 return m.test_match
21 21 except AttributeError:
22 22 return m.match
23 23
24 24 def _expandsets(kindpats, ctx, listsubrepos):
25 25 '''Returns the kindpats list with the 'set' patterns expanded.'''
26 26 fset = set()
27 27 other = []
28 28
29 29 for kind, pat, source in kindpats:
30 30 if kind == 'set':
31 31 if not ctx:
32 32 raise util.Abort("fileset expression with no context")
33 33 s = ctx.getfileset(pat)
34 34 fset.update(s)
35 35
36 36 if listsubrepos:
37 37 for subpath in ctx.substate:
38 38 s = ctx.sub(subpath).getfileset(pat)
39 39 fset.update(subpath + '/' + f for f in s)
40 40
41 41 continue
42 42 other.append((kind, pat, source))
43 43 return fset, other
44 44
45 45 def _kindpatsalwaysmatch(kindpats):
46 46 """"Checks whether the kindspats match everything, as e.g.
47 47 'relpath:.' does.
48 48 """
49 49 for kind, pat, source in kindpats:
50 50 if pat != '' or kind not in ['relpath', 'glob']:
51 51 return False
52 52 return True
53 53
54 54 class match(object):
55 55 def __init__(self, root, cwd, patterns, include=[], exclude=[],
56 56 default='glob', exact=False, auditor=None, ctx=None,
57 57 listsubrepos=False, warn=None):
58 58 """build an object to match a set of file patterns
59 59
60 60 arguments:
61 61 root - the canonical root of the tree you're matching against
62 62 cwd - the current working directory, if relevant
63 63 patterns - patterns to find
64 64 include - patterns to include (unless they are excluded)
65 65 exclude - patterns to exclude (even if they are included)
66 66 default - if a pattern in patterns has no explicit type, assume this one
67 67 exact - patterns are actually filenames (include/exclude still apply)
68 68 warn - optional function used for printing warnings
69 69
70 70 a pattern is one of:
71 71 'glob:<glob>' - a glob relative to cwd
72 72 're:<regexp>' - a regular expression
73 73 'path:<path>' - a path relative to repository root
74 74 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
75 75 'relpath:<path>' - a path relative to cwd
76 76 'relre:<regexp>' - a regexp that needn't match the start of a name
77 77 'set:<fileset>' - a fileset expression
78 78 'include:<path>' - a file of patterns to read and include
79 79 '<something>' - a pattern of the specified default type
80 80 """
81 81
82 82 self._root = root
83 83 self._cwd = cwd
84 84 self._files = [] # exact files and roots of patterns
85 85 self._anypats = bool(include or exclude)
86 86 self._always = False
87 87 self._pathrestricted = bool(include or exclude or patterns)
88 88 self._warn = warn
89 self._includeroots = set()
90 self._includedirs = set(['.'])
91 self._excluderoots = set()
89 92
90 93 matchfns = []
91 94 if include:
92 95 kindpats = self._normalize(include, 'glob', root, cwd, auditor)
93 96 self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)',
94 97 listsubrepos)
98 self._includeroots.update(_roots(kindpats))
99 self._includeroots.discard('.')
100 self._includedirs.update(util.dirs(self._includeroots))
95 101 matchfns.append(im)
96 102 if exclude:
97 103 kindpats = self._normalize(exclude, 'glob', root, cwd, auditor)
98 104 self.excludepat, em = _buildmatch(ctx, kindpats, '(?:/|$)',
99 105 listsubrepos)
106 self._excluderoots.update(_roots(kindpats))
107 self._excluderoots.discard('.')
100 108 matchfns.append(lambda f: not em(f))
101 109 if exact:
102 110 if isinstance(patterns, list):
103 111 self._files = patterns
104 112 else:
105 113 self._files = list(patterns)
106 114 matchfns.append(self.exact)
107 115 elif patterns:
108 116 kindpats = self._normalize(patterns, default, root, cwd, auditor)
109 117 if not _kindpatsalwaysmatch(kindpats):
110 118 self._files = _roots(kindpats)
111 119 self._anypats = self._anypats or _anypats(kindpats)
112 120 self.patternspat, pm = _buildmatch(ctx, kindpats, '$',
113 121 listsubrepos)
114 122 matchfns.append(pm)
115 123
116 124 if not matchfns:
117 125 m = util.always
118 126 self._always = True
119 127 elif len(matchfns) == 1:
120 128 m = matchfns[0]
121 129 else:
122 130 def m(f):
123 131 for matchfn in matchfns:
124 132 if not matchfn(f):
125 133 return False
126 134 return True
127 135
128 136 self.matchfn = m
129 137 self._fileroots = set(self._files)
130 138
131 139 def __call__(self, fn):
132 140 return self.matchfn(fn)
133 141 def __iter__(self):
134 142 for f in self._files:
135 143 yield f
136 144
137 145 # Callbacks related to how the matcher is used by dirstate.walk.
138 146 # Subscribers to these events must monkeypatch the matcher object.
139 147 def bad(self, f, msg):
140 148 '''Callback from dirstate.walk for each explicit file that can't be
141 149 found/accessed, with an error message.'''
142 150 pass
143 151
144 152 # If an explicitdir is set, it will be called when an explicitly listed
145 153 # directory is visited.
146 154 explicitdir = None
147 155
148 156 # If an traversedir is set, it will be called when a directory discovered
149 157 # by recursive traversal is visited.
150 158 traversedir = None
151 159
152 160 def abs(self, f):
153 161 '''Convert a repo path back to path that is relative to the root of the
154 162 matcher.'''
155 163 return f
156 164
157 165 def rel(self, f):
158 166 '''Convert repo path back to path that is relative to cwd of matcher.'''
159 167 return util.pathto(self._root, self._cwd, f)
160 168
161 169 def uipath(self, f):
162 170 '''Convert repo path to a display path. If patterns or -I/-X were used
163 171 to create this matcher, the display path will be relative to cwd.
164 172 Otherwise it is relative to the root of the repo.'''
165 173 return (self._pathrestricted and self.rel(f)) or self.abs(f)
166 174
167 175 def files(self):
168 176 '''Explicitly listed files or patterns or roots:
169 177 if no patterns or .always(): empty list,
170 178 if exact: list exact files,
171 179 if not .anypats(): list all files and dirs,
172 180 else: optimal roots'''
173 181 return self._files
174 182
175 183 @propertycache
176 184 def _dirs(self):
177 185 return set(util.dirs(self._fileroots)) | set(['.'])
178 186
179 187 def visitdir(self, dir):
188 '''Decides whether a directory should be visited based on whether it
189 has potential matches in it or one of its subdirectories. This is
190 based on the match's primary, included, and excluded patterns.
191
192 This function's behavior is undefined if it has returned False for
193 one of the dir's parent directories.
194 '''
195 if dir in self._excluderoots:
196 return False
197 parentdirs = None
198 if (self._includeroots and dir not in self._includeroots and
199 dir not in self._includedirs):
200 parentdirs = util.finddirs(dir)
201 if not any(parent in self._includeroots for parent in parentdirs):
202 return False
180 203 return (not self._fileroots or '.' in self._fileroots or
181 204 dir in self._fileroots or dir in self._dirs or
182 205 any(parentdir in self._fileroots
183 for parentdir in util.finddirs(dir)))
206 for parentdir in parentdirs or util.finddirs(dir)))
184 207
185 208 def exact(self, f):
186 209 '''Returns True if f is in .files().'''
187 210 return f in self._fileroots
188 211
189 212 def anypats(self):
190 213 '''Matcher uses patterns or include/exclude.'''
191 214 return self._anypats
192 215
193 216 def always(self):
194 217 '''Matcher will match everything and .files() will be empty
195 218 - optimization might be possible and necessary.'''
196 219 return self._always
197 220
198 221 def ispartial(self):
199 222 '''True if the matcher won't always match.
200 223
201 224 Although it's just the inverse of _always in this implementation,
202 225 an extenion such as narrowhg might make it return something
203 226 slightly different.'''
204 227 return not self._always
205 228
206 229 def isexact(self):
207 230 return self.matchfn == self.exact
208 231
209 232 def _normalize(self, patterns, default, root, cwd, auditor):
210 233 '''Convert 'kind:pat' from the patterns list to tuples with kind and
211 234 normalized and rooted patterns and with listfiles expanded.'''
212 235 kindpats = []
213 236 for kind, pat in [_patsplit(p, default) for p in patterns]:
214 237 if kind in ('glob', 'relpath'):
215 238 pat = pathutil.canonpath(root, cwd, pat, auditor)
216 239 elif kind in ('relglob', 'path'):
217 240 pat = util.normpath(pat)
218 241 elif kind in ('listfile', 'listfile0'):
219 242 try:
220 243 files = util.readfile(pat)
221 244 if kind == 'listfile0':
222 245 files = files.split('\0')
223 246 else:
224 247 files = files.splitlines()
225 248 files = [f for f in files if f]
226 249 except EnvironmentError:
227 250 raise util.Abort(_("unable to read file list (%s)") % pat)
228 251 for k, p, source in self._normalize(files, default, root, cwd,
229 252 auditor):
230 253 kindpats.append((k, p, pat))
231 254 continue
232 255 elif kind == 'include':
233 256 try:
234 257 includepats = readpatternfile(pat, self._warn)
235 258 for k, p, source in self._normalize(includepats, default,
236 259 root, cwd, auditor):
237 260 kindpats.append((k, p, source or pat))
238 261 except util.Abort, inst:
239 262 raise util.Abort('%s: %s' % (pat, inst[0]))
240 263 except IOError, inst:
241 264 if self._warn:
242 265 self._warn(_("skipping unreadable pattern file "
243 266 "'%s': %s\n") % (pat, inst.strerror))
244 267 continue
245 268 # else: re or relre - which cannot be normalized
246 269 kindpats.append((kind, pat, ''))
247 270 return kindpats
248 271
249 272 def exact(root, cwd, files):
250 273 return match(root, cwd, files, exact=True)
251 274
252 275 def always(root, cwd):
253 276 return match(root, cwd, [])
254 277
255 278 class narrowmatcher(match):
256 279 """Adapt a matcher to work on a subdirectory only.
257 280
258 281 The paths are remapped to remove/insert the path as needed:
259 282
260 283 >>> m1 = match('root', '', ['a.txt', 'sub/b.txt'])
261 284 >>> m2 = narrowmatcher('sub', m1)
262 285 >>> bool(m2('a.txt'))
263 286 False
264 287 >>> bool(m2('b.txt'))
265 288 True
266 289 >>> bool(m2.matchfn('a.txt'))
267 290 False
268 291 >>> bool(m2.matchfn('b.txt'))
269 292 True
270 293 >>> m2.files()
271 294 ['b.txt']
272 295 >>> m2.exact('b.txt')
273 296 True
274 297 >>> util.pconvert(m2.rel('b.txt'))
275 298 'sub/b.txt'
276 299 >>> def bad(f, msg):
277 300 ... print "%s: %s" % (f, msg)
278 301 >>> m1.bad = bad
279 302 >>> m2.bad('x.txt', 'No such file')
280 303 sub/x.txt: No such file
281 304 >>> m2.abs('c.txt')
282 305 'sub/c.txt'
283 306 """
284 307
285 308 def __init__(self, path, matcher):
286 309 self._root = matcher._root
287 310 self._cwd = matcher._cwd
288 311 self._path = path
289 312 self._matcher = matcher
290 313 self._always = matcher._always
291 314 self._pathrestricted = matcher._pathrestricted
292 315
293 316 self._files = [f[len(path) + 1:] for f in matcher._files
294 317 if f.startswith(path + "/")]
295 318
296 319 # If the parent repo had a path to this subrepo and no patterns are
297 320 # specified, this submatcher always matches.
298 321 if not self._always and not matcher._anypats:
299 322 self._always = any(f == path for f in matcher._files)
300 323
301 324 self._anypats = matcher._anypats
302 325 self.matchfn = lambda fn: matcher.matchfn(self._path + "/" + fn)
303 326 self._fileroots = set(self._files)
304 327
305 328 def abs(self, f):
306 329 return self._matcher.abs(self._path + "/" + f)
307 330
308 331 def bad(self, f, msg):
309 332 self._matcher.bad(self._path + "/" + f, msg)
310 333
311 334 def rel(self, f):
312 335 return self._matcher.rel(self._path + "/" + f)
313 336
314 337 class icasefsmatcher(match):
315 338 """A matcher for wdir on case insensitive filesystems, which normalizes the
316 339 given patterns to the case in the filesystem.
317 340 """
318 341
319 342 def __init__(self, root, cwd, patterns, include, exclude, default, auditor,
320 343 ctx, listsubrepos=False):
321 344 init = super(icasefsmatcher, self).__init__
322 345 self._dsnormalize = ctx.repo().dirstate.normalize
323 346
324 347 init(root, cwd, patterns, include, exclude, default, auditor=auditor,
325 348 ctx=ctx, listsubrepos=listsubrepos)
326 349
327 350 # m.exact(file) must be based off of the actual user input, otherwise
328 351 # inexact case matches are treated as exact, and not noted without -v.
329 352 if self._files:
330 353 self._fileroots = set(_roots(self._kp))
331 354
332 355 def _normalize(self, patterns, default, root, cwd, auditor):
333 356 self._kp = super(icasefsmatcher, self)._normalize(patterns, default,
334 357 root, cwd, auditor)
335 358 kindpats = []
336 359 for kind, pats, source in self._kp:
337 360 if kind not in ('re', 'relre'): # regex can't be normalized
338 361 pats = self._dsnormalize(pats)
339 362 kindpats.append((kind, pats, source))
340 363 return kindpats
341 364
342 365 def patkind(pattern, default=None):
343 366 '''If pattern is 'kind:pat' with a known kind, return kind.'''
344 367 return _patsplit(pattern, default)[0]
345 368
346 369 def _patsplit(pattern, default):
347 370 """Split a string into the optional pattern kind prefix and the actual
348 371 pattern."""
349 372 if ':' in pattern:
350 373 kind, pat = pattern.split(':', 1)
351 374 if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
352 375 'listfile', 'listfile0', 'set', 'include'):
353 376 return kind, pat
354 377 return default, pattern
355 378
356 379 def _globre(pat):
357 380 r'''Convert an extended glob string to a regexp string.
358 381
359 382 >>> print _globre(r'?')
360 383 .
361 384 >>> print _globre(r'*')
362 385 [^/]*
363 386 >>> print _globre(r'**')
364 387 .*
365 388 >>> print _globre(r'**/a')
366 389 (?:.*/)?a
367 390 >>> print _globre(r'a/**/b')
368 391 a\/(?:.*/)?b
369 392 >>> print _globre(r'[a*?!^][^b][!c]')
370 393 [a*?!^][\^b][^c]
371 394 >>> print _globre(r'{a,b}')
372 395 (?:a|b)
373 396 >>> print _globre(r'.\*\?')
374 397 \.\*\?
375 398 '''
376 399 i, n = 0, len(pat)
377 400 res = ''
378 401 group = 0
379 402 escape = util.re.escape
380 403 def peek():
381 404 return i < n and pat[i]
382 405 while i < n:
383 406 c = pat[i]
384 407 i += 1
385 408 if c not in '*?[{},\\':
386 409 res += escape(c)
387 410 elif c == '*':
388 411 if peek() == '*':
389 412 i += 1
390 413 if peek() == '/':
391 414 i += 1
392 415 res += '(?:.*/)?'
393 416 else:
394 417 res += '.*'
395 418 else:
396 419 res += '[^/]*'
397 420 elif c == '?':
398 421 res += '.'
399 422 elif c == '[':
400 423 j = i
401 424 if j < n and pat[j] in '!]':
402 425 j += 1
403 426 while j < n and pat[j] != ']':
404 427 j += 1
405 428 if j >= n:
406 429 res += '\\['
407 430 else:
408 431 stuff = pat[i:j].replace('\\','\\\\')
409 432 i = j + 1
410 433 if stuff[0] == '!':
411 434 stuff = '^' + stuff[1:]
412 435 elif stuff[0] == '^':
413 436 stuff = '\\' + stuff
414 437 res = '%s[%s]' % (res, stuff)
415 438 elif c == '{':
416 439 group += 1
417 440 res += '(?:'
418 441 elif c == '}' and group:
419 442 res += ')'
420 443 group -= 1
421 444 elif c == ',' and group:
422 445 res += '|'
423 446 elif c == '\\':
424 447 p = peek()
425 448 if p:
426 449 i += 1
427 450 res += escape(p)
428 451 else:
429 452 res += escape(c)
430 453 else:
431 454 res += escape(c)
432 455 return res
433 456
434 457 def _regex(kind, pat, globsuffix):
435 458 '''Convert a (normalized) pattern of any kind into a regular expression.
436 459 globsuffix is appended to the regexp of globs.'''
437 460 if not pat:
438 461 return ''
439 462 if kind == 're':
440 463 return pat
441 464 if kind == 'path':
442 465 return '^' + util.re.escape(pat) + '(?:/|$)'
443 466 if kind == 'relglob':
444 467 return '(?:|.*/)' + _globre(pat) + globsuffix
445 468 if kind == 'relpath':
446 469 return util.re.escape(pat) + '(?:/|$)'
447 470 if kind == 'relre':
448 471 if pat.startswith('^'):
449 472 return pat
450 473 return '.*' + pat
451 474 return _globre(pat) + globsuffix
452 475
453 476 def _buildmatch(ctx, kindpats, globsuffix, listsubrepos):
454 477 '''Return regexp string and a matcher function for kindpats.
455 478 globsuffix is appended to the regexp of globs.'''
456 479 fset, kindpats = _expandsets(kindpats, ctx, listsubrepos)
457 480 if not kindpats:
458 481 return "", fset.__contains__
459 482
460 483 regex, mf = _buildregexmatch(kindpats, globsuffix)
461 484 if fset:
462 485 return regex, lambda f: f in fset or mf(f)
463 486 return regex, mf
464 487
465 488 def _buildregexmatch(kindpats, globsuffix):
466 489 """Build a match function from a list of kinds and kindpats,
467 490 return regexp string and a matcher function."""
468 491 try:
469 492 regex = '(?:%s)' % '|'.join([_regex(k, p, globsuffix)
470 493 for (k, p, s) in kindpats])
471 494 if len(regex) > 20000:
472 495 raise OverflowError
473 496 return regex, _rematcher(regex)
474 497 except OverflowError:
475 498 # We're using a Python with a tiny regex engine and we
476 499 # made it explode, so we'll divide the pattern list in two
477 500 # until it works
478 501 l = len(kindpats)
479 502 if l < 2:
480 503 raise
481 504 regexa, a = _buildregexmatch(kindpats[:l//2], globsuffix)
482 505 regexb, b = _buildregexmatch(kindpats[l//2:], globsuffix)
483 506 return regex, lambda s: a(s) or b(s)
484 507 except re.error:
485 508 for k, p, s in kindpats:
486 509 try:
487 510 _rematcher('(?:%s)' % _regex(k, p, globsuffix))
488 511 except re.error:
489 512 if s:
490 513 raise util.Abort(_("%s: invalid pattern (%s): %s") %
491 514 (s, k, p))
492 515 else:
493 516 raise util.Abort(_("invalid pattern (%s): %s") % (k, p))
494 517 raise util.Abort(_("invalid pattern"))
495 518
496 519 def _roots(kindpats):
497 520 '''return roots and exact explicitly listed files from patterns
498 521
499 522 >>> _roots([('glob', 'g/*', ''), ('glob', 'g', ''), ('glob', 'g*', '')])
500 523 ['g', 'g', '.']
501 524 >>> _roots([('relpath', 'r', ''), ('path', 'p/p', ''), ('path', '', '')])
502 525 ['r', 'p/p', '.']
503 526 >>> _roots([('relglob', 'rg*', ''), ('re', 're/', ''), ('relre', 'rr', '')])
504 527 ['.', '.', '.']
505 528 '''
506 529 r = []
507 530 for kind, pat, source in kindpats:
508 531 if kind == 'glob': # find the non-glob prefix
509 532 root = []
510 533 for p in pat.split('/'):
511 534 if '[' in p or '{' in p or '*' in p or '?' in p:
512 535 break
513 536 root.append(p)
514 537 r.append('/'.join(root) or '.')
515 538 elif kind in ('relpath', 'path'):
516 539 r.append(pat or '.')
517 540 else: # relglob, re, relre
518 541 r.append('.')
519 542 return r
520 543
521 544 def _anypats(kindpats):
522 545 for kind, pat, source in kindpats:
523 546 if kind in ('glob', 're', 'relglob', 'relre', 'set'):
524 547 return True
525 548
526 549 _commentre = None
527 550
528 551 def readpatternfile(filepath, warn):
529 552 '''parse a pattern file, returning a list of
530 553 patterns. These patterns should be given to compile()
531 554 to be validated and converted into a match function.
532 555
533 556 trailing white space is dropped.
534 557 the escape character is backslash.
535 558 comments start with #.
536 559 empty lines are skipped.
537 560
538 561 lines can be of the following formats:
539 562
540 563 syntax: regexp # defaults following lines to non-rooted regexps
541 564 syntax: glob # defaults following lines to non-rooted globs
542 565 re:pattern # non-rooted regular expression
543 566 glob:pattern # non-rooted glob
544 567 pattern # pattern of the current default type'''
545 568
546 569 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:',
547 570 'include': 'include'}
548 571 syntax = 'relre:'
549 572 patterns = []
550 573
551 574 fp = open(filepath)
552 575 for line in fp:
553 576 if "#" in line:
554 577 global _commentre
555 578 if not _commentre:
556 579 _commentre = re.compile(r'((^|[^\\])(\\\\)*)#.*')
557 580 # remove comments prefixed by an even number of escapes
558 581 line = _commentre.sub(r'\1', line)
559 582 # fixup properly escaped comments that survived the above
560 583 line = line.replace("\\#", "#")
561 584 line = line.rstrip()
562 585 if not line:
563 586 continue
564 587
565 588 if line.startswith('syntax:'):
566 589 s = line[7:].strip()
567 590 try:
568 591 syntax = syntaxes[s]
569 592 except KeyError:
570 593 if warn:
571 594 warn(_("%s: ignoring invalid syntax '%s'\n") %
572 595 (filepath, s))
573 596 continue
574 597
575 598 linesyntax = syntax
576 599 for s, rels in syntaxes.iteritems():
577 600 if line.startswith(rels):
578 601 linesyntax = rels
579 602 line = line[len(rels):]
580 603 break
581 604 elif line.startswith(s+':'):
582 605 linesyntax = rels
583 606 line = line[len(s) + 1:]
584 607 break
585 608 patterns.append(linesyntax + line)
586 609 fp.close()
587 610 return patterns
@@ -1,282 +1,379
1 1
2 2 Set up repo
3 3
4 4 $ hg --config experimental.treemanifest=True init repo
5 5 $ cd repo
6 6
7 7 Requirements get set on init
8 8
9 9 $ grep treemanifest .hg/requires
10 10 treemanifest
11 11
12 12 Without directories, looks like any other repo
13 13
14 14 $ echo 0 > a
15 15 $ echo 0 > b
16 16 $ hg ci -Aqm initial
17 17 $ hg debugdata -m 0
18 18 a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
19 19 b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
20 20
21 21 Submanifest is stored in separate revlog
22 22
23 23 $ mkdir dir1
24 24 $ echo 1 > dir1/a
25 25 $ echo 1 > dir1/b
26 26 $ echo 1 > e
27 27 $ hg ci -Aqm 'add dir1'
28 28 $ hg debugdata -m 1
29 29 a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
30 30 b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
31 31 dir1\x008b3ffd73f901e83304c83d33132c8e774ceac44ed (esc)
32 32 e\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
33 33 $ hg debugdata --dir dir1 0
34 34 a\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
35 35 b\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
36 36
37 37 Can add nested directories
38 38
39 39 $ mkdir dir1/dir1
40 40 $ echo 2 > dir1/dir1/a
41 41 $ echo 2 > dir1/dir1/b
42 42 $ mkdir dir1/dir2
43 43 $ echo 2 > dir1/dir2/a
44 44 $ echo 2 > dir1/dir2/b
45 45 $ hg ci -Aqm 'add dir1/dir1'
46 46 $ hg files -r .
47 47 a
48 48 b
49 49 dir1/a (glob)
50 50 dir1/b (glob)
51 51 dir1/dir1/a (glob)
52 52 dir1/dir1/b (glob)
53 53 dir1/dir2/a (glob)
54 54 dir1/dir2/b (glob)
55 55 e
56 56
57 57 Revision is not created for unchanged directory
58 58
59 59 $ mkdir dir2
60 60 $ echo 3 > dir2/a
61 61 $ hg add dir2
62 62 adding dir2/a (glob)
63 63 $ hg debugindex --dir dir1 > before
64 64 $ hg ci -qm 'add dir2'
65 65 $ hg debugindex --dir dir1 > after
66 66 $ diff before after
67 67 $ rm before after
68 68
69 69 Removing directory does not create an revlog entry
70 70
71 71 $ hg rm dir1/dir1
72 72 removing dir1/dir1/a (glob)
73 73 removing dir1/dir1/b (glob)
74 74 $ hg debugindex --dir dir1/dir1 > before
75 75 $ hg ci -qm 'remove dir1/dir1'
76 76 $ hg debugindex --dir dir1/dir1 > after
77 77 $ diff before after
78 78 $ rm before after
79 79
80 80 Check that hg files (calls treemanifest.walk()) works
81 81 without loading all directory revlogs
82 82
83 83 $ hg co 'desc("add dir2")'
84 84 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
85 85 $ mv .hg/store/meta/dir2 .hg/store/meta/dir2-backup
86 86 $ hg files -r . dir1
87 87 dir1/a (glob)
88 88 dir1/b (glob)
89 89 dir1/dir1/a (glob)
90 90 dir1/dir1/b (glob)
91 91 dir1/dir2/a (glob)
92 92 dir1/dir2/b (glob)
93 93
94 94 Check that status between revisions works (calls treemanifest.matches())
95 95 without loading all directory revlogs
96 96
97 97 $ hg status --rev 'desc("add dir1")' --rev . dir1
98 98 A dir1/dir1/a
99 99 A dir1/dir1/b
100 100 A dir1/dir2/a
101 101 A dir1/dir2/b
102 102 $ mv .hg/store/meta/dir2-backup .hg/store/meta/dir2
103 103
104 104 Merge creates 2-parent revision of directory revlog
105 105
106 106 $ echo 5 > dir1/a
107 107 $ hg ci -Aqm 'modify dir1/a'
108 108 $ hg co '.^'
109 109 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
110 110 $ echo 6 > dir1/b
111 111 $ hg ci -Aqm 'modify dir1/b'
112 112 $ hg merge 'desc("modify dir1/a")'
113 113 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
114 114 (branch merge, don't forget to commit)
115 115 $ hg ci -m 'conflict-free merge involving dir1/'
116 116 $ cat dir1/a
117 117 5
118 118 $ cat dir1/b
119 119 6
120 120 $ hg debugindex --dir dir1
121 121 rev offset length base linkrev nodeid p1 p2
122 122 0 0 54 0 1 8b3ffd73f901 000000000000 000000000000
123 123 1 54 68 0 2 b66d046c644f 8b3ffd73f901 000000000000
124 124 2 122 12 0 4 b87265673c8a b66d046c644f 000000000000
125 125 3 134 95 0 5 aa5d3adcec72 b66d046c644f 000000000000
126 126 4 229 81 0 6 e29b066b91ad b66d046c644f 000000000000
127 127 5 310 107 5 7 a120ce2b83f5 e29b066b91ad aa5d3adcec72
128 128
129 129 Merge keeping directory from parent 1 does not create revlog entry. (Note that
130 130 dir1's manifest does change, but only because dir1/a's filelog changes.)
131 131
132 132 $ hg co 'desc("add dir2")'
133 133 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
134 134 $ echo 8 > dir2/a
135 135 $ hg ci -m 'modify dir2/a'
136 136 created new head
137 137
138 138 $ hg debugindex --dir dir2 > before
139 139 $ hg merge 'desc("modify dir1/a")'
140 140 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
141 141 (branch merge, don't forget to commit)
142 142 $ hg revert -r 'desc("modify dir2/a")' .
143 143 reverting dir1/a (glob)
144 144 $ hg ci -m 'merge, keeping parent 1'
145 145 $ hg debugindex --dir dir2 > after
146 146 $ diff before after
147 147 $ rm before after
148 148
149 149 Merge keeping directory from parent 2 does not create revlog entry. (Note that
150 150 dir2's manifest does change, but only because dir2/a's filelog changes.)
151 151
152 152 $ hg co 'desc("modify dir2/a")'
153 153 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
154 154 $ hg debugindex --dir dir1 > before
155 155 $ hg merge 'desc("modify dir1/a")'
156 156 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
157 157 (branch merge, don't forget to commit)
158 158 $ hg revert -r 'desc("modify dir1/a")' .
159 159 reverting dir2/a (glob)
160 160 $ hg ci -m 'merge, keeping parent 2'
161 161 created new head
162 162 $ hg debugindex --dir dir1 > after
163 163 $ diff before after
164 164 $ rm before after
165 165
166 166 Create flat source repo for tests with mixed flat/tree manifests
167 167
168 168 $ cd ..
169 169 $ hg init repo-flat
170 170 $ cd repo-flat
171 171
172 172 Create a few commits with flat manifest
173 173
174 174 $ echo 0 > a
175 175 $ echo 0 > b
176 176 $ echo 0 > e
177 177 $ for d in dir1 dir1/dir1 dir1/dir2 dir2
178 178 > do
179 179 > mkdir $d
180 180 > echo 0 > $d/a
181 181 > echo 0 > $d/b
182 182 > done
183 183 $ hg ci -Aqm initial
184 184
185 185 $ echo 1 > a
186 186 $ echo 1 > dir1/a
187 187 $ echo 1 > dir1/dir1/a
188 188 $ hg ci -Aqm 'modify on branch 1'
189 189
190 190 $ hg co 0
191 191 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
192 192 $ echo 2 > b
193 193 $ echo 2 > dir1/b
194 194 $ echo 2 > dir1/dir1/b
195 195 $ hg ci -Aqm 'modify on branch 2'
196 196
197 197 $ hg merge 1
198 198 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
199 199 (branch merge, don't forget to commit)
200 200 $ hg ci -m 'merge of flat manifests to new flat manifest'
201 201
202 202 Create clone with tree manifests enabled
203 203
204 204 $ cd ..
205 205 $ hg clone --pull --config experimental.treemanifest=1 repo-flat repo-mixed
206 206 requesting all changes
207 207 adding changesets
208 208 adding manifests
209 209 adding file changes
210 210 added 4 changesets with 17 changes to 11 files
211 211 updating to branch default
212 212 11 files updated, 0 files merged, 0 files removed, 0 files unresolved
213 213 $ cd repo-mixed
214 214 $ test -f .hg/store/meta
215 215 [1]
216 216 $ grep treemanifest .hg/requires
217 217 treemanifest
218 218
219 219 Commit should store revlog per directory
220 220
221 221 $ hg co 1
222 222 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
223 223 $ echo 3 > a
224 224 $ echo 3 > dir1/a
225 225 $ echo 3 > dir1/dir1/a
226 226 $ hg ci -m 'first tree'
227 227 created new head
228 228 $ find .hg/store/meta | sort
229 229 .hg/store/meta
230 230 .hg/store/meta/dir1
231 231 .hg/store/meta/dir1/00manifest.i
232 232 .hg/store/meta/dir1/dir1
233 233 .hg/store/meta/dir1/dir1/00manifest.i
234 234 .hg/store/meta/dir1/dir2
235 235 .hg/store/meta/dir1/dir2/00manifest.i
236 236 .hg/store/meta/dir2
237 237 .hg/store/meta/dir2/00manifest.i
238 238
239 239 Merge of two trees
240 240
241 241 $ hg co 2
242 242 6 files updated, 0 files merged, 0 files removed, 0 files unresolved
243 243 $ hg merge 1
244 244 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
245 245 (branch merge, don't forget to commit)
246 246 $ hg ci -m 'merge of flat manifests to new tree manifest'
247 247 created new head
248 248 $ hg diff -r 3
249 249
250 250 Parent of tree root manifest should be flat manifest, and two for merge
251 251
252 252 $ hg debugindex -m
253 253 rev offset length base linkrev nodeid p1 p2
254 254 0 0 80 0 0 40536115ed9e 000000000000 000000000000
255 255 1 80 83 0 1 f3376063c255 40536115ed9e 000000000000
256 256 2 163 103 0 2 5d9b9da231a2 40536115ed9e 000000000000
257 257 3 266 83 0 3 d17d663cbd8a 5d9b9da231a2 f3376063c255
258 258 4 349 132 4 4 c05a51345f86 f3376063c255 000000000000
259 259 5 481 110 4 5 82594b1f557d 5d9b9da231a2 f3376063c255
260 260
261 261
262 262 Status across flat/tree boundary should work
263 263
264 264 $ hg status --rev '.^' --rev .
265 265 M a
266 266 M dir1/a
267 267 M dir1/dir1/a
268 268
269 269
270 270 Turning off treemanifest config has no effect
271 271
272 272 $ hg debugindex .hg/store/meta/dir1/00manifest.i
273 273 rev offset length base linkrev nodeid p1 p2
274 274 0 0 125 0 4 63c9c0557d24 000000000000 000000000000
275 275 1 125 109 0 5 23d12a1f6e0e 000000000000 000000000000
276 276 $ echo 2 > dir1/a
277 277 $ hg --config experimental.treemanifest=False ci -qm 'modify dir1/a'
278 278 $ hg debugindex .hg/store/meta/dir1/00manifest.i
279 279 rev offset length base linkrev nodeid p1 p2
280 280 0 0 125 0 4 63c9c0557d24 000000000000 000000000000
281 281 1 125 109 0 5 23d12a1f6e0e 000000000000 000000000000
282 282 2 234 55 0 6 3cb2d87b4250 23d12a1f6e0e 000000000000
283
284 Create deeper repo with tree manifests.
285
286 $ cd ..
287 $ hg --config experimental.treemanifest=True init deeprepo
288 $ cd deeprepo
289
290 $ mkdir a
291 $ mkdir b
292 $ mkdir b/bar
293 $ mkdir b/bar/orange
294 $ mkdir b/bar/orange/fly
295 $ mkdir b/foo
296 $ mkdir b/foo/apple
297 $ mkdir b/foo/apple/bees
298
299 $ touch a/one.txt
300 $ touch a/two.txt
301 $ touch b/bar/fruits.txt
302 $ touch b/bar/orange/fly/gnat.py
303 $ touch b/bar/orange/fly/housefly.txt
304 $ touch b/foo/apple/bees/flower.py
305 $ touch c.txt
306 $ touch d.py
307
308 $ hg ci -Aqm 'initial'
309
310 We'll see that visitdir works by removing some treemanifest revlogs and running
311 the files command with various parameters.
312
313 Test files from the root.
314
315 $ hg files -r .
316 a/one.txt
317 a/two.txt
318 b/bar/fruits.txt
319 b/bar/orange/fly/gnat.py
320 b/bar/orange/fly/housefly.txt
321 b/foo/apple/bees/flower.py
322 c.txt
323 d.py
324
325 Test files for a subdirectory.
326
327 $ mv .hg/store/meta/a oldmf
328 $ hg files -r . b
329 b/bar/fruits.txt
330 b/bar/orange/fly/gnat.py
331 b/bar/orange/fly/housefly.txt
332 b/foo/apple/bees/flower.py
333 $ mv oldmf .hg/store/meta/a
334
335 Test files with just includes and excludes.
336
337 $ mv .hg/store/meta/a oldmf
338 $ mv .hg/store/meta/b/bar/orange/fly oldmf2
339 $ mv .hg/store/meta/b/foo/apple/bees oldmf3
340 $ hg files -r . -I b/bar -X b/bar/orange/fly -I b/foo -X b/foo/apple/bees
341 b/bar/fruits.txt
342 $ mv oldmf .hg/store/meta/a
343 $ mv oldmf2 .hg/store/meta/b/bar/orange/fly
344 $ mv oldmf3 .hg/store/meta/b/foo/apple/bees
345
346 Test files for a subdirectory, excluding a directory within it.
347
348 $ mv .hg/store/meta/a oldmf
349 $ mv .hg/store/meta/b/foo oldmf2
350 $ hg files -r . -X b/foo b
351 b/bar/fruits.txt
352 b/bar/orange/fly/gnat.py
353 b/bar/orange/fly/housefly.txt
354 $ mv oldmf .hg/store/meta/a
355 $ mv oldmf2 .hg/store/meta/b/foo
356
357 Test files for a sub directory, including only a directory within it, and
358 including an unrelated directory.
359
360 $ mv .hg/store/meta/a oldmf
361 $ mv .hg/store/meta/b/foo oldmf2
362 $ hg files -r . -I b/bar/orange -I a b
363 b/bar/orange/fly/gnat.py
364 b/bar/orange/fly/housefly.txt
365 $ mv oldmf .hg/store/meta/a
366 $ mv oldmf2 .hg/store/meta/b/foo
367
368 Test files for a pattern, including a directory, and excluding a directory
369 within that.
370
371 $ mv .hg/store/meta/a oldmf
372 $ mv .hg/store/meta/b/foo oldmf2
373 $ mv .hg/store/meta/b/bar/orange oldmf3
374 $ hg files -r . glob:**.txt -I b/bar -X b/bar/orange
375 b/bar/fruits.txt
376 $ mv oldmf .hg/store/meta/a
377 $ mv oldmf2 .hg/store/meta/b/foo
378 $ mv oldmf3 .hg/store/meta/b/bar/orange
379
General Comments 0
You need to be logged in to leave comments. Login now