##// END OF EJS Templates
match: handle includes using new intersectionmatcher
Martin von Zweigbergk -
r32497:9eccd559 default
parent child Browse files
Show More
@@ -1,875 +1,949
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 from __future__ import absolute_import
9 9
10 10 import copy
11 11 import os
12 12 import re
13 13
14 14 from .i18n import _
15 15 from . import (
16 16 error,
17 17 pathutil,
18 18 util,
19 19 )
20 20
21 21 propertycache = util.propertycache
22 22
23 23 def _rematcher(regex):
24 24 '''compile the regexp with the best available regexp engine and return a
25 25 matcher function'''
26 26 m = util.re.compile(regex)
27 27 try:
28 28 # slightly faster, provided by facebook's re2 bindings
29 29 return m.test_match
30 30 except AttributeError:
31 31 return m.match
32 32
33 33 def _expandsets(kindpats, ctx, listsubrepos):
34 34 '''Returns the kindpats list with the 'set' patterns expanded.'''
35 35 fset = set()
36 36 other = []
37 37
38 38 for kind, pat, source in kindpats:
39 39 if kind == 'set':
40 40 if not ctx:
41 41 raise error.ProgrammingError("fileset expression with no "
42 42 "context")
43 43 s = ctx.getfileset(pat)
44 44 fset.update(s)
45 45
46 46 if listsubrepos:
47 47 for subpath in ctx.substate:
48 48 s = ctx.sub(subpath).getfileset(pat)
49 49 fset.update(subpath + '/' + f for f in s)
50 50
51 51 continue
52 52 other.append((kind, pat, source))
53 53 return fset, other
54 54
55 55 def _expandsubinclude(kindpats, root):
56 56 '''Returns the list of subinclude matcher args and the kindpats without the
57 57 subincludes in it.'''
58 58 relmatchers = []
59 59 other = []
60 60
61 61 for kind, pat, source in kindpats:
62 62 if kind == 'subinclude':
63 63 sourceroot = pathutil.dirname(util.normpath(source))
64 64 pat = util.pconvert(pat)
65 65 path = pathutil.join(sourceroot, pat)
66 66
67 67 newroot = pathutil.dirname(path)
68 68 matcherargs = (newroot, '', [], ['include:%s' % path])
69 69
70 70 prefix = pathutil.canonpath(root, root, newroot)
71 71 if prefix:
72 72 prefix += '/'
73 73 relmatchers.append((prefix, matcherargs))
74 74 else:
75 75 other.append((kind, pat, source))
76 76
77 77 return relmatchers, other
78 78
79 79 def _kindpatsalwaysmatch(kindpats):
80 80 """"Checks whether the kindspats match everything, as e.g.
81 81 'relpath:.' does.
82 82 """
83 83 for kind, pat, source in kindpats:
84 84 if pat != '' or kind not in ['relpath', 'glob']:
85 85 return False
86 86 return True
87 87
88 88 def match(root, cwd, patterns, include=None, exclude=None, default='glob',
89 89 exact=False, auditor=None, ctx=None, listsubrepos=False, warn=None,
90 90 badfn=None, icasefs=False):
91 91 """build an object to match a set of file patterns
92 92
93 93 arguments:
94 94 root - the canonical root of the tree you're matching against
95 95 cwd - the current working directory, if relevant
96 96 patterns - patterns to find
97 97 include - patterns to include (unless they are excluded)
98 98 exclude - patterns to exclude (even if they are included)
99 99 default - if a pattern in patterns has no explicit type, assume this one
100 100 exact - patterns are actually filenames (include/exclude still apply)
101 101 warn - optional function used for printing warnings
102 102 badfn - optional bad() callback for this matcher instead of the default
103 103 icasefs - make a matcher for wdir on case insensitive filesystems, which
104 104 normalizes the given patterns to the case in the filesystem
105 105
106 106 a pattern is one of:
107 107 'glob:<glob>' - a glob relative to cwd
108 108 're:<regexp>' - a regular expression
109 109 'path:<path>' - a path relative to repository root, which is matched
110 110 recursively
111 111 'rootfilesin:<path>' - a path relative to repository root, which is
112 112 matched non-recursively (will not match subdirectories)
113 113 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
114 114 'relpath:<path>' - a path relative to cwd
115 115 'relre:<regexp>' - a regexp that needn't match the start of a name
116 116 'set:<fileset>' - a fileset expression
117 117 'include:<path>' - a file of patterns to read and include
118 118 'subinclude:<path>' - a file of patterns to match against files under
119 119 the same directory
120 120 '<something>' - a pattern of the specified default type
121 121 """
122 122 normalize = _donormalize
123 123 if icasefs:
124 124 if exact:
125 125 raise error.ProgrammingError("a case-insensitive exact matcher "
126 126 "doesn't make sense")
127 127 dirstate = ctx.repo().dirstate
128 128 dsnormalize = dirstate.normalize
129 129
130 130 def normalize(patterns, default, root, cwd, auditor, warn):
131 131 kp = _donormalize(patterns, default, root, cwd, auditor, warn)
132 132 kindpats = []
133 133 for kind, pats, source in kp:
134 134 if kind not in ('re', 'relre'): # regex can't be normalized
135 135 p = pats
136 136 pats = dsnormalize(pats)
137 137
138 138 # Preserve the original to handle a case only rename.
139 139 if p != pats and p in dirstate:
140 140 kindpats.append((kind, p, source))
141 141
142 142 kindpats.append((kind, pats, source))
143 143 return kindpats
144 144
145 m = matcher(root, cwd, normalize, patterns, include=include,
145 m = matcher(root, cwd, normalize, patterns, include=None,
146 146 default=default, exact=exact, auditor=auditor, ctx=ctx,
147 147 listsubrepos=listsubrepos, warn=warn, badfn=badfn)
148 if include:
149 im = matcher(root, cwd, normalize, [], include=include, default=default,
150 exact=False, auditor=auditor, ctx=ctx,
151 listsubrepos=listsubrepos, warn=warn, badfn=None)
152 m = intersectmatchers(m, im)
148 153 if exclude:
149 154 em = matcher(root, cwd, normalize, [], include=exclude, default=default,
150 155 exact=False, auditor=auditor, ctx=ctx,
151 156 listsubrepos=listsubrepos, warn=warn, badfn=None)
152 157 m = differencematcher(m, em)
153 158 return m
154 159
155 160 def exact(root, cwd, files, badfn=None):
156 161 return match(root, cwd, files, exact=True, badfn=badfn)
157 162
158 163 def always(root, cwd):
159 164 return match(root, cwd, [])
160 165
161 166 def badmatch(match, badfn):
162 167 """Make a copy of the given matcher, replacing its bad method with the given
163 168 one.
164 169 """
165 170 m = copy.copy(match)
166 171 m.bad = badfn
167 172 return m
168 173
169 174 def _donormalize(patterns, default, root, cwd, auditor, warn):
170 175 '''Convert 'kind:pat' from the patterns list to tuples with kind and
171 176 normalized and rooted patterns and with listfiles expanded.'''
172 177 kindpats = []
173 178 for kind, pat in [_patsplit(p, default) for p in patterns]:
174 179 if kind in ('glob', 'relpath'):
175 180 pat = pathutil.canonpath(root, cwd, pat, auditor)
176 181 elif kind in ('relglob', 'path', 'rootfilesin'):
177 182 pat = util.normpath(pat)
178 183 elif kind in ('listfile', 'listfile0'):
179 184 try:
180 185 files = util.readfile(pat)
181 186 if kind == 'listfile0':
182 187 files = files.split('\0')
183 188 else:
184 189 files = files.splitlines()
185 190 files = [f for f in files if f]
186 191 except EnvironmentError:
187 192 raise error.Abort(_("unable to read file list (%s)") % pat)
188 193 for k, p, source in _donormalize(files, default, root, cwd,
189 194 auditor, warn):
190 195 kindpats.append((k, p, pat))
191 196 continue
192 197 elif kind == 'include':
193 198 try:
194 199 fullpath = os.path.join(root, util.localpath(pat))
195 200 includepats = readpatternfile(fullpath, warn)
196 201 for k, p, source in _donormalize(includepats, default,
197 202 root, cwd, auditor, warn):
198 203 kindpats.append((k, p, source or pat))
199 204 except error.Abort as inst:
200 205 raise error.Abort('%s: %s' % (pat, inst[0]))
201 206 except IOError as inst:
202 207 if warn:
203 208 warn(_("skipping unreadable pattern file '%s': %s\n") %
204 209 (pat, inst.strerror))
205 210 continue
206 211 # else: re or relre - which cannot be normalized
207 212 kindpats.append((kind, pat, ''))
208 213 return kindpats
209 214
210 215 class basematcher(object):
211 216
212 217 def __init__(self, root, cwd, badfn=None, relativeuipath=True):
213 218 self._root = root
214 219 self._cwd = cwd
215 220 if badfn is not None:
216 221 self.bad = badfn
217 222 self._relativeuipath = relativeuipath
218 223
219 224 def __call__(self, fn):
220 225 return self.matchfn(fn)
221 226 def __iter__(self):
222 227 for f in self._files:
223 228 yield f
224 229 # Callbacks related to how the matcher is used by dirstate.walk.
225 230 # Subscribers to these events must monkeypatch the matcher object.
226 231 def bad(self, f, msg):
227 232 '''Callback from dirstate.walk for each explicit file that can't be
228 233 found/accessed, with an error message.'''
229 234 pass
230 235
231 236 # If an explicitdir is set, it will be called when an explicitly listed
232 237 # directory is visited.
233 238 explicitdir = None
234 239
235 240 # If an traversedir is set, it will be called when a directory discovered
236 241 # by recursive traversal is visited.
237 242 traversedir = None
238 243
239 244 def abs(self, f):
240 245 '''Convert a repo path back to path that is relative to the root of the
241 246 matcher.'''
242 247 return f
243 248
244 249 def rel(self, f):
245 250 '''Convert repo path back to path that is relative to cwd of matcher.'''
246 251 return util.pathto(self._root, self._cwd, f)
247 252
248 253 def uipath(self, f):
249 254 '''Convert repo path to a display path. If patterns or -I/-X were used
250 255 to create this matcher, the display path will be relative to cwd.
251 256 Otherwise it is relative to the root of the repo.'''
252 257 return (self._relativeuipath and self.rel(f)) or self.abs(f)
253 258
254 259 @propertycache
255 260 def _files(self):
256 261 return []
257 262
258 263 def files(self):
259 264 '''Explicitly listed files or patterns or roots:
260 265 if no patterns or .always(): empty list,
261 266 if exact: list exact files,
262 267 if not .anypats(): list all files and dirs,
263 268 else: optimal roots'''
264 269 return self._files
265 270
266 271 @propertycache
267 272 def _fileset(self):
268 273 return set(self._files)
269 274
270 275 def exact(self, f):
271 276 '''Returns True if f is in .files().'''
272 277 return f in self._fileset
273 278
274 279 def matchfn(self, f):
275 280 return False
276 281
277 282 def visitdir(self, dir):
278 283 '''Decides whether a directory should be visited based on whether it
279 284 has potential matches in it or one of its subdirectories. This is
280 285 based on the match's primary, included, and excluded patterns.
281 286
282 287 Returns the string 'all' if the given directory and all subdirectories
283 288 should be visited. Otherwise returns True or False indicating whether
284 289 the given directory should be visited.
285 290
286 291 This function's behavior is undefined if it has returned False for
287 292 one of the dir's parent directories.
288 293 '''
289 294 return False
290 295
291 296 def anypats(self):
292 297 '''Matcher uses patterns or include/exclude.'''
293 298 return False
294 299
295 300 def always(self):
296 301 '''Matcher will match everything and .files() will be empty
297 302 - optimization might be possible and necessary.'''
298 303 return False
299 304
300 305 def isexact(self):
301 306 return False
302 307
303 308 def prefix(self):
304 309 return not self.always() and not self.isexact() and not self.anypats()
305 310
306 311 class matcher(basematcher):
307 312
308 313 def __init__(self, root, cwd, normalize, patterns, include=None,
309 314 default='glob', exact=False, auditor=None, ctx=None,
310 315 listsubrepos=False, warn=None, badfn=None):
311 316 super(matcher, self).__init__(root, cwd, badfn,
312 317 relativeuipath=bool(include or patterns))
313 318 if include is None:
314 319 include = []
315 320
316 321 self._anypats = bool(include)
317 322 self._anyincludepats = False
318 323 self._always = False
319 324 self.patternspat = None
320 325 self.includepat = None
321 326
322 327 # roots are directories which are recursively included.
323 328 self._includeroots = set()
324 329 # dirs are directories which are non-recursively included.
325 330 self._includedirs = set()
326 331
327 332 matchfns = []
328 333 if include:
329 334 kindpats = normalize(include, 'glob', root, cwd, auditor, warn)
330 335 self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)',
331 336 listsubrepos, root)
332 337 self._anyincludepats = _anypats(kindpats)
333 338 roots, dirs = _rootsanddirs(kindpats)
334 339 self._includeroots.update(roots)
335 340 self._includedirs.update(dirs)
336 341 matchfns.append(im)
337 342 if exact:
338 343 if isinstance(patterns, list):
339 344 self._files = patterns
340 345 else:
341 346 self._files = list(patterns)
342 347 matchfns.append(self.exact)
343 348 elif patterns:
344 349 kindpats = normalize(patterns, default, root, cwd, auditor, warn)
345 350 if not _kindpatsalwaysmatch(kindpats):
346 351 self._files = _explicitfiles(kindpats)
347 352 self._anypats = self._anypats or _anypats(kindpats)
348 353 self.patternspat, pm = _buildmatch(ctx, kindpats, '$',
349 354 listsubrepos, root)
350 355 matchfns.append(pm)
351 356
352 357 if not matchfns:
353 358 m = util.always
354 359 self._always = True
355 360 elif len(matchfns) == 1:
356 361 m = matchfns[0]
357 362 else:
358 363 def m(f):
359 364 for matchfn in matchfns:
360 365 if not matchfn(f):
361 366 return False
362 367 return True
363 368
364 369 self.matchfn = m
365 370
366 371 @propertycache
367 372 def _dirs(self):
368 373 return set(util.dirs(self._fileset)) | {'.'}
369 374
370 375 def visitdir(self, dir):
371 376 if self.prefix() and dir in self._fileset:
372 377 return 'all'
373 378 if self._includeroots or self._includedirs:
374 379 if (not self._anyincludepats and
375 380 dir in self._includeroots):
376 381 # The condition above is essentially self.prefix() for includes
377 382 return 'all'
378 383 if ('.' not in self._includeroots and
379 384 dir not in self._includeroots and
380 385 dir not in self._includedirs and
381 386 not any(parent in self._includeroots
382 387 for parent in util.finddirs(dir))):
383 388 return False
384 389 return (not self._fileset or
385 390 '.' in self._fileset or
386 391 dir in self._fileset or
387 392 dir in self._dirs or
388 393 any(parentdir in self._fileset
389 394 for parentdir in util.finddirs(dir)))
390 395
391 396 def anypats(self):
392 397 return self._anypats
393 398
394 399 def always(self):
395 400 return self._always
396 401
397 402 def isexact(self):
398 403 return self.matchfn == self.exact
399 404
400 405 def __repr__(self):
401 406 return ('<matcher files=%r, patterns=%r, includes=%r>' %
402 407 (self._files, self.patternspat, self.includepat))
403 408
404 409 class differencematcher(basematcher):
405 410 '''Composes two matchers by matching if the first matches and the second
406 411 does not. Well, almost... If the user provides a pattern like "-X foo foo",
407 412 Mercurial actually does match "foo" against that. That's because exact
408 413 matches are treated specially. So, since this differencematcher is used for
409 414 excludes, it needs to special-case exact matching.
410 415
411 416 The second matcher's non-matching-attributes (root, cwd, bad, explicitdir,
412 417 traversedir) are ignored.
413 418
414 419 TODO: If we want to keep the behavior described above for exact matches, we
415 420 should consider instead treating the above case something like this:
416 421 union(exact(foo), difference(pattern(foo), include(foo)))
417 422 '''
418 423 def __init__(self, m1, m2):
419 424 super(differencematcher, self).__init__(m1._root, m1._cwd)
420 425 self._m1 = m1
421 426 self._m2 = m2
422 427 self.bad = m1.bad
423 428 self.explicitdir = m1.explicitdir
424 429 self.traversedir = m1.traversedir
425 430
426 431 def matchfn(self, f):
427 432 return self._m1(f) and (not self._m2(f) or self._m1.exact(f))
428 433
429 434 @propertycache
430 435 def _files(self):
431 436 if self.isexact():
432 437 return [f for f in self._m1.files() if self(f)]
433 438 # If m1 is not an exact matcher, we can't easily figure out the set of
434 439 # files, because its files() are not always files. For example, if
435 440 # m1 is "path:dir" and m2 is "rootfileins:.", we don't
436 441 # want to remove "dir" from the set even though it would match m2,
437 442 # because the "dir" in m1 may not be a file.
438 443 return self._m1.files()
439 444
440 445 def visitdir(self, dir):
441 446 if self._m2.visitdir(dir) == 'all':
442 447 # There's a bug here: If m1 matches file 'dir/file' and m2 excludes
443 448 # 'dir' (recursively), we should still visit 'dir' due to the
444 449 # exception we have for exact matches.
445 450 return False
446 451 return bool(self._m1.visitdir(dir))
447 452
448 453 def isexact(self):
449 454 return self._m1.isexact()
450 455
451 456 def anypats(self):
452 457 return self._m1.anypats() or self._m2.anypats()
453 458
454 459 def prefix(self):
455 460 return not self.always() and not self.isexact() and not self.anypats()
456 461
457 462 def __repr__(self):
458 463 return ('<differencematcher m1=%r, m2=%r>' % (self._m1, self._m2))
459 464
465 def intersectmatchers(m1, m2):
466 '''Composes two matchers by matching if both of them match.
467
468 The second matcher's non-matching-attributes (root, cwd, bad, explicitdir,
469 traversedir) are ignored.
470 '''
471 if m1 is None or m2 is None:
472 return m1 or m2
473 if m1.always():
474 m = copy.copy(m2)
475 # TODO: Consider encapsulating these things in a class so there's only
476 # one thing to copy from m1.
477 m.bad = m1.bad
478 m.explicitdir = m1.explicitdir
479 m.traversedir = m1.traversedir
480 m.abs = m1.abs
481 m.rel = m1.rel
482 m._relativeuipath |= m1._relativeuipath
483 return m
484 if m2.always():
485 m = copy.copy(m1)
486 m._relativeuipath |= m2._relativeuipath
487 return m
488 return intersectionmatcher(m1, m2)
489
490 class intersectionmatcher(basematcher):
491 def __init__(self, m1, m2):
492 super(intersectionmatcher, self).__init__(m1._root, m1._cwd)
493 self._m1 = m1
494 self._m2 = m2
495 self.bad = m1.bad
496 self.explicitdir = m1.explicitdir
497 self.traversedir = m1.traversedir
498
499 @propertycache
500 def _files(self):
501 if self.isexact():
502 m1, m2 = self._m1, self._m2
503 if not m1.isexact():
504 m1, m2 = m2, m1
505 return [f for f in m1.files() if m2(f)]
506 # It neither m1 nor m2 is an exact matcher, we can't easily intersect
507 # the set of files, because their files() are not always files. For
508 # example, if intersecting a matcher "-I glob:foo.txt" with matcher of
509 # "path:dir2", we don't want to remove "dir2" from the set.
510 return self._m1.files() + self._m2.files()
511
512 def matchfn(self, f):
513 return self._m1(f) and self._m2(f)
514
515 def visitdir(self, dir):
516 visit1 = self._m1.visitdir(dir)
517 if visit1 == 'all':
518 return self._m2.visitdir(dir)
519 # bool() because visit1=True + visit2='all' should not be 'all'
520 return bool(visit1 and self._m2.visitdir(dir))
521
522 def always(self):
523 return self._m1.always() and self._m2.always()
524
525 def isexact(self):
526 return self._m1.isexact() or self._m2.isexact()
527
528 def anypats(self):
529 return self._m1.anypats() or self._m2.anypats()
530
531 def __repr__(self):
532 return ('<intersectionmatcher m1=%r, m2=%r>' % (self._m1, self._m2))
533
460 534 class subdirmatcher(basematcher):
461 535 """Adapt a matcher to work on a subdirectory only.
462 536
463 537 The paths are remapped to remove/insert the path as needed:
464 538
465 539 >>> m1 = match('root', '', ['a.txt', 'sub/b.txt'])
466 540 >>> m2 = subdirmatcher('sub', m1)
467 541 >>> bool(m2('a.txt'))
468 542 False
469 543 >>> bool(m2('b.txt'))
470 544 True
471 545 >>> bool(m2.matchfn('a.txt'))
472 546 False
473 547 >>> bool(m2.matchfn('b.txt'))
474 548 True
475 549 >>> m2.files()
476 550 ['b.txt']
477 551 >>> m2.exact('b.txt')
478 552 True
479 553 >>> util.pconvert(m2.rel('b.txt'))
480 554 'sub/b.txt'
481 555 >>> def bad(f, msg):
482 556 ... print "%s: %s" % (f, msg)
483 557 >>> m1.bad = bad
484 558 >>> m2.bad('x.txt', 'No such file')
485 559 sub/x.txt: No such file
486 560 >>> m2.abs('c.txt')
487 561 'sub/c.txt'
488 562 """
489 563
490 564 def __init__(self, path, matcher):
491 565 super(subdirmatcher, self).__init__(matcher._root, matcher._cwd)
492 566 self._path = path
493 567 self._matcher = matcher
494 568 self._always = matcher.always()
495 569
496 570 self._files = [f[len(path) + 1:] for f in matcher._files
497 571 if f.startswith(path + "/")]
498 572
499 573 # If the parent repo had a path to this subrepo and the matcher is
500 574 # a prefix matcher, this submatcher always matches.
501 575 if matcher.prefix():
502 576 self._always = any(f == path for f in matcher._files)
503 577
504 578 def bad(self, f, msg):
505 579 self._matcher.bad(self._path + "/" + f, msg)
506 580
507 581 def abs(self, f):
508 582 return self._matcher.abs(self._path + "/" + f)
509 583
510 584 def rel(self, f):
511 585 return self._matcher.rel(self._path + "/" + f)
512 586
513 587 def uipath(self, f):
514 588 return self._matcher.uipath(self._path + "/" + f)
515 589
516 590 def matchfn(self, f):
517 591 # Some information is lost in the superclass's constructor, so we
518 592 # can not accurately create the matching function for the subdirectory
519 593 # from the inputs. Instead, we override matchfn() and visitdir() to
520 594 # call the original matcher with the subdirectory path prepended.
521 595 return self._matcher.matchfn(self._path + "/" + f)
522 596
523 597 def visitdir(self, dir):
524 598 if dir == '.':
525 599 dir = self._path
526 600 else:
527 601 dir = self._path + "/" + dir
528 602 return self._matcher.visitdir(dir)
529 603
530 604 def always(self):
531 605 return self._always
532 606
533 607 def anypats(self):
534 608 return self._matcher.anypats()
535 609
536 610 def patkind(pattern, default=None):
537 611 '''If pattern is 'kind:pat' with a known kind, return kind.'''
538 612 return _patsplit(pattern, default)[0]
539 613
540 614 def _patsplit(pattern, default):
541 615 """Split a string into the optional pattern kind prefix and the actual
542 616 pattern."""
543 617 if ':' in pattern:
544 618 kind, pat = pattern.split(':', 1)
545 619 if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
546 620 'listfile', 'listfile0', 'set', 'include', 'subinclude',
547 621 'rootfilesin'):
548 622 return kind, pat
549 623 return default, pattern
550 624
551 625 def _globre(pat):
552 626 r'''Convert an extended glob string to a regexp string.
553 627
554 628 >>> print _globre(r'?')
555 629 .
556 630 >>> print _globre(r'*')
557 631 [^/]*
558 632 >>> print _globre(r'**')
559 633 .*
560 634 >>> print _globre(r'**/a')
561 635 (?:.*/)?a
562 636 >>> print _globre(r'a/**/b')
563 637 a\/(?:.*/)?b
564 638 >>> print _globre(r'[a*?!^][^b][!c]')
565 639 [a*?!^][\^b][^c]
566 640 >>> print _globre(r'{a,b}')
567 641 (?:a|b)
568 642 >>> print _globre(r'.\*\?')
569 643 \.\*\?
570 644 '''
571 645 i, n = 0, len(pat)
572 646 res = ''
573 647 group = 0
574 648 escape = util.re.escape
575 649 def peek():
576 650 return i < n and pat[i:i + 1]
577 651 while i < n:
578 652 c = pat[i:i + 1]
579 653 i += 1
580 654 if c not in '*?[{},\\':
581 655 res += escape(c)
582 656 elif c == '*':
583 657 if peek() == '*':
584 658 i += 1
585 659 if peek() == '/':
586 660 i += 1
587 661 res += '(?:.*/)?'
588 662 else:
589 663 res += '.*'
590 664 else:
591 665 res += '[^/]*'
592 666 elif c == '?':
593 667 res += '.'
594 668 elif c == '[':
595 669 j = i
596 670 if j < n and pat[j:j + 1] in '!]':
597 671 j += 1
598 672 while j < n and pat[j:j + 1] != ']':
599 673 j += 1
600 674 if j >= n:
601 675 res += '\\['
602 676 else:
603 677 stuff = pat[i:j].replace('\\','\\\\')
604 678 i = j + 1
605 679 if stuff[0:1] == '!':
606 680 stuff = '^' + stuff[1:]
607 681 elif stuff[0:1] == '^':
608 682 stuff = '\\' + stuff
609 683 res = '%s[%s]' % (res, stuff)
610 684 elif c == '{':
611 685 group += 1
612 686 res += '(?:'
613 687 elif c == '}' and group:
614 688 res += ')'
615 689 group -= 1
616 690 elif c == ',' and group:
617 691 res += '|'
618 692 elif c == '\\':
619 693 p = peek()
620 694 if p:
621 695 i += 1
622 696 res += escape(p)
623 697 else:
624 698 res += escape(c)
625 699 else:
626 700 res += escape(c)
627 701 return res
628 702
629 703 def _regex(kind, pat, globsuffix):
630 704 '''Convert a (normalized) pattern of any kind into a regular expression.
631 705 globsuffix is appended to the regexp of globs.'''
632 706 if not pat:
633 707 return ''
634 708 if kind == 're':
635 709 return pat
636 710 if kind == 'path':
637 711 if pat == '.':
638 712 return ''
639 713 return '^' + util.re.escape(pat) + '(?:/|$)'
640 714 if kind == 'rootfilesin':
641 715 if pat == '.':
642 716 escaped = ''
643 717 else:
644 718 # Pattern is a directory name.
645 719 escaped = util.re.escape(pat) + '/'
646 720 # Anything after the pattern must be a non-directory.
647 721 return '^' + escaped + '[^/]+$'
648 722 if kind == 'relglob':
649 723 return '(?:|.*/)' + _globre(pat) + globsuffix
650 724 if kind == 'relpath':
651 725 return util.re.escape(pat) + '(?:/|$)'
652 726 if kind == 'relre':
653 727 if pat.startswith('^'):
654 728 return pat
655 729 return '.*' + pat
656 730 return _globre(pat) + globsuffix
657 731
658 732 def _buildmatch(ctx, kindpats, globsuffix, listsubrepos, root):
659 733 '''Return regexp string and a matcher function for kindpats.
660 734 globsuffix is appended to the regexp of globs.'''
661 735 matchfuncs = []
662 736
663 737 subincludes, kindpats = _expandsubinclude(kindpats, root)
664 738 if subincludes:
665 739 submatchers = {}
666 740 def matchsubinclude(f):
667 741 for prefix, matcherargs in subincludes:
668 742 if f.startswith(prefix):
669 743 mf = submatchers.get(prefix)
670 744 if mf is None:
671 745 mf = match(*matcherargs)
672 746 submatchers[prefix] = mf
673 747
674 748 if mf(f[len(prefix):]):
675 749 return True
676 750 return False
677 751 matchfuncs.append(matchsubinclude)
678 752
679 753 fset, kindpats = _expandsets(kindpats, ctx, listsubrepos)
680 754 if fset:
681 755 matchfuncs.append(fset.__contains__)
682 756
683 757 regex = ''
684 758 if kindpats:
685 759 regex, mf = _buildregexmatch(kindpats, globsuffix)
686 760 matchfuncs.append(mf)
687 761
688 762 if len(matchfuncs) == 1:
689 763 return regex, matchfuncs[0]
690 764 else:
691 765 return regex, lambda f: any(mf(f) for mf in matchfuncs)
692 766
693 767 def _buildregexmatch(kindpats, globsuffix):
694 768 """Build a match function from a list of kinds and kindpats,
695 769 return regexp string and a matcher function."""
696 770 try:
697 771 regex = '(?:%s)' % '|'.join([_regex(k, p, globsuffix)
698 772 for (k, p, s) in kindpats])
699 773 if len(regex) > 20000:
700 774 raise OverflowError
701 775 return regex, _rematcher(regex)
702 776 except OverflowError:
703 777 # We're using a Python with a tiny regex engine and we
704 778 # made it explode, so we'll divide the pattern list in two
705 779 # until it works
706 780 l = len(kindpats)
707 781 if l < 2:
708 782 raise
709 783 regexa, a = _buildregexmatch(kindpats[:l//2], globsuffix)
710 784 regexb, b = _buildregexmatch(kindpats[l//2:], globsuffix)
711 785 return regex, lambda s: a(s) or b(s)
712 786 except re.error:
713 787 for k, p, s in kindpats:
714 788 try:
715 789 _rematcher('(?:%s)' % _regex(k, p, globsuffix))
716 790 except re.error:
717 791 if s:
718 792 raise error.Abort(_("%s: invalid pattern (%s): %s") %
719 793 (s, k, p))
720 794 else:
721 795 raise error.Abort(_("invalid pattern (%s): %s") % (k, p))
722 796 raise error.Abort(_("invalid pattern"))
723 797
724 798 def _patternrootsanddirs(kindpats):
725 799 '''Returns roots and directories corresponding to each pattern.
726 800
727 801 This calculates the roots and directories exactly matching the patterns and
728 802 returns a tuple of (roots, dirs) for each. It does not return other
729 803 directories which may also need to be considered, like the parent
730 804 directories.
731 805 '''
732 806 r = []
733 807 d = []
734 808 for kind, pat, source in kindpats:
735 809 if kind == 'glob': # find the non-glob prefix
736 810 root = []
737 811 for p in pat.split('/'):
738 812 if '[' in p or '{' in p or '*' in p or '?' in p:
739 813 break
740 814 root.append(p)
741 815 r.append('/'.join(root) or '.')
742 816 elif kind in ('relpath', 'path'):
743 817 r.append(pat or '.')
744 818 elif kind in ('rootfilesin',):
745 819 d.append(pat or '.')
746 820 else: # relglob, re, relre
747 821 r.append('.')
748 822 return r, d
749 823
750 824 def _roots(kindpats):
751 825 '''Returns root directories to match recursively from the given patterns.'''
752 826 roots, dirs = _patternrootsanddirs(kindpats)
753 827 return roots
754 828
755 829 def _rootsanddirs(kindpats):
756 830 '''Returns roots and exact directories from patterns.
757 831
758 832 roots are directories to match recursively, whereas exact directories should
759 833 be matched non-recursively. The returned (roots, dirs) tuple will also
760 834 include directories that need to be implicitly considered as either, such as
761 835 parent directories.
762 836
763 837 >>> _rootsanddirs(\
764 838 [('glob', 'g/h/*', ''), ('glob', 'g/h', ''), ('glob', 'g*', '')])
765 839 (['g/h', 'g/h', '.'], ['g', '.'])
766 840 >>> _rootsanddirs(\
767 841 [('rootfilesin', 'g/h', ''), ('rootfilesin', '', '')])
768 842 ([], ['g/h', '.', 'g', '.'])
769 843 >>> _rootsanddirs(\
770 844 [('relpath', 'r', ''), ('path', 'p/p', ''), ('path', '', '')])
771 845 (['r', 'p/p', '.'], ['p', '.'])
772 846 >>> _rootsanddirs(\
773 847 [('relglob', 'rg*', ''), ('re', 're/', ''), ('relre', 'rr', '')])
774 848 (['.', '.', '.'], ['.'])
775 849 '''
776 850 r, d = _patternrootsanddirs(kindpats)
777 851
778 852 # Append the parents as non-recursive/exact directories, since they must be
779 853 # scanned to get to either the roots or the other exact directories.
780 854 d.extend(util.dirs(d))
781 855 d.extend(util.dirs(r))
782 856 # util.dirs() does not include the root directory, so add it manually
783 857 d.append('.')
784 858
785 859 return r, d
786 860
787 861 def _explicitfiles(kindpats):
788 862 '''Returns the potential explicit filenames from the patterns.
789 863
790 864 >>> _explicitfiles([('path', 'foo/bar', '')])
791 865 ['foo/bar']
792 866 >>> _explicitfiles([('rootfilesin', 'foo/bar', '')])
793 867 []
794 868 '''
795 869 # Keep only the pattern kinds where one can specify filenames (vs only
796 870 # directory names).
797 871 filable = [kp for kp in kindpats if kp[0] not in ('rootfilesin',)]
798 872 return _roots(filable)
799 873
800 874 def _anypats(kindpats):
801 875 for kind, pat, source in kindpats:
802 876 if kind in ('glob', 're', 'relglob', 'relre', 'set', 'rootfilesin'):
803 877 return True
804 878
805 879 _commentre = None
806 880
807 881 def readpatternfile(filepath, warn, sourceinfo=False):
808 882 '''parse a pattern file, returning a list of
809 883 patterns. These patterns should be given to compile()
810 884 to be validated and converted into a match function.
811 885
812 886 trailing white space is dropped.
813 887 the escape character is backslash.
814 888 comments start with #.
815 889 empty lines are skipped.
816 890
817 891 lines can be of the following formats:
818 892
819 893 syntax: regexp # defaults following lines to non-rooted regexps
820 894 syntax: glob # defaults following lines to non-rooted globs
821 895 re:pattern # non-rooted regular expression
822 896 glob:pattern # non-rooted glob
823 897 pattern # pattern of the current default type
824 898
825 899 if sourceinfo is set, returns a list of tuples:
826 900 (pattern, lineno, originalline). This is useful to debug ignore patterns.
827 901 '''
828 902
829 903 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:',
830 904 'include': 'include', 'subinclude': 'subinclude'}
831 905 syntax = 'relre:'
832 906 patterns = []
833 907
834 908 fp = open(filepath, 'rb')
835 909 for lineno, line in enumerate(util.iterfile(fp), start=1):
836 910 if "#" in line:
837 911 global _commentre
838 912 if not _commentre:
839 913 _commentre = util.re.compile(br'((?:^|[^\\])(?:\\\\)*)#.*')
840 914 # remove comments prefixed by an even number of escapes
841 915 m = _commentre.search(line)
842 916 if m:
843 917 line = line[:m.end(1)]
844 918 # fixup properly escaped comments that survived the above
845 919 line = line.replace("\\#", "#")
846 920 line = line.rstrip()
847 921 if not line:
848 922 continue
849 923
850 924 if line.startswith('syntax:'):
851 925 s = line[7:].strip()
852 926 try:
853 927 syntax = syntaxes[s]
854 928 except KeyError:
855 929 if warn:
856 930 warn(_("%s: ignoring invalid syntax '%s'\n") %
857 931 (filepath, s))
858 932 continue
859 933
860 934 linesyntax = syntax
861 935 for s, rels in syntaxes.iteritems():
862 936 if line.startswith(rels):
863 937 linesyntax = rels
864 938 line = line[len(rels):]
865 939 break
866 940 elif line.startswith(s+':'):
867 941 linesyntax = rels
868 942 line = line[len(s) + 1:]
869 943 break
870 944 if sourceinfo:
871 945 patterns.append((linesyntax + line, lineno, line))
872 946 else:
873 947 patterns.append(linesyntax + line)
874 948 fp.close()
875 949 return patterns
@@ -1,518 +1,518
1 1 $ hg init t
2 2 $ cd t
3 3 $ mkdir -p beans
4 4 $ for b in kidney navy turtle borlotti black pinto; do
5 5 > echo $b > beans/$b
6 6 > done
7 7 $ mkdir -p mammals/Procyonidae
8 8 $ for m in cacomistle coatimundi raccoon; do
9 9 > echo $m > mammals/Procyonidae/$m
10 10 > done
11 11 $ echo skunk > mammals/skunk
12 12 $ echo fennel > fennel
13 13 $ echo fenugreek > fenugreek
14 14 $ echo fiddlehead > fiddlehead
15 15 $ hg addremove
16 16 adding beans/black
17 17 adding beans/borlotti
18 18 adding beans/kidney
19 19 adding beans/navy
20 20 adding beans/pinto
21 21 adding beans/turtle
22 22 adding fennel
23 23 adding fenugreek
24 24 adding fiddlehead
25 25 adding mammals/Procyonidae/cacomistle
26 26 adding mammals/Procyonidae/coatimundi
27 27 adding mammals/Procyonidae/raccoon
28 28 adding mammals/skunk
29 29 $ hg commit -m "commit #0"
30 30
31 31 $ hg debugwalk
32 32 matcher: <matcher files=[], patterns=None, includes=None>
33 33 f beans/black beans/black
34 34 f beans/borlotti beans/borlotti
35 35 f beans/kidney beans/kidney
36 36 f beans/navy beans/navy
37 37 f beans/pinto beans/pinto
38 38 f beans/turtle beans/turtle
39 39 f fennel fennel
40 40 f fenugreek fenugreek
41 41 f fiddlehead fiddlehead
42 42 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
43 43 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
44 44 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
45 45 f mammals/skunk mammals/skunk
46 46 $ hg debugwalk -I.
47 47 matcher: <matcher files=[], patterns=None, includes='(?:)'>
48 48 f beans/black beans/black
49 49 f beans/borlotti beans/borlotti
50 50 f beans/kidney beans/kidney
51 51 f beans/navy beans/navy
52 52 f beans/pinto beans/pinto
53 53 f beans/turtle beans/turtle
54 54 f fennel fennel
55 55 f fenugreek fenugreek
56 56 f fiddlehead fiddlehead
57 57 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
58 58 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
59 59 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
60 60 f mammals/skunk mammals/skunk
61 61
62 62 $ cd mammals
63 63 $ hg debugwalk
64 64 matcher: <matcher files=[], patterns=None, includes=None>
65 65 f beans/black ../beans/black
66 66 f beans/borlotti ../beans/borlotti
67 67 f beans/kidney ../beans/kidney
68 68 f beans/navy ../beans/navy
69 69 f beans/pinto ../beans/pinto
70 70 f beans/turtle ../beans/turtle
71 71 f fennel ../fennel
72 72 f fenugreek ../fenugreek
73 73 f fiddlehead ../fiddlehead
74 74 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
75 75 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
76 76 f mammals/Procyonidae/raccoon Procyonidae/raccoon
77 77 f mammals/skunk skunk
78 78 $ hg debugwalk -X ../beans
79 79 matcher: <differencematcher m1=<matcher files=[], patterns=None, includes=None>, m2=<matcher files=[], patterns=None, includes='(?:beans(?:/|$))'>>
80 80 f fennel ../fennel
81 81 f fenugreek ../fenugreek
82 82 f fiddlehead ../fiddlehead
83 83 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
84 84 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
85 85 f mammals/Procyonidae/raccoon Procyonidae/raccoon
86 86 f mammals/skunk skunk
87 87 $ hg debugwalk -I '*k'
88 88 matcher: <matcher files=[], patterns=None, includes='(?:mammals\\/[^/]*k(?:/|$))'>
89 89 f mammals/skunk skunk
90 90 $ hg debugwalk -I 'glob:*k'
91 91 matcher: <matcher files=[], patterns=None, includes='(?:mammals\\/[^/]*k(?:/|$))'>
92 92 f mammals/skunk skunk
93 93 $ hg debugwalk -I 'relglob:*k'
94 94 matcher: <matcher files=[], patterns=None, includes='(?:(?:|.*/)[^/]*k(?:/|$))'>
95 95 f beans/black ../beans/black
96 96 f fenugreek ../fenugreek
97 97 f mammals/skunk skunk
98 98 $ hg debugwalk -I 'relglob:*k' .
99 matcher: <matcher files=['mammals'], patterns='(?:mammals(?:/|$))', includes='(?:(?:|.*/)[^/]*k(?:/|$))'>
99 matcher: <intersectionmatcher m1=<matcher files=['mammals'], patterns='(?:mammals(?:/|$))', includes=None>, m2=<matcher files=[], patterns=None, includes='(?:(?:|.*/)[^/]*k(?:/|$))'>>
100 100 f mammals/skunk skunk
101 101 $ hg debugwalk -I 're:.*k$'
102 102 matcher: <matcher files=[], patterns=None, includes='(?:.*k$)'>
103 103 f beans/black ../beans/black
104 104 f fenugreek ../fenugreek
105 105 f mammals/skunk skunk
106 106 $ hg debugwalk -I 'relre:.*k$'
107 107 matcher: <matcher files=[], patterns=None, includes='(?:.*.*k$)'>
108 108 f beans/black ../beans/black
109 109 f fenugreek ../fenugreek
110 110 f mammals/skunk skunk
111 111 $ hg debugwalk -I 'path:beans'
112 112 matcher: <matcher files=[], patterns=None, includes='(?:^beans(?:/|$))'>
113 113 f beans/black ../beans/black
114 114 f beans/borlotti ../beans/borlotti
115 115 f beans/kidney ../beans/kidney
116 116 f beans/navy ../beans/navy
117 117 f beans/pinto ../beans/pinto
118 118 f beans/turtle ../beans/turtle
119 119 $ hg debugwalk -I 'relpath:detour/../../beans'
120 120 matcher: <matcher files=[], patterns=None, includes='(?:beans(?:/|$))'>
121 121 f beans/black ../beans/black
122 122 f beans/borlotti ../beans/borlotti
123 123 f beans/kidney ../beans/kidney
124 124 f beans/navy ../beans/navy
125 125 f beans/pinto ../beans/pinto
126 126 f beans/turtle ../beans/turtle
127 127
128 128 $ hg debugwalk 'rootfilesin:'
129 129 matcher: <matcher files=[], patterns='(?:^[^/]+$)', includes=None>
130 130 f fennel ../fennel
131 131 f fenugreek ../fenugreek
132 132 f fiddlehead ../fiddlehead
133 133 $ hg debugwalk -I 'rootfilesin:'
134 134 matcher: <matcher files=[], patterns=None, includes='(?:^[^/]+$)'>
135 135 f fennel ../fennel
136 136 f fenugreek ../fenugreek
137 137 f fiddlehead ../fiddlehead
138 138 $ hg debugwalk 'rootfilesin:.'
139 139 matcher: <matcher files=[], patterns='(?:^[^/]+$)', includes=None>
140 140 f fennel ../fennel
141 141 f fenugreek ../fenugreek
142 142 f fiddlehead ../fiddlehead
143 143 $ hg debugwalk -I 'rootfilesin:.'
144 144 matcher: <matcher files=[], patterns=None, includes='(?:^[^/]+$)'>
145 145 f fennel ../fennel
146 146 f fenugreek ../fenugreek
147 147 f fiddlehead ../fiddlehead
148 148 $ hg debugwalk -X 'rootfilesin:'
149 149 matcher: <differencematcher m1=<matcher files=[], patterns=None, includes=None>, m2=<matcher files=[], patterns=None, includes='(?:^[^/]+$)'>>
150 150 f beans/black ../beans/black
151 151 f beans/borlotti ../beans/borlotti
152 152 f beans/kidney ../beans/kidney
153 153 f beans/navy ../beans/navy
154 154 f beans/pinto ../beans/pinto
155 155 f beans/turtle ../beans/turtle
156 156 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
157 157 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
158 158 f mammals/Procyonidae/raccoon Procyonidae/raccoon
159 159 f mammals/skunk skunk
160 160 $ hg debugwalk 'rootfilesin:fennel'
161 161 matcher: <matcher files=[], patterns='(?:^fennel/[^/]+$)', includes=None>
162 162 $ hg debugwalk -I 'rootfilesin:fennel'
163 163 matcher: <matcher files=[], patterns=None, includes='(?:^fennel/[^/]+$)'>
164 164 $ hg debugwalk 'rootfilesin:skunk'
165 165 matcher: <matcher files=[], patterns='(?:^skunk/[^/]+$)', includes=None>
166 166 $ hg debugwalk -I 'rootfilesin:skunk'
167 167 matcher: <matcher files=[], patterns=None, includes='(?:^skunk/[^/]+$)'>
168 168 $ hg debugwalk 'rootfilesin:beans'
169 169 matcher: <matcher files=[], patterns='(?:^beans/[^/]+$)', includes=None>
170 170 f beans/black ../beans/black
171 171 f beans/borlotti ../beans/borlotti
172 172 f beans/kidney ../beans/kidney
173 173 f beans/navy ../beans/navy
174 174 f beans/pinto ../beans/pinto
175 175 f beans/turtle ../beans/turtle
176 176 $ hg debugwalk -I 'rootfilesin:beans'
177 177 matcher: <matcher files=[], patterns=None, includes='(?:^beans/[^/]+$)'>
178 178 f beans/black ../beans/black
179 179 f beans/borlotti ../beans/borlotti
180 180 f beans/kidney ../beans/kidney
181 181 f beans/navy ../beans/navy
182 182 f beans/pinto ../beans/pinto
183 183 f beans/turtle ../beans/turtle
184 184 $ hg debugwalk 'rootfilesin:mammals'
185 185 matcher: <matcher files=[], patterns='(?:^mammals/[^/]+$)', includes=None>
186 186 f mammals/skunk skunk
187 187 $ hg debugwalk -I 'rootfilesin:mammals'
188 188 matcher: <matcher files=[], patterns=None, includes='(?:^mammals/[^/]+$)'>
189 189 f mammals/skunk skunk
190 190 $ hg debugwalk 'rootfilesin:mammals/'
191 191 matcher: <matcher files=[], patterns='(?:^mammals/[^/]+$)', includes=None>
192 192 f mammals/skunk skunk
193 193 $ hg debugwalk -I 'rootfilesin:mammals/'
194 194 matcher: <matcher files=[], patterns=None, includes='(?:^mammals/[^/]+$)'>
195 195 f mammals/skunk skunk
196 196 $ hg debugwalk -X 'rootfilesin:mammals'
197 197 matcher: <differencematcher m1=<matcher files=[], patterns=None, includes=None>, m2=<matcher files=[], patterns=None, includes='(?:^mammals/[^/]+$)'>>
198 198 f beans/black ../beans/black
199 199 f beans/borlotti ../beans/borlotti
200 200 f beans/kidney ../beans/kidney
201 201 f beans/navy ../beans/navy
202 202 f beans/pinto ../beans/pinto
203 203 f beans/turtle ../beans/turtle
204 204 f fennel ../fennel
205 205 f fenugreek ../fenugreek
206 206 f fiddlehead ../fiddlehead
207 207 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
208 208 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
209 209 f mammals/Procyonidae/raccoon Procyonidae/raccoon
210 210
211 211 $ hg debugwalk .
212 212 matcher: <matcher files=['mammals'], patterns='(?:mammals(?:/|$))', includes=None>
213 213 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
214 214 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
215 215 f mammals/Procyonidae/raccoon Procyonidae/raccoon
216 216 f mammals/skunk skunk
217 217 $ hg debugwalk -I.
218 218 matcher: <matcher files=[], patterns=None, includes='(?:mammals(?:/|$))'>
219 219 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
220 220 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
221 221 f mammals/Procyonidae/raccoon Procyonidae/raccoon
222 222 f mammals/skunk skunk
223 223 $ hg debugwalk Procyonidae
224 224 matcher: <matcher files=['mammals/Procyonidae'], patterns='(?:mammals\\/Procyonidae(?:/|$))', includes=None>
225 225 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
226 226 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
227 227 f mammals/Procyonidae/raccoon Procyonidae/raccoon
228 228
229 229 $ cd Procyonidae
230 230 $ hg debugwalk .
231 231 matcher: <matcher files=['mammals/Procyonidae'], patterns='(?:mammals\\/Procyonidae(?:/|$))', includes=None>
232 232 f mammals/Procyonidae/cacomistle cacomistle
233 233 f mammals/Procyonidae/coatimundi coatimundi
234 234 f mammals/Procyonidae/raccoon raccoon
235 235 $ hg debugwalk ..
236 236 matcher: <matcher files=['mammals'], patterns='(?:mammals(?:/|$))', includes=None>
237 237 f mammals/Procyonidae/cacomistle cacomistle
238 238 f mammals/Procyonidae/coatimundi coatimundi
239 239 f mammals/Procyonidae/raccoon raccoon
240 240 f mammals/skunk ../skunk
241 241 $ cd ..
242 242
243 243 $ hg debugwalk ../beans
244 244 matcher: <matcher files=['beans'], patterns='(?:beans(?:/|$))', includes=None>
245 245 f beans/black ../beans/black
246 246 f beans/borlotti ../beans/borlotti
247 247 f beans/kidney ../beans/kidney
248 248 f beans/navy ../beans/navy
249 249 f beans/pinto ../beans/pinto
250 250 f beans/turtle ../beans/turtle
251 251 $ hg debugwalk .
252 252 matcher: <matcher files=['mammals'], patterns='(?:mammals(?:/|$))', includes=None>
253 253 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
254 254 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
255 255 f mammals/Procyonidae/raccoon Procyonidae/raccoon
256 256 f mammals/skunk skunk
257 257 $ hg debugwalk .hg
258 258 abort: path 'mammals/.hg' is inside nested repo 'mammals' (glob)
259 259 [255]
260 260 $ hg debugwalk ../.hg
261 261 abort: path contains illegal component: .hg
262 262 [255]
263 263 $ cd ..
264 264
265 265 $ hg debugwalk -Ibeans
266 266 matcher: <matcher files=[], patterns=None, includes='(?:beans(?:/|$))'>
267 267 f beans/black beans/black
268 268 f beans/borlotti beans/borlotti
269 269 f beans/kidney beans/kidney
270 270 f beans/navy beans/navy
271 271 f beans/pinto beans/pinto
272 272 f beans/turtle beans/turtle
273 273 $ hg debugwalk -I '{*,{b,m}*/*}k'
274 274 matcher: <matcher files=[], patterns=None, includes='(?:(?:[^/]*|(?:b|m)[^/]*\\/[^/]*)k(?:/|$))'>
275 275 f beans/black beans/black
276 276 f fenugreek fenugreek
277 277 f mammals/skunk mammals/skunk
278 278 $ hg debugwalk -Ibeans mammals
279 matcher: <matcher files=['mammals'], patterns='(?:mammals(?:/|$))', includes='(?:beans(?:/|$))'>
279 matcher: <intersectionmatcher m1=<matcher files=['mammals'], patterns='(?:mammals(?:/|$))', includes=None>, m2=<matcher files=[], patterns=None, includes='(?:beans(?:/|$))'>>
280 280 $ hg debugwalk -Inon-existent
281 281 matcher: <matcher files=[], patterns=None, includes='(?:non\\-existent(?:/|$))'>
282 282 $ hg debugwalk -Inon-existent -Ibeans/black
283 283 matcher: <matcher files=[], patterns=None, includes='(?:non\\-existent(?:/|$)|beans\\/black(?:/|$))'>
284 284 f beans/black beans/black
285 285 $ hg debugwalk -Ibeans beans/black
286 matcher: <matcher files=['beans/black'], patterns='(?:beans\\/black(?:/|$))', includes='(?:beans(?:/|$))'>
286 matcher: <intersectionmatcher m1=<matcher files=['beans/black'], patterns='(?:beans\\/black(?:/|$))', includes=None>, m2=<matcher files=[], patterns=None, includes='(?:beans(?:/|$))'>>
287 287 f beans/black beans/black exact
288 288 $ hg debugwalk -Ibeans/black beans
289 matcher: <matcher files=['beans'], patterns='(?:beans(?:/|$))', includes='(?:beans\\/black(?:/|$))'>
289 matcher: <intersectionmatcher m1=<matcher files=['beans'], patterns='(?:beans(?:/|$))', includes=None>, m2=<matcher files=[], patterns=None, includes='(?:beans\\/black(?:/|$))'>>
290 290 f beans/black beans/black
291 291 $ hg debugwalk -Xbeans/black beans
292 292 matcher: <differencematcher m1=<matcher files=['beans'], patterns='(?:beans(?:/|$))', includes=None>, m2=<matcher files=[], patterns=None, includes='(?:beans\\/black(?:/|$))'>>
293 293 f beans/borlotti beans/borlotti
294 294 f beans/kidney beans/kidney
295 295 f beans/navy beans/navy
296 296 f beans/pinto beans/pinto
297 297 f beans/turtle beans/turtle
298 298 $ hg debugwalk -Xbeans/black -Ibeans
299 299 matcher: <differencematcher m1=<matcher files=[], patterns=None, includes='(?:beans(?:/|$))'>, m2=<matcher files=[], patterns=None, includes='(?:beans\\/black(?:/|$))'>>
300 300 f beans/borlotti beans/borlotti
301 301 f beans/kidney beans/kidney
302 302 f beans/navy beans/navy
303 303 f beans/pinto beans/pinto
304 304 f beans/turtle beans/turtle
305 305 $ hg debugwalk -Xbeans/black beans/black
306 306 matcher: <differencematcher m1=<matcher files=['beans/black'], patterns='(?:beans\\/black(?:/|$))', includes=None>, m2=<matcher files=[], patterns=None, includes='(?:beans\\/black(?:/|$))'>>
307 307 f beans/black beans/black exact
308 308 $ hg debugwalk -Xbeans/black -Ibeans/black
309 309 matcher: <differencematcher m1=<matcher files=[], patterns=None, includes='(?:beans\\/black(?:/|$))'>, m2=<matcher files=[], patterns=None, includes='(?:beans\\/black(?:/|$))'>>
310 310 $ hg debugwalk -Xbeans beans/black
311 311 matcher: <differencematcher m1=<matcher files=['beans/black'], patterns='(?:beans\\/black(?:/|$))', includes=None>, m2=<matcher files=[], patterns=None, includes='(?:beans(?:/|$))'>>
312 312 f beans/black beans/black exact
313 313 $ hg debugwalk -Xbeans -Ibeans/black
314 314 matcher: <differencematcher m1=<matcher files=[], patterns=None, includes='(?:beans\\/black(?:/|$))'>, m2=<matcher files=[], patterns=None, includes='(?:beans(?:/|$))'>>
315 315 $ hg debugwalk 'glob:mammals/../beans/b*'
316 316 matcher: <matcher files=['beans'], patterns='(?:beans\\/b[^/]*$)', includes=None>
317 317 f beans/black beans/black
318 318 f beans/borlotti beans/borlotti
319 319 $ hg debugwalk '-X*/Procyonidae' mammals
320 320 matcher: <differencematcher m1=<matcher files=['mammals'], patterns='(?:mammals(?:/|$))', includes=None>, m2=<matcher files=[], patterns=None, includes='(?:[^/]*\\/Procyonidae(?:/|$))'>>
321 321 f mammals/skunk mammals/skunk
322 322 $ hg debugwalk path:mammals
323 323 matcher: <matcher files=['mammals'], patterns='(?:^mammals(?:/|$))', includes=None>
324 324 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
325 325 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
326 326 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
327 327 f mammals/skunk mammals/skunk
328 328 $ hg debugwalk ..
329 329 abort: .. not under root '$TESTTMP/t' (glob)
330 330 [255]
331 331 $ hg debugwalk beans/../..
332 332 abort: beans/../.. not under root '$TESTTMP/t' (glob)
333 333 [255]
334 334 $ hg debugwalk .hg
335 335 abort: path contains illegal component: .hg
336 336 [255]
337 337 $ hg debugwalk beans/../.hg
338 338 abort: path contains illegal component: .hg
339 339 [255]
340 340 $ hg debugwalk beans/../.hg/data
341 341 abort: path contains illegal component: .hg/data (glob)
342 342 [255]
343 343 $ hg debugwalk beans/.hg
344 344 abort: path 'beans/.hg' is inside nested repo 'beans' (glob)
345 345 [255]
346 346
347 347 Test absolute paths:
348 348
349 349 $ hg debugwalk `pwd`/beans
350 350 matcher: <matcher files=['beans'], patterns='(?:beans(?:/|$))', includes=None>
351 351 f beans/black beans/black
352 352 f beans/borlotti beans/borlotti
353 353 f beans/kidney beans/kidney
354 354 f beans/navy beans/navy
355 355 f beans/pinto beans/pinto
356 356 f beans/turtle beans/turtle
357 357 $ hg debugwalk `pwd`/..
358 358 abort: $TESTTMP/t/.. not under root '$TESTTMP/t' (glob)
359 359 [255]
360 360
361 361 Test patterns:
362 362
363 363 $ hg debugwalk glob:\*
364 364 matcher: <matcher files=['.'], patterns='(?:[^/]*$)', includes=None>
365 365 f fennel fennel
366 366 f fenugreek fenugreek
367 367 f fiddlehead fiddlehead
368 368 #if eol-in-paths
369 369 $ echo glob:glob > glob:glob
370 370 $ hg addremove
371 371 adding glob:glob
372 372 warning: filename contains ':', which is reserved on Windows: 'glob:glob'
373 373 $ hg debugwalk glob:\*
374 374 matcher: <matcher files=['.'], patterns='(?:[^/]*$)', includes=None>
375 375 f fennel fennel
376 376 f fenugreek fenugreek
377 377 f fiddlehead fiddlehead
378 378 f glob:glob glob:glob
379 379 $ hg debugwalk glob:glob
380 380 matcher: <matcher files=['glob'], patterns='(?:glob$)', includes=None>
381 381 glob: No such file or directory
382 382 $ hg debugwalk glob:glob:glob
383 383 matcher: <matcher files=['glob:glob'], patterns='(?:glob\\:glob$)', includes=None>
384 384 f glob:glob glob:glob exact
385 385 $ hg debugwalk path:glob:glob
386 386 matcher: <matcher files=['glob:glob'], patterns='(?:^glob\\:glob(?:/|$))', includes=None>
387 387 f glob:glob glob:glob exact
388 388 $ rm glob:glob
389 389 $ hg addremove
390 390 removing glob:glob
391 391 #endif
392 392
393 393 $ hg debugwalk 'glob:**e'
394 394 matcher: <matcher files=['.'], patterns='(?:.*e$)', includes=None>
395 395 f beans/turtle beans/turtle
396 396 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
397 397
398 398 $ hg debugwalk 're:.*[kb]$'
399 399 matcher: <matcher files=['.'], patterns='(?:.*[kb]$)', includes=None>
400 400 f beans/black beans/black
401 401 f fenugreek fenugreek
402 402 f mammals/skunk mammals/skunk
403 403
404 404 $ hg debugwalk path:beans/black
405 405 matcher: <matcher files=['beans/black'], patterns='(?:^beans\\/black(?:/|$))', includes=None>
406 406 f beans/black beans/black exact
407 407 $ hg debugwalk path:beans//black
408 408 matcher: <matcher files=['beans/black'], patterns='(?:^beans\\/black(?:/|$))', includes=None>
409 409 f beans/black beans/black exact
410 410
411 411 $ hg debugwalk relglob:Procyonidae
412 412 matcher: <matcher files=['.'], patterns='(?:(?:|.*/)Procyonidae$)', includes=None>
413 413 $ hg debugwalk 'relglob:Procyonidae/**'
414 414 matcher: <matcher files=['.'], patterns='(?:(?:|.*/)Procyonidae\\/.*$)', includes=None>
415 415 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
416 416 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
417 417 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
418 418 $ hg debugwalk 'relglob:Procyonidae/**' fennel
419 419 matcher: <matcher files=['.', 'fennel'], patterns='(?:(?:|.*/)Procyonidae\\/.*$|fennel(?:/|$))', includes=None>
420 420 f fennel fennel exact
421 421 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
422 422 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
423 423 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
424 424 $ hg debugwalk beans 'glob:beans/*'
425 425 matcher: <matcher files=['beans', 'beans'], patterns='(?:beans(?:/|$)|beans\\/[^/]*$)', includes=None>
426 426 f beans/black beans/black
427 427 f beans/borlotti beans/borlotti
428 428 f beans/kidney beans/kidney
429 429 f beans/navy beans/navy
430 430 f beans/pinto beans/pinto
431 431 f beans/turtle beans/turtle
432 432 $ hg debugwalk 'glob:mamm**'
433 433 matcher: <matcher files=['.'], patterns='(?:mamm.*$)', includes=None>
434 434 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
435 435 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
436 436 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
437 437 f mammals/skunk mammals/skunk
438 438 $ hg debugwalk 'glob:mamm**' fennel
439 439 matcher: <matcher files=['.', 'fennel'], patterns='(?:mamm.*$|fennel(?:/|$))', includes=None>
440 440 f fennel fennel exact
441 441 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
442 442 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
443 443 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
444 444 f mammals/skunk mammals/skunk
445 445 $ hg debugwalk 'glob:j*'
446 446 matcher: <matcher files=['.'], patterns='(?:j[^/]*$)', includes=None>
447 447 $ hg debugwalk NOEXIST
448 448 matcher: <matcher files=['NOEXIST'], patterns='(?:NOEXIST(?:/|$))', includes=None>
449 449 NOEXIST: * (glob)
450 450
451 451 #if fifo
452 452 $ mkfifo fifo
453 453 $ hg debugwalk fifo
454 454 matcher: <matcher files=['fifo'], patterns='(?:fifo(?:/|$))', includes=None>
455 455 fifo: unsupported file type (type is fifo)
456 456 #endif
457 457
458 458 $ rm fenugreek
459 459 $ hg debugwalk fenugreek
460 460 matcher: <matcher files=['fenugreek'], patterns='(?:fenugreek(?:/|$))', includes=None>
461 461 f fenugreek fenugreek exact
462 462 $ hg rm fenugreek
463 463 $ hg debugwalk fenugreek
464 464 matcher: <matcher files=['fenugreek'], patterns='(?:fenugreek(?:/|$))', includes=None>
465 465 f fenugreek fenugreek exact
466 466 $ touch new
467 467 $ hg debugwalk new
468 468 matcher: <matcher files=['new'], patterns='(?:new(?:/|$))', includes=None>
469 469 f new new exact
470 470
471 471 $ mkdir ignored
472 472 $ touch ignored/file
473 473 $ echo '^ignored$' > .hgignore
474 474 $ hg debugwalk ignored
475 475 matcher: <matcher files=['ignored'], patterns='(?:ignored(?:/|$))', includes=None>
476 476 $ hg debugwalk ignored/file
477 477 matcher: <matcher files=['ignored/file'], patterns='(?:ignored\\/file(?:/|$))', includes=None>
478 478 f ignored/file ignored/file exact
479 479
480 480 Test listfile and listfile0
481 481
482 482 $ $PYTHON -c "file('listfile0', 'wb').write('fenugreek\0new\0')"
483 483 $ hg debugwalk -I 'listfile0:listfile0'
484 484 matcher: <matcher files=[], patterns=None, includes='(?:fenugreek(?:/|$)|new(?:/|$))'>
485 485 f fenugreek fenugreek
486 486 f new new
487 487 $ $PYTHON -c "file('listfile', 'wb').write('fenugreek\nnew\r\nmammals/skunk\n')"
488 488 $ hg debugwalk -I 'listfile:listfile'
489 489 matcher: <matcher files=[], patterns=None, includes='(?:fenugreek(?:/|$)|new(?:/|$)|mammals\\/skunk(?:/|$))'>
490 490 f fenugreek fenugreek
491 491 f mammals/skunk mammals/skunk
492 492 f new new
493 493
494 494 $ cd ..
495 495 $ hg debugwalk -R t t/mammals/skunk
496 496 matcher: <matcher files=['mammals/skunk'], patterns='(?:mammals\\/skunk(?:/|$))', includes=None>
497 497 f mammals/skunk t/mammals/skunk exact
498 498 $ mkdir t2
499 499 $ cd t2
500 500 $ hg debugwalk -R ../t ../t/mammals/skunk
501 501 matcher: <matcher files=['mammals/skunk'], patterns='(?:mammals\\/skunk(?:/|$))', includes=None>
502 502 f mammals/skunk ../t/mammals/skunk exact
503 503 $ hg debugwalk --cwd ../t mammals/skunk
504 504 matcher: <matcher files=['mammals/skunk'], patterns='(?:mammals\\/skunk(?:/|$))', includes=None>
505 505 f mammals/skunk mammals/skunk exact
506 506
507 507 $ cd ..
508 508
509 509 Test split patterns on overflow
510 510
511 511 $ cd t
512 512 $ echo fennel > overflow.list
513 513 $ $PYTHON -c "for i in xrange(20000 / 100): print 'x' * 100" >> overflow.list
514 514 $ echo fenugreek >> overflow.list
515 515 $ hg debugwalk 'listfile:overflow.list' 2>&1 | egrep -v '(^matcher: |^xxx)'
516 516 f fennel fennel exact
517 517 f fenugreek fenugreek exact
518 518 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now