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