##// END OF EJS Templates
match: sort patterns before compiling them into a regex...
marmoute -
r51285:47686726 stable
parent child Browse files
Show More
@@ -1,1664 +1,1665 b''
1 1 # match.py - filename matching
2 2 #
3 3 # Copyright 2008, 2009 Olivia Mackall <olivia@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
9 9 import bisect
10 10 import copy
11 11 import itertools
12 12 import os
13 13 import re
14 14
15 15 from .i18n import _
16 16 from .pycompat import open
17 17 from . import (
18 18 encoding,
19 19 error,
20 20 pathutil,
21 21 policy,
22 22 pycompat,
23 23 util,
24 24 )
25 25 from .utils import stringutil
26 26
27 27 rustmod = policy.importrust('dirstate')
28 28
29 29 allpatternkinds = (
30 30 b're',
31 31 b'glob',
32 32 b'path',
33 33 b'relglob',
34 34 b'relpath',
35 35 b'relre',
36 36 b'rootglob',
37 37 b'listfile',
38 38 b'listfile0',
39 39 b'set',
40 40 b'include',
41 41 b'subinclude',
42 42 b'rootfilesin',
43 43 )
44 44 cwdrelativepatternkinds = (b'relpath', b'glob')
45 45
46 46 propertycache = util.propertycache
47 47
48 48
49 49 def _rematcher(regex):
50 50 """compile the regexp with the best available regexp engine and return a
51 51 matcher function"""
52 52 m = util.re.compile(regex)
53 53 try:
54 54 # slightly faster, provided by facebook's re2 bindings
55 55 return m.test_match
56 56 except AttributeError:
57 57 return m.match
58 58
59 59
60 60 def _expandsets(cwd, kindpats, ctx=None, listsubrepos=False, badfn=None):
61 61 '''Returns the kindpats list with the 'set' patterns expanded to matchers'''
62 62 matchers = []
63 63 other = []
64 64
65 65 for kind, pat, source in kindpats:
66 66 if kind == b'set':
67 67 if ctx is None:
68 68 raise error.ProgrammingError(
69 69 b"fileset expression with no context"
70 70 )
71 71 matchers.append(ctx.matchfileset(cwd, pat, badfn=badfn))
72 72
73 73 if listsubrepos:
74 74 for subpath in ctx.substate:
75 75 sm = ctx.sub(subpath).matchfileset(cwd, pat, badfn=badfn)
76 76 pm = prefixdirmatcher(subpath, sm, badfn=badfn)
77 77 matchers.append(pm)
78 78
79 79 continue
80 80 other.append((kind, pat, source))
81 81 return matchers, other
82 82
83 83
84 84 def _expandsubinclude(kindpats, root):
85 85 """Returns the list of subinclude matcher args and the kindpats without the
86 86 subincludes in it."""
87 87 relmatchers = []
88 88 other = []
89 89
90 90 for kind, pat, source in kindpats:
91 91 if kind == b'subinclude':
92 92 sourceroot = pathutil.dirname(util.normpath(source))
93 93 pat = util.pconvert(pat)
94 94 path = pathutil.join(sourceroot, pat)
95 95
96 96 newroot = pathutil.dirname(path)
97 97 matcherargs = (newroot, b'', [], [b'include:%s' % path])
98 98
99 99 prefix = pathutil.canonpath(root, root, newroot)
100 100 if prefix:
101 101 prefix += b'/'
102 102 relmatchers.append((prefix, matcherargs))
103 103 else:
104 104 other.append((kind, pat, source))
105 105
106 106 return relmatchers, other
107 107
108 108
109 109 def _kindpatsalwaysmatch(kindpats):
110 110 """Checks whether the kindspats match everything, as e.g.
111 111 'relpath:.' does.
112 112 """
113 113 for kind, pat, source in kindpats:
114 114 if pat != b'' or kind not in [b'relpath', b'glob']:
115 115 return False
116 116 return True
117 117
118 118
119 119 def _buildkindpatsmatcher(
120 120 matchercls,
121 121 root,
122 122 cwd,
123 123 kindpats,
124 124 ctx=None,
125 125 listsubrepos=False,
126 126 badfn=None,
127 127 ):
128 128 matchers = []
129 129 fms, kindpats = _expandsets(
130 130 cwd,
131 131 kindpats,
132 132 ctx=ctx,
133 133 listsubrepos=listsubrepos,
134 134 badfn=badfn,
135 135 )
136 136 if kindpats:
137 137 m = matchercls(root, kindpats, badfn=badfn)
138 138 matchers.append(m)
139 139 if fms:
140 140 matchers.extend(fms)
141 141 if not matchers:
142 142 return nevermatcher(badfn=badfn)
143 143 if len(matchers) == 1:
144 144 return matchers[0]
145 145 return unionmatcher(matchers)
146 146
147 147
148 148 def match(
149 149 root,
150 150 cwd,
151 151 patterns=None,
152 152 include=None,
153 153 exclude=None,
154 154 default=b'glob',
155 155 auditor=None,
156 156 ctx=None,
157 157 listsubrepos=False,
158 158 warn=None,
159 159 badfn=None,
160 160 icasefs=False,
161 161 ):
162 162 r"""build an object to match a set of file patterns
163 163
164 164 arguments:
165 165 root - the canonical root of the tree you're matching against
166 166 cwd - the current working directory, if relevant
167 167 patterns - patterns to find
168 168 include - patterns to include (unless they are excluded)
169 169 exclude - patterns to exclude (even if they are included)
170 170 default - if a pattern in patterns has no explicit type, assume this one
171 171 auditor - optional path auditor
172 172 ctx - optional changecontext
173 173 listsubrepos - if True, recurse into subrepositories
174 174 warn - optional function used for printing warnings
175 175 badfn - optional bad() callback for this matcher instead of the default
176 176 icasefs - make a matcher for wdir on case insensitive filesystems, which
177 177 normalizes the given patterns to the case in the filesystem
178 178
179 179 a pattern is one of:
180 180 'glob:<glob>' - a glob relative to cwd
181 181 're:<regexp>' - a regular expression
182 182 'path:<path>' - a path relative to repository root, which is matched
183 183 recursively
184 184 'rootfilesin:<path>' - a path relative to repository root, which is
185 185 matched non-recursively (will not match subdirectories)
186 186 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
187 187 'relpath:<path>' - a path relative to cwd
188 188 'relre:<regexp>' - a regexp that needn't match the start of a name
189 189 'set:<fileset>' - a fileset expression
190 190 'include:<path>' - a file of patterns to read and include
191 191 'subinclude:<path>' - a file of patterns to match against files under
192 192 the same directory
193 193 '<something>' - a pattern of the specified default type
194 194
195 195 >>> def _match(root, *args, **kwargs):
196 196 ... return match(util.localpath(root), *args, **kwargs)
197 197
198 198 Usually a patternmatcher is returned:
199 199 >>> _match(b'/foo', b'.', [br're:.*\.c$', b'path:foo/a', b'*.py'])
200 <patternmatcher patterns='.*\\.c$|foo/a(?:/|$)|[^/]*\\.py$'>
200 <patternmatcher patterns='[^/]*\\.py$|foo/a(?:/|$)|.*\\.c$'>
201 201
202 202 Combining 'patterns' with 'include' (resp. 'exclude') gives an
203 203 intersectionmatcher (resp. a differencematcher):
204 204 >>> type(_match(b'/foo', b'.', [br're:.*\.c$'], include=[b'path:lib']))
205 205 <class 'mercurial.match.intersectionmatcher'>
206 206 >>> type(_match(b'/foo', b'.', [br're:.*\.c$'], exclude=[b'path:build']))
207 207 <class 'mercurial.match.differencematcher'>
208 208
209 209 Notice that, if 'patterns' is empty, an alwaysmatcher is returned:
210 210 >>> _match(b'/foo', b'.', [])
211 211 <alwaysmatcher>
212 212
213 213 The 'default' argument determines which kind of pattern is assumed if a
214 214 pattern has no prefix:
215 215 >>> _match(b'/foo', b'.', [br'.*\.c$'], default=b're')
216 216 <patternmatcher patterns='.*\\.c$'>
217 217 >>> _match(b'/foo', b'.', [b'main.py'], default=b'relpath')
218 218 <patternmatcher patterns='main\\.py(?:/|$)'>
219 219 >>> _match(b'/foo', b'.', [b'main.py'], default=b're')
220 220 <patternmatcher patterns='main.py'>
221 221
222 222 The primary use of matchers is to check whether a value (usually a file
223 223 name) matches againset one of the patterns given at initialization. There
224 224 are two ways of doing this check.
225 225
226 226 >>> m = _match(b'/foo', b'', [br're:.*\.c$', b'relpath:a'])
227 227
228 228 1. Calling the matcher with a file name returns True if any pattern
229 229 matches that file name:
230 230 >>> m(b'a')
231 231 True
232 232 >>> m(b'main.c')
233 233 True
234 234 >>> m(b'test.py')
235 235 False
236 236
237 237 2. Using the exact() method only returns True if the file name matches one
238 238 of the exact patterns (i.e. not re: or glob: patterns):
239 239 >>> m.exact(b'a')
240 240 True
241 241 >>> m.exact(b'main.c')
242 242 False
243 243 """
244 244 assert os.path.isabs(root)
245 245 cwd = os.path.join(root, util.localpath(cwd))
246 246 normalize = _donormalize
247 247 if icasefs:
248 248 dirstate = ctx.repo().dirstate
249 249 dsnormalize = dirstate.normalize
250 250
251 251 def normalize(patterns, default, root, cwd, auditor, warn):
252 252 kp = _donormalize(patterns, default, root, cwd, auditor, warn)
253 253 kindpats = []
254 254 for kind, pats, source in kp:
255 255 if kind not in (b're', b'relre'): # regex can't be normalized
256 256 p = pats
257 257 pats = dsnormalize(pats)
258 258
259 259 # Preserve the original to handle a case only rename.
260 260 if p != pats and p in dirstate:
261 261 kindpats.append((kind, p, source))
262 262
263 263 kindpats.append((kind, pats, source))
264 264 return kindpats
265 265
266 266 if patterns:
267 267 kindpats = normalize(patterns, default, root, cwd, auditor, warn)
268 268 if _kindpatsalwaysmatch(kindpats):
269 269 m = alwaysmatcher(badfn)
270 270 else:
271 271 m = _buildkindpatsmatcher(
272 272 patternmatcher,
273 273 root,
274 274 cwd,
275 275 kindpats,
276 276 ctx=ctx,
277 277 listsubrepos=listsubrepos,
278 278 badfn=badfn,
279 279 )
280 280 else:
281 281 # It's a little strange that no patterns means to match everything.
282 282 # Consider changing this to match nothing (probably using nevermatcher).
283 283 m = alwaysmatcher(badfn)
284 284
285 285 if include:
286 286 kindpats = normalize(include, b'glob', root, cwd, auditor, warn)
287 287 im = _buildkindpatsmatcher(
288 288 includematcher,
289 289 root,
290 290 cwd,
291 291 kindpats,
292 292 ctx=ctx,
293 293 listsubrepos=listsubrepos,
294 294 badfn=None,
295 295 )
296 296 m = intersectmatchers(m, im)
297 297 if exclude:
298 298 kindpats = normalize(exclude, b'glob', root, cwd, auditor, warn)
299 299 em = _buildkindpatsmatcher(
300 300 includematcher,
301 301 root,
302 302 cwd,
303 303 kindpats,
304 304 ctx=ctx,
305 305 listsubrepos=listsubrepos,
306 306 badfn=None,
307 307 )
308 308 m = differencematcher(m, em)
309 309 return m
310 310
311 311
312 312 def exact(files, badfn=None):
313 313 return exactmatcher(files, badfn=badfn)
314 314
315 315
316 316 def always(badfn=None):
317 317 return alwaysmatcher(badfn)
318 318
319 319
320 320 def never(badfn=None):
321 321 return nevermatcher(badfn)
322 322
323 323
324 324 def badmatch(match, badfn):
325 325 """Make a copy of the given matcher, replacing its bad method with the given
326 326 one.
327 327 """
328 328 m = copy.copy(match)
329 329 m.bad = badfn
330 330 return m
331 331
332 332
333 333 def _donormalize(patterns, default, root, cwd, auditor=None, warn=None):
334 334 """Convert 'kind:pat' from the patterns list to tuples with kind and
335 335 normalized and rooted patterns and with listfiles expanded."""
336 336 kindpats = []
337 337 for kind, pat in [_patsplit(p, default) for p in patterns]:
338 338 if kind in cwdrelativepatternkinds:
339 339 pat = pathutil.canonpath(root, cwd, pat, auditor=auditor)
340 340 elif kind in (b'relglob', b'path', b'rootfilesin', b'rootglob'):
341 341 pat = util.normpath(pat)
342 342 elif kind in (b'listfile', b'listfile0'):
343 343 try:
344 344 files = util.readfile(pat)
345 345 if kind == b'listfile0':
346 346 files = files.split(b'\0')
347 347 else:
348 348 files = files.splitlines()
349 349 files = [f for f in files if f]
350 350 except EnvironmentError:
351 351 raise error.Abort(_(b"unable to read file list (%s)") % pat)
352 352 for k, p, source in _donormalize(
353 353 files, default, root, cwd, auditor, warn
354 354 ):
355 355 kindpats.append((k, p, pat))
356 356 continue
357 357 elif kind == b'include':
358 358 try:
359 359 fullpath = os.path.join(root, util.localpath(pat))
360 360 includepats = readpatternfile(fullpath, warn)
361 361 for k, p, source in _donormalize(
362 362 includepats, default, root, cwd, auditor, warn
363 363 ):
364 364 kindpats.append((k, p, source or pat))
365 365 except error.Abort as inst:
366 366 raise error.Abort(
367 367 b'%s: %s'
368 368 % (
369 369 pat,
370 370 inst.message,
371 371 )
372 372 )
373 373 except IOError as inst:
374 374 if warn:
375 375 warn(
376 376 _(b"skipping unreadable pattern file '%s': %s\n")
377 377 % (pat, stringutil.forcebytestr(inst.strerror))
378 378 )
379 379 continue
380 380 # else: re or relre - which cannot be normalized
381 381 kindpats.append((kind, pat, b''))
382 382 return kindpats
383 383
384 384
385 385 class basematcher:
386 386 def __init__(self, badfn=None):
387 387 if badfn is not None:
388 388 self.bad = badfn
389 389
390 390 def __call__(self, fn):
391 391 return self.matchfn(fn)
392 392
393 393 # Callbacks related to how the matcher is used by dirstate.walk.
394 394 # Subscribers to these events must monkeypatch the matcher object.
395 395 def bad(self, f, msg):
396 396 """Callback from dirstate.walk for each explicit file that can't be
397 397 found/accessed, with an error message."""
398 398
399 399 # If an traversedir is set, it will be called when a directory discovered
400 400 # by recursive traversal is visited.
401 401 traversedir = None
402 402
403 403 @propertycache
404 404 def _files(self):
405 405 return []
406 406
407 407 def files(self):
408 408 """Explicitly listed files or patterns or roots:
409 409 if no patterns or .always(): empty list,
410 410 if exact: list exact files,
411 411 if not .anypats(): list all files and dirs,
412 412 else: optimal roots"""
413 413 return self._files
414 414
415 415 @propertycache
416 416 def _fileset(self):
417 417 return set(self._files)
418 418
419 419 def exact(self, f):
420 420 '''Returns True if f is in .files().'''
421 421 return f in self._fileset
422 422
423 423 def matchfn(self, f):
424 424 return False
425 425
426 426 def visitdir(self, dir):
427 427 """Decides whether a directory should be visited based on whether it
428 428 has potential matches in it or one of its subdirectories. This is
429 429 based on the match's primary, included, and excluded patterns.
430 430
431 431 Returns the string 'all' if the given directory and all subdirectories
432 432 should be visited. Otherwise returns True or False indicating whether
433 433 the given directory should be visited.
434 434 """
435 435 return True
436 436
437 437 def visitchildrenset(self, dir):
438 438 """Decides whether a directory should be visited based on whether it
439 439 has potential matches in it or one of its subdirectories, and
440 440 potentially lists which subdirectories of that directory should be
441 441 visited. This is based on the match's primary, included, and excluded
442 442 patterns.
443 443
444 444 This function is very similar to 'visitdir', and the following mapping
445 445 can be applied:
446 446
447 447 visitdir | visitchildrenlist
448 448 ----------+-------------------
449 449 False | set()
450 450 'all' | 'all'
451 451 True | 'this' OR non-empty set of subdirs -or files- to visit
452 452
453 453 Example:
454 454 Assume matchers ['path:foo/bar', 'rootfilesin:qux'], we would return
455 455 the following values (assuming the implementation of visitchildrenset
456 456 is capable of recognizing this; some implementations are not).
457 457
458 458 '' -> {'foo', 'qux'}
459 459 'baz' -> set()
460 460 'foo' -> {'bar'}
461 461 # Ideally this would be 'all', but since the prefix nature of matchers
462 462 # is applied to the entire matcher, we have to downgrade this to
463 463 # 'this' due to the non-prefix 'rootfilesin'-kind matcher being mixed
464 464 # in.
465 465 'foo/bar' -> 'this'
466 466 'qux' -> 'this'
467 467
468 468 Important:
469 469 Most matchers do not know if they're representing files or
470 470 directories. They see ['path:dir/f'] and don't know whether 'f' is a
471 471 file or a directory, so visitchildrenset('dir') for most matchers will
472 472 return {'f'}, but if the matcher knows it's a file (like exactmatcher
473 473 does), it may return 'this'. Do not rely on the return being a set
474 474 indicating that there are no files in this dir to investigate (or
475 475 equivalently that if there are files to investigate in 'dir' that it
476 476 will always return 'this').
477 477 """
478 478 return b'this'
479 479
480 480 def always(self):
481 481 """Matcher will match everything and .files() will be empty --
482 482 optimization might be possible."""
483 483 return False
484 484
485 485 def isexact(self):
486 486 """Matcher will match exactly the list of files in .files() --
487 487 optimization might be possible."""
488 488 return False
489 489
490 490 def prefix(self):
491 491 """Matcher will match the paths in .files() recursively --
492 492 optimization might be possible."""
493 493 return False
494 494
495 495 def anypats(self):
496 496 """None of .always(), .isexact(), and .prefix() is true --
497 497 optimizations will be difficult."""
498 498 return not self.always() and not self.isexact() and not self.prefix()
499 499
500 500
501 501 class alwaysmatcher(basematcher):
502 502 '''Matches everything.'''
503 503
504 504 def __init__(self, badfn=None):
505 505 super(alwaysmatcher, self).__init__(badfn)
506 506
507 507 def always(self):
508 508 return True
509 509
510 510 def matchfn(self, f):
511 511 return True
512 512
513 513 def visitdir(self, dir):
514 514 return b'all'
515 515
516 516 def visitchildrenset(self, dir):
517 517 return b'all'
518 518
519 519 def __repr__(self):
520 520 return r'<alwaysmatcher>'
521 521
522 522
523 523 class nevermatcher(basematcher):
524 524 '''Matches nothing.'''
525 525
526 526 def __init__(self, badfn=None):
527 527 super(nevermatcher, self).__init__(badfn)
528 528
529 529 # It's a little weird to say that the nevermatcher is an exact matcher
530 530 # or a prefix matcher, but it seems to make sense to let callers take
531 531 # fast paths based on either. There will be no exact matches, nor any
532 532 # prefixes (files() returns []), so fast paths iterating over them should
533 533 # be efficient (and correct).
534 534 def isexact(self):
535 535 return True
536 536
537 537 def prefix(self):
538 538 return True
539 539
540 540 def visitdir(self, dir):
541 541 return False
542 542
543 543 def visitchildrenset(self, dir):
544 544 return set()
545 545
546 546 def __repr__(self):
547 547 return r'<nevermatcher>'
548 548
549 549
550 550 class predicatematcher(basematcher):
551 551 """A matcher adapter for a simple boolean function"""
552 552
553 553 def __init__(self, predfn, predrepr=None, badfn=None):
554 554 super(predicatematcher, self).__init__(badfn)
555 555 self.matchfn = predfn
556 556 self._predrepr = predrepr
557 557
558 558 @encoding.strmethod
559 559 def __repr__(self):
560 560 s = stringutil.buildrepr(self._predrepr) or pycompat.byterepr(
561 561 self.matchfn
562 562 )
563 563 return b'<predicatenmatcher pred=%s>' % s
564 564
565 565
566 566 def path_or_parents_in_set(path, prefix_set):
567 567 """Returns True if `path` (or any parent of `path`) is in `prefix_set`."""
568 568 l = len(prefix_set)
569 569 if l == 0:
570 570 return False
571 571 if path in prefix_set:
572 572 return True
573 573 # If there's more than 5 paths in prefix_set, it's *probably* quicker to
574 574 # "walk up" the directory hierarchy instead, with the assumption that most
575 575 # directory hierarchies are relatively shallow and hash lookup is cheap.
576 576 if l > 5:
577 577 return any(
578 578 parentdir in prefix_set for parentdir in pathutil.finddirs(path)
579 579 )
580 580
581 581 # FIXME: Ideally we'd never get to this point if this is the case - we'd
582 582 # recognize ourselves as an 'always' matcher and skip this.
583 583 if b'' in prefix_set:
584 584 return True
585 585
586 586 sl = ord(b'/')
587 587
588 588 # We already checked that path isn't in prefix_set exactly, so
589 589 # `path[len(pf)] should never raise IndexError.
590 590 return any(path.startswith(pf) and path[len(pf)] == sl for pf in prefix_set)
591 591
592 592
593 593 class patternmatcher(basematcher):
594 594 r"""Matches a set of (kind, pat, source) against a 'root' directory.
595 595
596 596 >>> kindpats = [
597 597 ... (b're', br'.*\.c$', b''),
598 598 ... (b'path', b'foo/a', b''),
599 599 ... (b'relpath', b'b', b''),
600 600 ... (b'glob', b'*.h', b''),
601 601 ... ]
602 602 >>> m = patternmatcher(b'foo', kindpats)
603 603 >>> m(b'main.c') # matches re:.*\.c$
604 604 True
605 605 >>> m(b'b.txt')
606 606 False
607 607 >>> m(b'foo/a') # matches path:foo/a
608 608 True
609 609 >>> m(b'a') # does not match path:b, since 'root' is 'foo'
610 610 False
611 611 >>> m(b'b') # matches relpath:b, since 'root' is 'foo'
612 612 True
613 613 >>> m(b'lib.h') # matches glob:*.h
614 614 True
615 615
616 616 >>> m.files()
617 ['', 'foo/a', 'b', '']
617 [b'', b'foo/a', b'', b'b']
618 618 >>> m.exact(b'foo/a')
619 619 True
620 620 >>> m.exact(b'b')
621 621 True
622 622 >>> m.exact(b'lib.h') # exact matches are for (rel)path kinds
623 623 False
624 624 """
625 625
626 626 def __init__(self, root, kindpats, badfn=None):
627 627 super(patternmatcher, self).__init__(badfn)
628 kindpats.sort()
628 629
629 630 self._files = _explicitfiles(kindpats)
630 631 self._prefix = _prefix(kindpats)
631 632 self._pats, self.matchfn = _buildmatch(kindpats, b'$', root)
632 633
633 634 @propertycache
634 635 def _dirs(self):
635 636 return set(pathutil.dirs(self._fileset))
636 637
637 638 def visitdir(self, dir):
638 639 if self._prefix and dir in self._fileset:
639 640 return b'all'
640 641 return dir in self._dirs or path_or_parents_in_set(dir, self._fileset)
641 642
642 643 def visitchildrenset(self, dir):
643 644 ret = self.visitdir(dir)
644 645 if ret is True:
645 646 return b'this'
646 647 elif not ret:
647 648 return set()
648 649 assert ret == b'all'
649 650 return b'all'
650 651
651 652 def prefix(self):
652 653 return self._prefix
653 654
654 655 @encoding.strmethod
655 656 def __repr__(self):
656 657 return b'<patternmatcher patterns=%r>' % pycompat.bytestr(self._pats)
657 658
658 659
659 660 # This is basically a reimplementation of pathutil.dirs that stores the
660 661 # children instead of just a count of them, plus a small optional optimization
661 662 # to avoid some directories we don't need.
662 663 class _dirchildren:
663 664 def __init__(self, paths, onlyinclude=None):
664 665 self._dirs = {}
665 666 self._onlyinclude = onlyinclude or []
666 667 addpath = self.addpath
667 668 for f in paths:
668 669 addpath(f)
669 670
670 671 def addpath(self, path):
671 672 if path == b'':
672 673 return
673 674 dirs = self._dirs
674 675 findsplitdirs = _dirchildren._findsplitdirs
675 676 for d, b in findsplitdirs(path):
676 677 if d not in self._onlyinclude:
677 678 continue
678 679 dirs.setdefault(d, set()).add(b)
679 680
680 681 @staticmethod
681 682 def _findsplitdirs(path):
682 683 # yields (dirname, basename) tuples, walking back to the root. This is
683 684 # very similar to pathutil.finddirs, except:
684 685 # - produces a (dirname, basename) tuple, not just 'dirname'
685 686 # Unlike manifest._splittopdir, this does not suffix `dirname` with a
686 687 # slash.
687 688 oldpos = len(path)
688 689 pos = path.rfind(b'/')
689 690 while pos != -1:
690 691 yield path[:pos], path[pos + 1 : oldpos]
691 692 oldpos = pos
692 693 pos = path.rfind(b'/', 0, pos)
693 694 yield b'', path[:oldpos]
694 695
695 696 def get(self, path):
696 697 return self._dirs.get(path, set())
697 698
698 699
699 700 class includematcher(basematcher):
700 701 def __init__(self, root, kindpats, badfn=None):
701 702 super(includematcher, self).__init__(badfn)
702 703 if rustmod is not None:
703 704 # We need to pass the patterns to Rust because they can contain
704 705 # patterns from the user interface
705 706 self._kindpats = kindpats
706 707 self._pats, self.matchfn = _buildmatch(kindpats, b'(?:/|$)', root)
707 708 self._prefix = _prefix(kindpats)
708 709 roots, dirs, parents = _rootsdirsandparents(kindpats)
709 710 # roots are directories which are recursively included.
710 711 self._roots = set(roots)
711 712 # dirs are directories which are non-recursively included.
712 713 self._dirs = set(dirs)
713 714 # parents are directories which are non-recursively included because
714 715 # they are needed to get to items in _dirs or _roots.
715 716 self._parents = parents
716 717
717 718 def visitdir(self, dir):
718 719 if self._prefix and dir in self._roots:
719 720 return b'all'
720 721 return (
721 722 dir in self._dirs
722 723 or dir in self._parents
723 724 or path_or_parents_in_set(dir, self._roots)
724 725 )
725 726
726 727 @propertycache
727 728 def _allparentschildren(self):
728 729 # It may seem odd that we add dirs, roots, and parents, and then
729 730 # restrict to only parents. This is to catch the case of:
730 731 # dirs = ['foo/bar']
731 732 # parents = ['foo']
732 733 # if we asked for the children of 'foo', but had only added
733 734 # self._parents, we wouldn't be able to respond ['bar'].
734 735 return _dirchildren(
735 736 itertools.chain(self._dirs, self._roots, self._parents),
736 737 onlyinclude=self._parents,
737 738 )
738 739
739 740 def visitchildrenset(self, dir):
740 741 if self._prefix and dir in self._roots:
741 742 return b'all'
742 743 # Note: this does *not* include the 'dir in self._parents' case from
743 744 # visitdir, that's handled below.
744 745 if (
745 746 b'' in self._roots
746 747 or dir in self._dirs
747 748 or path_or_parents_in_set(dir, self._roots)
748 749 ):
749 750 return b'this'
750 751
751 752 if dir in self._parents:
752 753 return self._allparentschildren.get(dir) or set()
753 754 return set()
754 755
755 756 @encoding.strmethod
756 757 def __repr__(self):
757 758 return b'<includematcher includes=%r>' % pycompat.bytestr(self._pats)
758 759
759 760
760 761 class exactmatcher(basematcher):
761 762 r"""Matches the input files exactly. They are interpreted as paths, not
762 763 patterns (so no kind-prefixes).
763 764
764 765 >>> m = exactmatcher([b'a.txt', br're:.*\.c$'])
765 766 >>> m(b'a.txt')
766 767 True
767 768 >>> m(b'b.txt')
768 769 False
769 770
770 771 Input files that would be matched are exactly those returned by .files()
771 772 >>> m.files()
772 773 ['a.txt', 're:.*\\.c$']
773 774
774 775 So pattern 're:.*\.c$' is not considered as a regex, but as a file name
775 776 >>> m(b'main.c')
776 777 False
777 778 >>> m(br're:.*\.c$')
778 779 True
779 780 """
780 781
781 782 def __init__(self, files, badfn=None):
782 783 super(exactmatcher, self).__init__(badfn)
783 784
784 785 if isinstance(files, list):
785 786 self._files = files
786 787 else:
787 788 self._files = list(files)
788 789
789 790 matchfn = basematcher.exact
790 791
791 792 @propertycache
792 793 def _dirs(self):
793 794 return set(pathutil.dirs(self._fileset))
794 795
795 796 def visitdir(self, dir):
796 797 return dir in self._dirs
797 798
798 799 @propertycache
799 800 def _visitchildrenset_candidates(self):
800 801 """A memoized set of candidates for visitchildrenset."""
801 802 return self._fileset | self._dirs - {b''}
802 803
803 804 @propertycache
804 805 def _sorted_visitchildrenset_candidates(self):
805 806 """A memoized sorted list of candidates for visitchildrenset."""
806 807 return sorted(self._visitchildrenset_candidates)
807 808
808 809 def visitchildrenset(self, dir):
809 810 if not self._fileset or dir not in self._dirs:
810 811 return set()
811 812
812 813 if dir == b'':
813 814 candidates = self._visitchildrenset_candidates
814 815 else:
815 816 candidates = self._sorted_visitchildrenset_candidates
816 817 d = dir + b'/'
817 818 # Use bisect to find the first element potentially starting with d
818 819 # (i.e. >= d). This should always find at least one element (we'll
819 820 # assert later if this is not the case).
820 821 first = bisect.bisect_left(candidates, d)
821 822 # We need a representation of the first element that is > d that
822 823 # does not start with d, so since we added a `/` on the end of dir,
823 824 # we'll add whatever comes after slash (we could probably assume
824 825 # that `0` is after `/`, but let's not) to the end of dir instead.
825 826 dnext = dir + encoding.strtolocal(chr(ord(b'/') + 1))
826 827 # Use bisect to find the first element >= d_next
827 828 last = bisect.bisect_left(candidates, dnext, lo=first)
828 829 dlen = len(d)
829 830 candidates = {c[dlen:] for c in candidates[first:last]}
830 831 # self._dirs includes all of the directories, recursively, so if
831 832 # we're attempting to match foo/bar/baz.txt, it'll have '', 'foo',
832 833 # 'foo/bar' in it. Thus we can safely ignore a candidate that has a
833 834 # '/' in it, indicating a it's for a subdir-of-a-subdir; the
834 835 # immediate subdir will be in there without a slash.
835 836 ret = {c for c in candidates if b'/' not in c}
836 837 # We really do not expect ret to be empty, since that would imply that
837 838 # there's something in _dirs that didn't have a file in _fileset.
838 839 assert ret
839 840 return ret
840 841
841 842 def isexact(self):
842 843 return True
843 844
844 845 @encoding.strmethod
845 846 def __repr__(self):
846 847 return b'<exactmatcher files=%r>' % self._files
847 848
848 849
849 850 class differencematcher(basematcher):
850 851 """Composes two matchers by matching if the first matches and the second
851 852 does not.
852 853
853 854 The second matcher's non-matching-attributes (bad, traversedir) are ignored.
854 855 """
855 856
856 857 def __init__(self, m1, m2):
857 858 super(differencematcher, self).__init__()
858 859 self._m1 = m1
859 860 self._m2 = m2
860 861 self.bad = m1.bad
861 862 self.traversedir = m1.traversedir
862 863
863 864 def matchfn(self, f):
864 865 return self._m1(f) and not self._m2(f)
865 866
866 867 @propertycache
867 868 def _files(self):
868 869 if self.isexact():
869 870 return [f for f in self._m1.files() if self(f)]
870 871 # If m1 is not an exact matcher, we can't easily figure out the set of
871 872 # files, because its files() are not always files. For example, if
872 873 # m1 is "path:dir" and m2 is "rootfileins:.", we don't
873 874 # want to remove "dir" from the set even though it would match m2,
874 875 # because the "dir" in m1 may not be a file.
875 876 return self._m1.files()
876 877
877 878 def visitdir(self, dir):
878 879 if self._m2.visitdir(dir) == b'all':
879 880 return False
880 881 elif not self._m2.visitdir(dir):
881 882 # m2 does not match dir, we can return 'all' here if possible
882 883 return self._m1.visitdir(dir)
883 884 return bool(self._m1.visitdir(dir))
884 885
885 886 def visitchildrenset(self, dir):
886 887 m2_set = self._m2.visitchildrenset(dir)
887 888 if m2_set == b'all':
888 889 return set()
889 890 m1_set = self._m1.visitchildrenset(dir)
890 891 # Possible values for m1: 'all', 'this', set(...), set()
891 892 # Possible values for m2: 'this', set(...), set()
892 893 # If m2 has nothing under here that we care about, return m1, even if
893 894 # it's 'all'. This is a change in behavior from visitdir, which would
894 895 # return True, not 'all', for some reason.
895 896 if not m2_set:
896 897 return m1_set
897 898 if m1_set in [b'all', b'this']:
898 899 # Never return 'all' here if m2_set is any kind of non-empty (either
899 900 # 'this' or set(foo)), since m2 might return set() for a
900 901 # subdirectory.
901 902 return b'this'
902 903 # Possible values for m1: set(...), set()
903 904 # Possible values for m2: 'this', set(...)
904 905 # We ignore m2's set results. They're possibly incorrect:
905 906 # m1 = path:dir/subdir, m2=rootfilesin:dir, visitchildrenset(''):
906 907 # m1 returns {'dir'}, m2 returns {'dir'}, if we subtracted we'd
907 908 # return set(), which is *not* correct, we still need to visit 'dir'!
908 909 return m1_set
909 910
910 911 def isexact(self):
911 912 return self._m1.isexact()
912 913
913 914 @encoding.strmethod
914 915 def __repr__(self):
915 916 return b'<differencematcher m1=%r, m2=%r>' % (self._m1, self._m2)
916 917
917 918
918 919 def intersectmatchers(m1, m2):
919 920 """Composes two matchers by matching if both of them match.
920 921
921 922 The second matcher's non-matching-attributes (bad, traversedir) are ignored.
922 923 """
923 924 if m1 is None or m2 is None:
924 925 return m1 or m2
925 926 if m1.always():
926 927 m = copy.copy(m2)
927 928 # TODO: Consider encapsulating these things in a class so there's only
928 929 # one thing to copy from m1.
929 930 m.bad = m1.bad
930 931 m.traversedir = m1.traversedir
931 932 return m
932 933 if m2.always():
933 934 m = copy.copy(m1)
934 935 return m
935 936 return intersectionmatcher(m1, m2)
936 937
937 938
938 939 class intersectionmatcher(basematcher):
939 940 def __init__(self, m1, m2):
940 941 super(intersectionmatcher, self).__init__()
941 942 self._m1 = m1
942 943 self._m2 = m2
943 944 self.bad = m1.bad
944 945 self.traversedir = m1.traversedir
945 946
946 947 @propertycache
947 948 def _files(self):
948 949 if self.isexact():
949 950 m1, m2 = self._m1, self._m2
950 951 if not m1.isexact():
951 952 m1, m2 = m2, m1
952 953 return [f for f in m1.files() if m2(f)]
953 954 # It neither m1 nor m2 is an exact matcher, we can't easily intersect
954 955 # the set of files, because their files() are not always files. For
955 956 # example, if intersecting a matcher "-I glob:foo.txt" with matcher of
956 957 # "path:dir2", we don't want to remove "dir2" from the set.
957 958 return self._m1.files() + self._m2.files()
958 959
959 960 def matchfn(self, f):
960 961 return self._m1(f) and self._m2(f)
961 962
962 963 def visitdir(self, dir):
963 964 visit1 = self._m1.visitdir(dir)
964 965 if visit1 == b'all':
965 966 return self._m2.visitdir(dir)
966 967 # bool() because visit1=True + visit2='all' should not be 'all'
967 968 return bool(visit1 and self._m2.visitdir(dir))
968 969
969 970 def visitchildrenset(self, dir):
970 971 m1_set = self._m1.visitchildrenset(dir)
971 972 if not m1_set:
972 973 return set()
973 974 m2_set = self._m2.visitchildrenset(dir)
974 975 if not m2_set:
975 976 return set()
976 977
977 978 if m1_set == b'all':
978 979 return m2_set
979 980 elif m2_set == b'all':
980 981 return m1_set
981 982
982 983 if m1_set == b'this' or m2_set == b'this':
983 984 return b'this'
984 985
985 986 assert isinstance(m1_set, set) and isinstance(m2_set, set)
986 987 return m1_set.intersection(m2_set)
987 988
988 989 def always(self):
989 990 return self._m1.always() and self._m2.always()
990 991
991 992 def isexact(self):
992 993 return self._m1.isexact() or self._m2.isexact()
993 994
994 995 @encoding.strmethod
995 996 def __repr__(self):
996 997 return b'<intersectionmatcher m1=%r, m2=%r>' % (self._m1, self._m2)
997 998
998 999
999 1000 class subdirmatcher(basematcher):
1000 1001 """Adapt a matcher to work on a subdirectory only.
1001 1002
1002 1003 The paths are remapped to remove/insert the path as needed:
1003 1004
1004 1005 >>> from . import pycompat
1005 1006 >>> m1 = match(util.localpath(b'/root'), b'', [b'a.txt', b'sub/b.txt'], auditor=lambda name: None)
1006 1007 >>> m2 = subdirmatcher(b'sub', m1)
1007 1008 >>> m2(b'a.txt')
1008 1009 False
1009 1010 >>> m2(b'b.txt')
1010 1011 True
1011 1012 >>> m2.matchfn(b'a.txt')
1012 1013 False
1013 1014 >>> m2.matchfn(b'b.txt')
1014 1015 True
1015 1016 >>> m2.files()
1016 1017 ['b.txt']
1017 1018 >>> m2.exact(b'b.txt')
1018 1019 True
1019 1020 >>> def bad(f, msg):
1020 1021 ... print(pycompat.sysstr(b"%s: %s" % (f, msg)))
1021 1022 >>> m1.bad = bad
1022 1023 >>> m2.bad(b'x.txt', b'No such file')
1023 1024 sub/x.txt: No such file
1024 1025 """
1025 1026
1026 1027 def __init__(self, path, matcher):
1027 1028 super(subdirmatcher, self).__init__()
1028 1029 self._path = path
1029 1030 self._matcher = matcher
1030 1031 self._always = matcher.always()
1031 1032
1032 1033 self._files = [
1033 1034 f[len(path) + 1 :]
1034 1035 for f in matcher._files
1035 1036 if f.startswith(path + b"/")
1036 1037 ]
1037 1038
1038 1039 # If the parent repo had a path to this subrepo and the matcher is
1039 1040 # a prefix matcher, this submatcher always matches.
1040 1041 if matcher.prefix():
1041 1042 self._always = any(f == path for f in matcher._files)
1042 1043
1043 1044 def bad(self, f, msg):
1044 1045 self._matcher.bad(self._path + b"/" + f, msg)
1045 1046
1046 1047 def matchfn(self, f):
1047 1048 # Some information is lost in the superclass's constructor, so we
1048 1049 # can not accurately create the matching function for the subdirectory
1049 1050 # from the inputs. Instead, we override matchfn() and visitdir() to
1050 1051 # call the original matcher with the subdirectory path prepended.
1051 1052 return self._matcher.matchfn(self._path + b"/" + f)
1052 1053
1053 1054 def visitdir(self, dir):
1054 1055 if dir == b'':
1055 1056 dir = self._path
1056 1057 else:
1057 1058 dir = self._path + b"/" + dir
1058 1059 return self._matcher.visitdir(dir)
1059 1060
1060 1061 def visitchildrenset(self, dir):
1061 1062 if dir == b'':
1062 1063 dir = self._path
1063 1064 else:
1064 1065 dir = self._path + b"/" + dir
1065 1066 return self._matcher.visitchildrenset(dir)
1066 1067
1067 1068 def always(self):
1068 1069 return self._always
1069 1070
1070 1071 def prefix(self):
1071 1072 return self._matcher.prefix() and not self._always
1072 1073
1073 1074 @encoding.strmethod
1074 1075 def __repr__(self):
1075 1076 return b'<subdirmatcher path=%r, matcher=%r>' % (
1076 1077 self._path,
1077 1078 self._matcher,
1078 1079 )
1079 1080
1080 1081
1081 1082 class prefixdirmatcher(basematcher):
1082 1083 """Adapt a matcher to work on a parent directory.
1083 1084
1084 1085 The matcher's non-matching-attributes (bad, traversedir) are ignored.
1085 1086
1086 1087 The prefix path should usually be the relative path from the root of
1087 1088 this matcher to the root of the wrapped matcher.
1088 1089
1089 1090 >>> m1 = match(util.localpath(b'/root/d/e'), b'f', [b'../a.txt', b'b.txt'], auditor=lambda name: None)
1090 1091 >>> m2 = prefixdirmatcher(b'd/e', m1)
1091 1092 >>> m2(b'a.txt')
1092 1093 False
1093 1094 >>> m2(b'd/e/a.txt')
1094 1095 True
1095 1096 >>> m2(b'd/e/b.txt')
1096 1097 False
1097 1098 >>> m2.files()
1098 1099 ['d/e/a.txt', 'd/e/f/b.txt']
1099 1100 >>> m2.exact(b'd/e/a.txt')
1100 1101 True
1101 1102 >>> m2.visitdir(b'd')
1102 1103 True
1103 1104 >>> m2.visitdir(b'd/e')
1104 1105 True
1105 1106 >>> m2.visitdir(b'd/e/f')
1106 1107 True
1107 1108 >>> m2.visitdir(b'd/e/g')
1108 1109 False
1109 1110 >>> m2.visitdir(b'd/ef')
1110 1111 False
1111 1112 """
1112 1113
1113 1114 def __init__(self, path, matcher, badfn=None):
1114 1115 super(prefixdirmatcher, self).__init__(badfn)
1115 1116 if not path:
1116 1117 raise error.ProgrammingError(b'prefix path must not be empty')
1117 1118 self._path = path
1118 1119 self._pathprefix = path + b'/'
1119 1120 self._matcher = matcher
1120 1121
1121 1122 @propertycache
1122 1123 def _files(self):
1123 1124 return [self._pathprefix + f for f in self._matcher._files]
1124 1125
1125 1126 def matchfn(self, f):
1126 1127 if not f.startswith(self._pathprefix):
1127 1128 return False
1128 1129 return self._matcher.matchfn(f[len(self._pathprefix) :])
1129 1130
1130 1131 @propertycache
1131 1132 def _pathdirs(self):
1132 1133 return set(pathutil.finddirs(self._path))
1133 1134
1134 1135 def visitdir(self, dir):
1135 1136 if dir == self._path:
1136 1137 return self._matcher.visitdir(b'')
1137 1138 if dir.startswith(self._pathprefix):
1138 1139 return self._matcher.visitdir(dir[len(self._pathprefix) :])
1139 1140 return dir in self._pathdirs
1140 1141
1141 1142 def visitchildrenset(self, dir):
1142 1143 if dir == self._path:
1143 1144 return self._matcher.visitchildrenset(b'')
1144 1145 if dir.startswith(self._pathprefix):
1145 1146 return self._matcher.visitchildrenset(dir[len(self._pathprefix) :])
1146 1147 if dir in self._pathdirs:
1147 1148 return b'this'
1148 1149 return set()
1149 1150
1150 1151 def isexact(self):
1151 1152 return self._matcher.isexact()
1152 1153
1153 1154 def prefix(self):
1154 1155 return self._matcher.prefix()
1155 1156
1156 1157 @encoding.strmethod
1157 1158 def __repr__(self):
1158 1159 return b'<prefixdirmatcher path=%r, matcher=%r>' % (
1159 1160 pycompat.bytestr(self._path),
1160 1161 self._matcher,
1161 1162 )
1162 1163
1163 1164
1164 1165 class unionmatcher(basematcher):
1165 1166 """A matcher that is the union of several matchers.
1166 1167
1167 1168 The non-matching-attributes (bad, traversedir) are taken from the first
1168 1169 matcher.
1169 1170 """
1170 1171
1171 1172 def __init__(self, matchers):
1172 1173 m1 = matchers[0]
1173 1174 super(unionmatcher, self).__init__()
1174 1175 self.traversedir = m1.traversedir
1175 1176 self._matchers = matchers
1176 1177
1177 1178 def matchfn(self, f):
1178 1179 for match in self._matchers:
1179 1180 if match(f):
1180 1181 return True
1181 1182 return False
1182 1183
1183 1184 def visitdir(self, dir):
1184 1185 r = False
1185 1186 for m in self._matchers:
1186 1187 v = m.visitdir(dir)
1187 1188 if v == b'all':
1188 1189 return v
1189 1190 r |= v
1190 1191 return r
1191 1192
1192 1193 def visitchildrenset(self, dir):
1193 1194 r = set()
1194 1195 this = False
1195 1196 for m in self._matchers:
1196 1197 v = m.visitchildrenset(dir)
1197 1198 if not v:
1198 1199 continue
1199 1200 if v == b'all':
1200 1201 return v
1201 1202 if this or v == b'this':
1202 1203 this = True
1203 1204 # don't break, we might have an 'all' in here.
1204 1205 continue
1205 1206 assert isinstance(v, set)
1206 1207 r = r.union(v)
1207 1208 if this:
1208 1209 return b'this'
1209 1210 return r
1210 1211
1211 1212 @encoding.strmethod
1212 1213 def __repr__(self):
1213 1214 return b'<unionmatcher matchers=%r>' % self._matchers
1214 1215
1215 1216
1216 1217 def patkind(pattern, default=None):
1217 1218 r"""If pattern is 'kind:pat' with a known kind, return kind.
1218 1219
1219 1220 >>> patkind(br're:.*\.c$')
1220 1221 're'
1221 1222 >>> patkind(b'glob:*.c')
1222 1223 'glob'
1223 1224 >>> patkind(b'relpath:test.py')
1224 1225 'relpath'
1225 1226 >>> patkind(b'main.py')
1226 1227 >>> patkind(b'main.py', default=b're')
1227 1228 're'
1228 1229 """
1229 1230 return _patsplit(pattern, default)[0]
1230 1231
1231 1232
1232 1233 def _patsplit(pattern, default):
1233 1234 """Split a string into the optional pattern kind prefix and the actual
1234 1235 pattern."""
1235 1236 if b':' in pattern:
1236 1237 kind, pat = pattern.split(b':', 1)
1237 1238 if kind in allpatternkinds:
1238 1239 return kind, pat
1239 1240 return default, pattern
1240 1241
1241 1242
1242 1243 def _globre(pat):
1243 1244 r"""Convert an extended glob string to a regexp string.
1244 1245
1245 1246 >>> from . import pycompat
1246 1247 >>> def bprint(s):
1247 1248 ... print(pycompat.sysstr(s))
1248 1249 >>> bprint(_globre(br'?'))
1249 1250 .
1250 1251 >>> bprint(_globre(br'*'))
1251 1252 [^/]*
1252 1253 >>> bprint(_globre(br'**'))
1253 1254 .*
1254 1255 >>> bprint(_globre(br'**/a'))
1255 1256 (?:.*/)?a
1256 1257 >>> bprint(_globre(br'a/**/b'))
1257 1258 a/(?:.*/)?b
1258 1259 >>> bprint(_globre(br'[a*?!^][^b][!c]'))
1259 1260 [a*?!^][\^b][^c]
1260 1261 >>> bprint(_globre(br'{a,b}'))
1261 1262 (?:a|b)
1262 1263 >>> bprint(_globre(br'.\*\?'))
1263 1264 \.\*\?
1264 1265 """
1265 1266 i, n = 0, len(pat)
1266 1267 res = b''
1267 1268 group = 0
1268 1269 escape = util.stringutil.regexbytesescapemap.get
1269 1270
1270 1271 def peek():
1271 1272 return i < n and pat[i : i + 1]
1272 1273
1273 1274 while i < n:
1274 1275 c = pat[i : i + 1]
1275 1276 i += 1
1276 1277 if c not in b'*?[{},\\':
1277 1278 res += escape(c, c)
1278 1279 elif c == b'*':
1279 1280 if peek() == b'*':
1280 1281 i += 1
1281 1282 if peek() == b'/':
1282 1283 i += 1
1283 1284 res += b'(?:.*/)?'
1284 1285 else:
1285 1286 res += b'.*'
1286 1287 else:
1287 1288 res += b'[^/]*'
1288 1289 elif c == b'?':
1289 1290 res += b'.'
1290 1291 elif c == b'[':
1291 1292 j = i
1292 1293 if j < n and pat[j : j + 1] in b'!]':
1293 1294 j += 1
1294 1295 while j < n and pat[j : j + 1] != b']':
1295 1296 j += 1
1296 1297 if j >= n:
1297 1298 res += b'\\['
1298 1299 else:
1299 1300 stuff = pat[i:j].replace(b'\\', b'\\\\')
1300 1301 i = j + 1
1301 1302 if stuff[0:1] == b'!':
1302 1303 stuff = b'^' + stuff[1:]
1303 1304 elif stuff[0:1] == b'^':
1304 1305 stuff = b'\\' + stuff
1305 1306 res = b'%s[%s]' % (res, stuff)
1306 1307 elif c == b'{':
1307 1308 group += 1
1308 1309 res += b'(?:'
1309 1310 elif c == b'}' and group:
1310 1311 res += b')'
1311 1312 group -= 1
1312 1313 elif c == b',' and group:
1313 1314 res += b'|'
1314 1315 elif c == b'\\':
1315 1316 p = peek()
1316 1317 if p:
1317 1318 i += 1
1318 1319 res += escape(p, p)
1319 1320 else:
1320 1321 res += escape(c, c)
1321 1322 else:
1322 1323 res += escape(c, c)
1323 1324 return res
1324 1325
1325 1326
1326 1327 FLAG_RE = util.re.compile(br'^\(\?([aiLmsux]+)\)(.*)')
1327 1328
1328 1329
1329 1330 def _regex(kind, pat, globsuffix):
1330 1331 """Convert a (normalized) pattern of any kind into a
1331 1332 regular expression.
1332 1333 globsuffix is appended to the regexp of globs."""
1333 1334 if not pat and kind in (b'glob', b'relpath'):
1334 1335 return b''
1335 1336 if kind == b're':
1336 1337 return pat
1337 1338 if kind in (b'path', b'relpath'):
1338 1339 if pat == b'.':
1339 1340 return b''
1340 1341 return util.stringutil.reescape(pat) + b'(?:/|$)'
1341 1342 if kind == b'rootfilesin':
1342 1343 if pat == b'.':
1343 1344 escaped = b''
1344 1345 else:
1345 1346 # Pattern is a directory name.
1346 1347 escaped = util.stringutil.reescape(pat) + b'/'
1347 1348 # Anything after the pattern must be a non-directory.
1348 1349 return escaped + b'[^/]+$'
1349 1350 if kind == b'relglob':
1350 1351 globre = _globre(pat)
1351 1352 if globre.startswith(b'[^/]*'):
1352 1353 # When pat has the form *XYZ (common), make the returned regex more
1353 1354 # legible by returning the regex for **XYZ instead of **/*XYZ.
1354 1355 return b'.*' + globre[len(b'[^/]*') :] + globsuffix
1355 1356 return b'(?:|.*/)' + globre + globsuffix
1356 1357 if kind == b'relre':
1357 1358 flag = None
1358 1359 m = FLAG_RE.match(pat)
1359 1360 if m:
1360 1361 flag, pat = m.groups()
1361 1362 if not pat.startswith(b'^'):
1362 1363 pat = b'.*' + pat
1363 1364 if flag is not None:
1364 1365 pat = br'(?%s:%s)' % (flag, pat)
1365 1366 return pat
1366 1367 if kind in (b'glob', b'rootglob'):
1367 1368 return _globre(pat) + globsuffix
1368 1369 raise error.ProgrammingError(b'not a regex pattern: %s:%s' % (kind, pat))
1369 1370
1370 1371
1371 1372 def _buildmatch(kindpats, globsuffix, root):
1372 1373 """Return regexp string and a matcher function for kindpats.
1373 1374 globsuffix is appended to the regexp of globs."""
1374 1375 matchfuncs = []
1375 1376
1376 1377 subincludes, kindpats = _expandsubinclude(kindpats, root)
1377 1378 if subincludes:
1378 1379 submatchers = {}
1379 1380
1380 1381 def matchsubinclude(f):
1381 1382 for prefix, matcherargs in subincludes:
1382 1383 if f.startswith(prefix):
1383 1384 mf = submatchers.get(prefix)
1384 1385 if mf is None:
1385 1386 mf = match(*matcherargs)
1386 1387 submatchers[prefix] = mf
1387 1388
1388 1389 if mf(f[len(prefix) :]):
1389 1390 return True
1390 1391 return False
1391 1392
1392 1393 matchfuncs.append(matchsubinclude)
1393 1394
1394 1395 regex = b''
1395 1396 if kindpats:
1396 1397 if all(k == b'rootfilesin' for k, p, s in kindpats):
1397 1398 dirs = {p for k, p, s in kindpats}
1398 1399
1399 1400 def mf(f):
1400 1401 i = f.rfind(b'/')
1401 1402 if i >= 0:
1402 1403 dir = f[:i]
1403 1404 else:
1404 1405 dir = b'.'
1405 1406 return dir in dirs
1406 1407
1407 1408 regex = b'rootfilesin: %s' % stringutil.pprint(list(sorted(dirs)))
1408 1409 matchfuncs.append(mf)
1409 1410 else:
1410 1411 regex, mf = _buildregexmatch(kindpats, globsuffix)
1411 1412 matchfuncs.append(mf)
1412 1413
1413 1414 if len(matchfuncs) == 1:
1414 1415 return regex, matchfuncs[0]
1415 1416 else:
1416 1417 return regex, lambda f: any(mf(f) for mf in matchfuncs)
1417 1418
1418 1419
1419 1420 MAX_RE_SIZE = 20000
1420 1421
1421 1422
1422 1423 def _joinregexes(regexps):
1423 1424 """gather multiple regular expressions into a single one"""
1424 1425 return b'|'.join(regexps)
1425 1426
1426 1427
1427 1428 def _buildregexmatch(kindpats, globsuffix):
1428 1429 """Build a match function from a list of kinds and kindpats,
1429 1430 return regexp string and a matcher function.
1430 1431
1431 1432 Test too large input
1432 1433 >>> _buildregexmatch([
1433 1434 ... (b'relglob', b'?' * MAX_RE_SIZE, b'')
1434 1435 ... ], b'$')
1435 1436 Traceback (most recent call last):
1436 1437 ...
1437 1438 Abort: matcher pattern is too long (20009 bytes)
1438 1439 """
1439 1440 try:
1440 1441 allgroups = []
1441 1442 regexps = [_regex(k, p, globsuffix) for (k, p, s) in kindpats]
1442 1443 fullregexp = _joinregexes(regexps)
1443 1444
1444 1445 startidx = 0
1445 1446 groupsize = 0
1446 1447 for idx, r in enumerate(regexps):
1447 1448 piecesize = len(r)
1448 1449 if piecesize > MAX_RE_SIZE:
1449 1450 msg = _(b"matcher pattern is too long (%d bytes)") % piecesize
1450 1451 raise error.Abort(msg)
1451 1452 elif (groupsize + piecesize) > MAX_RE_SIZE:
1452 1453 group = regexps[startidx:idx]
1453 1454 allgroups.append(_joinregexes(group))
1454 1455 startidx = idx
1455 1456 groupsize = 0
1456 1457 groupsize += piecesize + 1
1457 1458
1458 1459 if startidx == 0:
1459 1460 matcher = _rematcher(fullregexp)
1460 1461 func = lambda s: bool(matcher(s))
1461 1462 else:
1462 1463 group = regexps[startidx:]
1463 1464 allgroups.append(_joinregexes(group))
1464 1465 allmatchers = [_rematcher(g) for g in allgroups]
1465 1466 func = lambda s: any(m(s) for m in allmatchers)
1466 1467 return fullregexp, func
1467 1468 except re.error:
1468 1469 for k, p, s in kindpats:
1469 1470 try:
1470 1471 _rematcher(_regex(k, p, globsuffix))
1471 1472 except re.error:
1472 1473 if s:
1473 1474 raise error.Abort(
1474 1475 _(b"%s: invalid pattern (%s): %s") % (s, k, p)
1475 1476 )
1476 1477 else:
1477 1478 raise error.Abort(_(b"invalid pattern (%s): %s") % (k, p))
1478 1479 raise error.Abort(_(b"invalid pattern"))
1479 1480
1480 1481
1481 1482 def _patternrootsanddirs(kindpats):
1482 1483 """Returns roots and directories corresponding to each pattern.
1483 1484
1484 1485 This calculates the roots and directories exactly matching the patterns and
1485 1486 returns a tuple of (roots, dirs) for each. It does not return other
1486 1487 directories which may also need to be considered, like the parent
1487 1488 directories.
1488 1489 """
1489 1490 r = []
1490 1491 d = []
1491 1492 for kind, pat, source in kindpats:
1492 1493 if kind in (b'glob', b'rootglob'): # find the non-glob prefix
1493 1494 root = []
1494 1495 for p in pat.split(b'/'):
1495 1496 if b'[' in p or b'{' in p or b'*' in p or b'?' in p:
1496 1497 break
1497 1498 root.append(p)
1498 1499 r.append(b'/'.join(root))
1499 1500 elif kind in (b'relpath', b'path'):
1500 1501 if pat == b'.':
1501 1502 pat = b''
1502 1503 r.append(pat)
1503 1504 elif kind in (b'rootfilesin',):
1504 1505 if pat == b'.':
1505 1506 pat = b''
1506 1507 d.append(pat)
1507 1508 else: # relglob, re, relre
1508 1509 r.append(b'')
1509 1510 return r, d
1510 1511
1511 1512
1512 1513 def _roots(kindpats):
1513 1514 '''Returns root directories to match recursively from the given patterns.'''
1514 1515 roots, dirs = _patternrootsanddirs(kindpats)
1515 1516 return roots
1516 1517
1517 1518
1518 1519 def _rootsdirsandparents(kindpats):
1519 1520 """Returns roots and exact directories from patterns.
1520 1521
1521 1522 `roots` are directories to match recursively, `dirs` should
1522 1523 be matched non-recursively, and `parents` are the implicitly required
1523 1524 directories to walk to items in either roots or dirs.
1524 1525
1525 1526 Returns a tuple of (roots, dirs, parents).
1526 1527
1527 1528 >>> r = _rootsdirsandparents(
1528 1529 ... [(b'glob', b'g/h/*', b''), (b'glob', b'g/h', b''),
1529 1530 ... (b'glob', b'g*', b'')])
1530 1531 >>> print(r[0:2], sorted(r[2])) # the set has an unstable output
1531 1532 (['g/h', 'g/h', ''], []) ['', 'g']
1532 1533 >>> r = _rootsdirsandparents(
1533 1534 ... [(b'rootfilesin', b'g/h', b''), (b'rootfilesin', b'', b'')])
1534 1535 >>> print(r[0:2], sorted(r[2])) # the set has an unstable output
1535 1536 ([], ['g/h', '']) ['', 'g']
1536 1537 >>> r = _rootsdirsandparents(
1537 1538 ... [(b'relpath', b'r', b''), (b'path', b'p/p', b''),
1538 1539 ... (b'path', b'', b'')])
1539 1540 >>> print(r[0:2], sorted(r[2])) # the set has an unstable output
1540 1541 (['r', 'p/p', ''], []) ['', 'p']
1541 1542 >>> r = _rootsdirsandparents(
1542 1543 ... [(b'relglob', b'rg*', b''), (b're', b're/', b''),
1543 1544 ... (b'relre', b'rr', b'')])
1544 1545 >>> print(r[0:2], sorted(r[2])) # the set has an unstable output
1545 1546 (['', '', ''], []) ['']
1546 1547 """
1547 1548 r, d = _patternrootsanddirs(kindpats)
1548 1549
1549 1550 p = set()
1550 1551 # Add the parents as non-recursive/exact directories, since they must be
1551 1552 # scanned to get to either the roots or the other exact directories.
1552 1553 p.update(pathutil.dirs(d))
1553 1554 p.update(pathutil.dirs(r))
1554 1555
1555 1556 # FIXME: all uses of this function convert these to sets, do so before
1556 1557 # returning.
1557 1558 # FIXME: all uses of this function do not need anything in 'roots' and
1558 1559 # 'dirs' to also be in 'parents', consider removing them before returning.
1559 1560 return r, d, p
1560 1561
1561 1562
1562 1563 def _explicitfiles(kindpats):
1563 1564 """Returns the potential explicit filenames from the patterns.
1564 1565
1565 1566 >>> _explicitfiles([(b'path', b'foo/bar', b'')])
1566 1567 ['foo/bar']
1567 1568 >>> _explicitfiles([(b'rootfilesin', b'foo/bar', b'')])
1568 1569 []
1569 1570 """
1570 1571 # Keep only the pattern kinds where one can specify filenames (vs only
1571 1572 # directory names).
1572 1573 filable = [kp for kp in kindpats if kp[0] not in (b'rootfilesin',)]
1573 1574 return _roots(filable)
1574 1575
1575 1576
1576 1577 def _prefix(kindpats):
1577 1578 '''Whether all the patterns match a prefix (i.e. recursively)'''
1578 1579 for kind, pat, source in kindpats:
1579 1580 if kind not in (b'path', b'relpath'):
1580 1581 return False
1581 1582 return True
1582 1583
1583 1584
1584 1585 _commentre = None
1585 1586
1586 1587
1587 1588 def readpatternfile(filepath, warn, sourceinfo=False):
1588 1589 """parse a pattern file, returning a list of
1589 1590 patterns. These patterns should be given to compile()
1590 1591 to be validated and converted into a match function.
1591 1592
1592 1593 trailing white space is dropped.
1593 1594 the escape character is backslash.
1594 1595 comments start with #.
1595 1596 empty lines are skipped.
1596 1597
1597 1598 lines can be of the following formats:
1598 1599
1599 1600 syntax: regexp # defaults following lines to non-rooted regexps
1600 1601 syntax: glob # defaults following lines to non-rooted globs
1601 1602 re:pattern # non-rooted regular expression
1602 1603 glob:pattern # non-rooted glob
1603 1604 rootglob:pat # rooted glob (same root as ^ in regexps)
1604 1605 pattern # pattern of the current default type
1605 1606
1606 1607 if sourceinfo is set, returns a list of tuples:
1607 1608 (pattern, lineno, originalline).
1608 1609 This is useful to debug ignore patterns.
1609 1610 """
1610 1611
1611 1612 syntaxes = {
1612 1613 b're': b'relre:',
1613 1614 b'regexp': b'relre:',
1614 1615 b'glob': b'relglob:',
1615 1616 b'rootglob': b'rootglob:',
1616 1617 b'include': b'include',
1617 1618 b'subinclude': b'subinclude',
1618 1619 }
1619 1620 syntax = b'relre:'
1620 1621 patterns = []
1621 1622
1622 1623 fp = open(filepath, b'rb')
1623 1624 for lineno, line in enumerate(fp, start=1):
1624 1625 if b"#" in line:
1625 1626 global _commentre
1626 1627 if not _commentre:
1627 1628 _commentre = util.re.compile(br'((?:^|[^\\])(?:\\\\)*)#.*')
1628 1629 # remove comments prefixed by an even number of escapes
1629 1630 m = _commentre.search(line)
1630 1631 if m:
1631 1632 line = line[: m.end(1)]
1632 1633 # fixup properly escaped comments that survived the above
1633 1634 line = line.replace(b"\\#", b"#")
1634 1635 line = line.rstrip()
1635 1636 if not line:
1636 1637 continue
1637 1638
1638 1639 if line.startswith(b'syntax:'):
1639 1640 s = line[7:].strip()
1640 1641 try:
1641 1642 syntax = syntaxes[s]
1642 1643 except KeyError:
1643 1644 if warn:
1644 1645 warn(
1645 1646 _(b"%s: ignoring invalid syntax '%s'\n") % (filepath, s)
1646 1647 )
1647 1648 continue
1648 1649
1649 1650 linesyntax = syntax
1650 1651 for s, rels in syntaxes.items():
1651 1652 if line.startswith(rels):
1652 1653 linesyntax = rels
1653 1654 line = line[len(rels) :]
1654 1655 break
1655 1656 elif line.startswith(s + b':'):
1656 1657 linesyntax = rels
1657 1658 line = line[len(s) + 1 :]
1658 1659 break
1659 1660 if sourceinfo:
1660 1661 patterns.append((linesyntax + line, lineno, line))
1661 1662 else:
1662 1663 patterns.append(linesyntax + line)
1663 1664 fp.close()
1664 1665 return patterns
@@ -1,1037 +1,1037 b''
1 1 $ fileset() {
2 2 > hg debugfileset --all-files "$@"
3 3 > }
4 4
5 5 $ hg init repo
6 6 $ cd repo
7 7 $ echo a > a1
8 8 $ echo a > a2
9 9 $ echo b > b1
10 10 $ echo b > b2
11 11 $ hg ci -Am addfiles
12 12 adding a1
13 13 adding a2
14 14 adding b1
15 15 adding b2
16 16
17 17 Test operators and basic patterns
18 18
19 19 $ fileset -v a1
20 20 (symbol 'a1')
21 21 * matcher:
22 22 <patternmatcher patterns='a1$'>
23 23 a1
24 24 $ fileset -v 'a*'
25 25 (symbol 'a*')
26 26 * matcher:
27 27 <patternmatcher patterns='a[^/]*$'>
28 28 a1
29 29 a2
30 30 $ fileset -v '"re:a\d"'
31 31 (string 're:a\\d')
32 32 * matcher:
33 33 <patternmatcher patterns='a\\d'>
34 34 a1
35 35 a2
36 36 $ fileset -v '!re:"a\d"'
37 37 (not
38 38 (kindpat
39 39 (symbol 're')
40 40 (string 'a\\d')))
41 41 * matcher:
42 42 <predicatenmatcher
43 43 pred=<not
44 44 <patternmatcher patterns='a\\d'>>>
45 45 b1
46 46 b2
47 47 $ fileset -v 'path:a1 or glob:b?'
48 48 (or
49 49 (kindpat
50 50 (symbol 'path')
51 51 (symbol 'a1'))
52 52 (kindpat
53 53 (symbol 'glob')
54 54 (symbol 'b?')))
55 55 * matcher:
56 <patternmatcher patterns='a1(?:/|$)|b.$'>
56 <patternmatcher patterns='b.$|a1(?:/|$)'>
57 57 a1
58 58 b1
59 59 b2
60 60 $ fileset -v --no-show-matcher 'a1 or a2'
61 61 (or
62 62 (symbol 'a1')
63 63 (symbol 'a2'))
64 64 a1
65 65 a2
66 66 $ fileset 'a1 | a2'
67 67 a1
68 68 a2
69 69 $ fileset 'a* and "*1"'
70 70 a1
71 71 $ fileset 'a* & "*1"'
72 72 a1
73 73 $ fileset 'not (r"a*")'
74 74 b1
75 75 b2
76 76 $ fileset '! ("a*")'
77 77 b1
78 78 b2
79 79 $ fileset 'a* - a1'
80 80 a2
81 81 $ fileset 'a_b'
82 82 $ fileset '"\xy"'
83 83 hg: parse error: invalid \x escape* (glob)
84 84 [10]
85 85
86 86 Test invalid syntax
87 87
88 88 $ fileset -v '"added"()'
89 89 (func
90 90 (string 'added')
91 91 None)
92 92 hg: parse error: not a symbol
93 93 [10]
94 94 $ fileset -v '()()'
95 95 (func
96 96 (group
97 97 None)
98 98 None)
99 99 hg: parse error: not a symbol
100 100 [10]
101 101 $ fileset -v -- '-x'
102 102 (negate
103 103 (symbol 'x'))
104 104 hg: parse error: can't use negate operator in this context
105 105 [10]
106 106 $ fileset -v -- '-()'
107 107 (negate
108 108 (group
109 109 None))
110 110 hg: parse error: can't use negate operator in this context
111 111 [10]
112 112 $ fileset -p parsed 'a, b, c'
113 113 * parsed:
114 114 (list
115 115 (symbol 'a')
116 116 (symbol 'b')
117 117 (symbol 'c'))
118 118 hg: parse error: can't use a list in this context
119 119 (see 'hg help "filesets.x or y"')
120 120 [10]
121 121
122 122 $ fileset '"path":.'
123 123 hg: parse error: not a symbol
124 124 [10]
125 125 $ fileset 'path:foo bar'
126 126 hg: parse error at 9: invalid token
127 127 [10]
128 128 $ fileset 'foo:bar:baz'
129 129 hg: parse error: not a symbol
130 130 [10]
131 131 $ fileset 'foo:bar()'
132 132 hg: parse error: pattern must be a string
133 133 [10]
134 134 $ fileset 'foo:bar'
135 135 hg: parse error: invalid pattern kind: foo
136 136 [10]
137 137
138 138 Show parsed tree at stages:
139 139
140 140 $ fileset -p unknown a
141 141 abort: invalid stage name: unknown
142 142 [255]
143 143
144 144 $ fileset -p parsed 'path:a1 or glob:b?'
145 145 * parsed:
146 146 (or
147 147 (kindpat
148 148 (symbol 'path')
149 149 (symbol 'a1'))
150 150 (kindpat
151 151 (symbol 'glob')
152 152 (symbol 'b?')))
153 153 a1
154 154 b1
155 155 b2
156 156
157 157 $ fileset -p all -s 'a1 or a2 or (grep("b") & clean())'
158 158 * parsed:
159 159 (or
160 160 (symbol 'a1')
161 161 (symbol 'a2')
162 162 (group
163 163 (and
164 164 (func
165 165 (symbol 'grep')
166 166 (string 'b'))
167 167 (func
168 168 (symbol 'clean')
169 169 None))))
170 170 * analyzed:
171 171 (or
172 172 (symbol 'a1')
173 173 (symbol 'a2')
174 174 (and
175 175 (func
176 176 (symbol 'grep')
177 177 (string 'b'))
178 178 (withstatus
179 179 (func
180 180 (symbol 'clean')
181 181 None)
182 182 (string 'clean'))))
183 183 * optimized:
184 184 (or
185 185 (patterns
186 186 (symbol 'a1')
187 187 (symbol 'a2'))
188 188 (and
189 189 (withstatus
190 190 (func
191 191 (symbol 'clean')
192 192 None)
193 193 (string 'clean'))
194 194 (func
195 195 (symbol 'grep')
196 196 (string 'b'))))
197 197 * matcher:
198 198 <unionmatcher matchers=[
199 199 <patternmatcher patterns='a1$|a2$'>,
200 200 <intersectionmatcher
201 201 m1=<predicatenmatcher pred=clean>,
202 202 m2=<predicatenmatcher pred=grep('b')>>]>
203 203 a1
204 204 a2
205 205 b1
206 206 b2
207 207
208 208 Union of basic patterns:
209 209
210 210 $ fileset -p optimized -s -r. 'a1 or a2 or path:b1'
211 211 * optimized:
212 212 (patterns
213 213 (symbol 'a1')
214 214 (symbol 'a2')
215 215 (kindpat
216 216 (symbol 'path')
217 217 (symbol 'b1')))
218 218 * matcher:
219 219 <patternmatcher patterns='a1$|a2$|b1(?:/|$)'>
220 220 a1
221 221 a2
222 222 b1
223 223
224 224 OR expression should be reordered by weight:
225 225
226 226 $ fileset -p optimized -s -r. 'grep("a") or a1 or grep("b") or b2'
227 227 * optimized:
228 228 (or
229 229 (patterns
230 230 (symbol 'a1')
231 231 (symbol 'b2'))
232 232 (func
233 233 (symbol 'grep')
234 234 (string 'a'))
235 235 (func
236 236 (symbol 'grep')
237 237 (string 'b')))
238 238 * matcher:
239 239 <unionmatcher matchers=[
240 240 <patternmatcher patterns='a1$|b2$'>,
241 241 <predicatenmatcher pred=grep('a')>,
242 242 <predicatenmatcher pred=grep('b')>]>
243 243 a1
244 244 a2
245 245 b1
246 246 b2
247 247
248 248 Use differencematcher for 'x and not y':
249 249
250 250 $ fileset -p optimized -s 'a* and not a1'
251 251 * optimized:
252 252 (minus
253 253 (symbol 'a*')
254 254 (symbol 'a1'))
255 255 * matcher:
256 256 <differencematcher
257 257 m1=<patternmatcher patterns='a[^/]*$'>,
258 258 m2=<patternmatcher patterns='a1$'>>
259 259 a2
260 260
261 261 $ fileset -p optimized -s '!binary() and a*'
262 262 * optimized:
263 263 (minus
264 264 (symbol 'a*')
265 265 (func
266 266 (symbol 'binary')
267 267 None))
268 268 * matcher:
269 269 <differencematcher
270 270 m1=<patternmatcher patterns='a[^/]*$'>,
271 271 m2=<predicatenmatcher pred=binary>>
272 272 a1
273 273 a2
274 274
275 275 'x - y' is rewritten to 'x and not y' first so the operands can be reordered:
276 276
277 277 $ fileset -p analyzed -p optimized -s 'a* - a1'
278 278 * analyzed:
279 279 (and
280 280 (symbol 'a*')
281 281 (not
282 282 (symbol 'a1')))
283 283 * optimized:
284 284 (minus
285 285 (symbol 'a*')
286 286 (symbol 'a1'))
287 287 * matcher:
288 288 <differencematcher
289 289 m1=<patternmatcher patterns='a[^/]*$'>,
290 290 m2=<patternmatcher patterns='a1$'>>
291 291 a2
292 292
293 293 $ fileset -p analyzed -p optimized -s 'binary() - a*'
294 294 * analyzed:
295 295 (and
296 296 (func
297 297 (symbol 'binary')
298 298 None)
299 299 (not
300 300 (symbol 'a*')))
301 301 * optimized:
302 302 (and
303 303 (not
304 304 (symbol 'a*'))
305 305 (func
306 306 (symbol 'binary')
307 307 None))
308 308 * matcher:
309 309 <intersectionmatcher
310 310 m1=<predicatenmatcher
311 311 pred=<not
312 312 <patternmatcher patterns='a[^/]*$'>>>,
313 313 m2=<predicatenmatcher pred=binary>>
314 314
315 315 Test files status
316 316
317 317 $ rm a1
318 318 $ hg rm a2
319 319 $ echo b >> b2
320 320 $ hg cp b1 c1
321 321 $ echo c > c2
322 322 $ echo c > c3
323 323 $ cat > .hgignore <<EOF
324 324 > \.hgignore
325 325 > 2$
326 326 > EOF
327 327 $ fileset 'modified()'
328 328 b2
329 329 $ fileset 'added()'
330 330 c1
331 331 $ fileset 'removed()'
332 332 a2
333 333 $ fileset 'deleted()'
334 334 a1
335 335 $ fileset 'missing()'
336 336 a1
337 337 $ fileset 'unknown()'
338 338 c3
339 339 $ fileset 'ignored()'
340 340 .hgignore
341 341 c2
342 342 $ fileset 'hgignore()'
343 343 .hgignore
344 344 a2
345 345 b2
346 346 c2
347 347 $ fileset 'clean()'
348 348 b1
349 349 $ fileset 'copied()'
350 350 c1
351 351
352 352 Test files status in different revisions
353 353
354 354 $ hg status -m
355 355 M b2
356 356 $ fileset -r0 'revs("wdir()", modified())' --traceback
357 357 b2
358 358 $ hg status -a
359 359 A c1
360 360 $ fileset -r0 'revs("wdir()", added())'
361 361 c1
362 362 $ hg status --change 0 -a
363 363 A a1
364 364 A a2
365 365 A b1
366 366 A b2
367 367 $ hg status -mru
368 368 M b2
369 369 R a2
370 370 ? c3
371 371 $ fileset -r0 'added() and revs("wdir()", modified() or removed() or unknown())'
372 372 a2
373 373 b2
374 374 $ fileset -r0 'added() or revs("wdir()", added())'
375 375 a1
376 376 a2
377 377 b1
378 378 b2
379 379 c1
380 380
381 381 Test insertion of status hints
382 382
383 383 $ fileset -p optimized 'added()'
384 384 * optimized:
385 385 (withstatus
386 386 (func
387 387 (symbol 'added')
388 388 None)
389 389 (string 'added'))
390 390 c1
391 391
392 392 $ fileset -p optimized 'a* & removed()'
393 393 * optimized:
394 394 (and
395 395 (symbol 'a*')
396 396 (withstatus
397 397 (func
398 398 (symbol 'removed')
399 399 None)
400 400 (string 'removed')))
401 401 a2
402 402
403 403 $ fileset -p optimized 'a* - removed()'
404 404 * optimized:
405 405 (minus
406 406 (symbol 'a*')
407 407 (withstatus
408 408 (func
409 409 (symbol 'removed')
410 410 None)
411 411 (string 'removed')))
412 412 a1
413 413
414 414 $ fileset -p analyzed -p optimized '(added() + removed()) - a*'
415 415 * analyzed:
416 416 (and
417 417 (withstatus
418 418 (or
419 419 (func
420 420 (symbol 'added')
421 421 None)
422 422 (func
423 423 (symbol 'removed')
424 424 None))
425 425 (string 'added removed'))
426 426 (not
427 427 (symbol 'a*')))
428 428 * optimized:
429 429 (and
430 430 (not
431 431 (symbol 'a*'))
432 432 (withstatus
433 433 (or
434 434 (func
435 435 (symbol 'added')
436 436 None)
437 437 (func
438 438 (symbol 'removed')
439 439 None))
440 440 (string 'added removed')))
441 441 c1
442 442
443 443 $ fileset -p optimized 'a* + b* + added() + unknown()'
444 444 * optimized:
445 445 (withstatus
446 446 (or
447 447 (patterns
448 448 (symbol 'a*')
449 449 (symbol 'b*'))
450 450 (func
451 451 (symbol 'added')
452 452 None)
453 453 (func
454 454 (symbol 'unknown')
455 455 None))
456 456 (string 'added unknown'))
457 457 a1
458 458 a2
459 459 b1
460 460 b2
461 461 c1
462 462 c3
463 463
464 464 $ fileset -p analyzed -p optimized 'removed() & missing() & a*'
465 465 * analyzed:
466 466 (and
467 467 (withstatus
468 468 (and
469 469 (func
470 470 (symbol 'removed')
471 471 None)
472 472 (func
473 473 (symbol 'missing')
474 474 None))
475 475 (string 'removed missing'))
476 476 (symbol 'a*'))
477 477 * optimized:
478 478 (and
479 479 (symbol 'a*')
480 480 (withstatus
481 481 (and
482 482 (func
483 483 (symbol 'removed')
484 484 None)
485 485 (func
486 486 (symbol 'missing')
487 487 None))
488 488 (string 'removed missing')))
489 489
490 490 $ fileset -p optimized 'clean() & revs(0, added())'
491 491 * optimized:
492 492 (and
493 493 (withstatus
494 494 (func
495 495 (symbol 'clean')
496 496 None)
497 497 (string 'clean'))
498 498 (func
499 499 (symbol 'revs')
500 500 (list
501 501 (symbol '0')
502 502 (withstatus
503 503 (func
504 504 (symbol 'added')
505 505 None)
506 506 (string 'added')))))
507 507 b1
508 508
509 509 $ fileset -p optimized 'clean() & status(null, 0, b* & added())'
510 510 * optimized:
511 511 (and
512 512 (withstatus
513 513 (func
514 514 (symbol 'clean')
515 515 None)
516 516 (string 'clean'))
517 517 (func
518 518 (symbol 'status')
519 519 (list
520 520 (symbol 'null')
521 521 (symbol '0')
522 522 (and
523 523 (symbol 'b*')
524 524 (withstatus
525 525 (func
526 526 (symbol 'added')
527 527 None)
528 528 (string 'added'))))))
529 529 b1
530 530
531 531 Test files properties
532 532
533 533 >>> open('bin', 'wb').write(b'\0a') and None
534 534 $ fileset 'binary()'
535 535 bin
536 536 $ fileset 'binary() and unknown()'
537 537 bin
538 538 $ echo '^bin$' >> .hgignore
539 539 $ fileset 'binary() and ignored()'
540 540 bin
541 541 $ hg add bin
542 542 $ fileset 'binary()'
543 543 bin
544 544
545 545 $ fileset -p optimized -s 'binary() and b*'
546 546 * optimized:
547 547 (and
548 548 (symbol 'b*')
549 549 (func
550 550 (symbol 'binary')
551 551 None))
552 552 * matcher:
553 553 <intersectionmatcher
554 554 m1=<patternmatcher patterns='b[^/]*$'>,
555 555 m2=<predicatenmatcher pred=binary>>
556 556 bin
557 557
558 558 $ fileset 'grep("b{1}")'
559 559 .hgignore
560 560 b1
561 561 b2
562 562 c1
563 563 $ fileset 'grep("missingparens(")'
564 564 hg: parse error: invalid match pattern: (unbalanced parenthesis|missing \)).* (re)
565 565 [10]
566 566
567 567 #if execbit
568 568 $ chmod +x b2
569 569 $ fileset 'exec()'
570 570 b2
571 571 #endif
572 572
573 573 #if symlink
574 574 $ ln -s b2 b2link
575 575 $ fileset 'symlink() and unknown()'
576 576 b2link
577 577 $ hg add b2link
578 578 #endif
579 579
580 580 #if no-windows
581 581 $ echo foo > con.xml
582 582 $ fileset 'not portable()'
583 583 con.xml
584 584 $ hg --config ui.portablefilenames=ignore add con.xml
585 585 #endif
586 586
587 587 >>> open('1k', 'wb').write(b' '*1024) and None
588 588 >>> open('2k', 'wb').write(b' '*2048) and None
589 589 $ hg add 1k 2k
590 590 $ fileset 'size("bar")'
591 591 hg: parse error: couldn't parse size: bar
592 592 [10]
593 593 $ fileset '(1k, 2k)'
594 594 hg: parse error: can't use a list in this context
595 595 (see 'hg help "filesets.x or y"')
596 596 [10]
597 597 $ fileset 'size(1k)'
598 598 1k
599 599 $ fileset '(1k or 2k) and size("< 2k")'
600 600 1k
601 601 $ fileset '(1k or 2k) and size("<=2k")'
602 602 1k
603 603 2k
604 604 $ fileset '(1k or 2k) and size("> 1k")'
605 605 2k
606 606 $ fileset '(1k or 2k) and size(">=1K")'
607 607 1k
608 608 2k
609 609 $ fileset '(1k or 2k) and size(".5KB - 1.5kB")'
610 610 1k
611 611 $ fileset 'size("1M")'
612 612 $ fileset 'size("1 GB")'
613 613
614 614 Test merge states
615 615
616 616 $ hg ci -m manychanges
617 617 $ hg file -r . 'set:copied() & modified()'
618 618 [1]
619 619 $ hg up -C 0
620 620 * files updated, 0 files merged, * files removed, 0 files unresolved (glob)
621 621 $ echo c >> b2
622 622 $ hg ci -m diverging b2
623 623 created new head
624 624 $ fileset 'resolved()'
625 625 $ fileset 'unresolved()'
626 626 $ hg merge
627 627 merging b2
628 628 warning: conflicts while merging b2! (edit, then use 'hg resolve --mark')
629 629 * files updated, 0 files merged, 1 files removed, 1 files unresolved (glob)
630 630 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
631 631 [1]
632 632 $ fileset 'resolved()'
633 633 $ fileset 'unresolved()'
634 634 b2
635 635 $ echo e > b2
636 636 $ hg resolve -m b2
637 637 (no more unresolved files)
638 638 $ fileset 'resolved()'
639 639 b2
640 640 $ fileset 'unresolved()'
641 641 $ hg ci -m merge
642 642
643 643 Test subrepo predicate
644 644
645 645 $ hg init sub
646 646 $ echo a > sub/suba
647 647 $ hg -R sub add sub/suba
648 648 $ hg -R sub ci -m sub
649 649 $ echo 'sub = sub' > .hgsub
650 650 $ hg init sub2
651 651 $ echo b > sub2/b
652 652 $ hg -R sub2 ci -Am sub2
653 653 adding b
654 654 $ echo 'sub2 = sub2' >> .hgsub
655 655 $ fileset 'subrepo()'
656 656 $ hg add .hgsub
657 657 $ fileset 'subrepo()'
658 658 sub
659 659 sub2
660 660 $ fileset 'subrepo("sub")'
661 661 sub
662 662 $ fileset 'subrepo("glob:*")'
663 663 sub
664 664 sub2
665 665 $ hg ci -m subrepo
666 666
667 667 Test that .hgsubstate is updated as appropriate during a conversion. The
668 668 saverev property is enough to alter the hashes of the subrepo.
669 669
670 670 $ hg init ../converted
671 671 $ hg --config extensions.convert= convert --config convert.hg.saverev=True \
672 672 > sub ../converted/sub
673 673 initializing destination ../converted/sub repository
674 674 scanning source...
675 675 sorting...
676 676 converting...
677 677 0 sub
678 678 $ hg clone -U sub2 ../converted/sub2
679 679 $ hg --config extensions.convert= convert --config convert.hg.saverev=True \
680 680 > . ../converted
681 681 scanning source...
682 682 sorting...
683 683 converting...
684 684 4 addfiles
685 685 3 manychanges
686 686 2 diverging
687 687 1 merge
688 688 0 subrepo
689 689 no ".hgsubstate" updates will be made for "sub2"
690 690 $ hg up -q -R ../converted -r tip
691 691 $ hg --cwd ../converted cat sub/suba sub2/b -r tip
692 692 a
693 693 b
694 694 $ oldnode=`hg log -r tip -T "{node}\n"`
695 695 $ newnode=`hg log -R ../converted -r tip -T "{node}\n"`
696 696 $ [ "$oldnode" != "$newnode" ] || echo "nothing changed"
697 697
698 698 Test with a revision
699 699
700 700 $ hg log -G --template '{rev} {desc}\n'
701 701 @ 4 subrepo
702 702 |
703 703 o 3 merge
704 704 |\
705 705 | o 2 diverging
706 706 | |
707 707 o | 1 manychanges
708 708 |/
709 709 o 0 addfiles
710 710
711 711 $ echo unknown > unknown
712 712 $ fileset -r1 'modified()'
713 713 b2
714 714 $ fileset -r1 'added() and c1'
715 715 c1
716 716 $ fileset -r1 'removed()'
717 717 a2
718 718 $ fileset -r1 'deleted()'
719 719 $ fileset -r1 'unknown()'
720 720 $ fileset -r1 'ignored()'
721 721 $ fileset -r1 'hgignore()'
722 722 .hgignore
723 723 a2
724 724 b2
725 725 bin
726 726 c2
727 727 sub2
728 728 $ fileset -r1 'binary()'
729 729 bin
730 730 $ fileset -r1 'size(1k)'
731 731 1k
732 732 $ fileset -r3 'resolved()'
733 733 $ fileset -r3 'unresolved()'
734 734
735 735 #if execbit
736 736 $ fileset -r1 'exec()'
737 737 b2
738 738 #endif
739 739
740 740 #if symlink
741 741 $ fileset -r1 'symlink()'
742 742 b2link
743 743 #endif
744 744
745 745 #if no-windows
746 746 $ fileset -r1 'not portable()'
747 747 con.xml
748 748 $ hg forget 'con.xml'
749 749 #endif
750 750
751 751 $ fileset -r4 'subrepo("re:su.*")'
752 752 sub
753 753 sub2
754 754 $ fileset -r4 'subrepo(re:su.*)'
755 755 sub
756 756 sub2
757 757 $ fileset -r4 'subrepo("sub")'
758 758 sub
759 759 $ fileset -r4 'b2 or c1'
760 760 b2
761 761 c1
762 762
763 763 >>> open('dos', 'wb').write(b"dos\r\n") and None
764 764 >>> open('mixed', 'wb').write(b"dos\r\nunix\n") and None
765 765 >>> open('mac', 'wb').write(b"mac\r") and None
766 766 $ hg add dos mixed mac
767 767
768 768 (remove a1, to examine safety of 'eol' on removed files)
769 769 $ rm a1
770 770
771 771 $ fileset 'eol(dos)'
772 772 dos
773 773 mixed
774 774 $ fileset 'eol(unix)'
775 775 .hgignore
776 776 .hgsub
777 777 .hgsubstate
778 778 b1
779 779 b2
780 780 b2.orig
781 781 c1
782 782 c2
783 783 c3
784 784 con.xml (no-windows !)
785 785 mixed
786 786 unknown
787 787 $ fileset 'eol(mac)'
788 788 mac
789 789
790 790 Test safety of 'encoding' on removed files
791 791
792 792 $ fileset 'encoding("ascii")'
793 793 .hgignore
794 794 .hgsub
795 795 .hgsubstate
796 796 1k
797 797 2k
798 798 b1
799 799 b2
800 800 b2.orig
801 801 b2link (symlink !)
802 802 bin
803 803 c1
804 804 c2
805 805 c3
806 806 con.xml (no-windows !)
807 807 dos
808 808 mac
809 809 mixed
810 810 unknown
811 811
812 812 Test 'revs(...)'
813 813 ================
814 814
815 815 small reminder of the repository state
816 816
817 817 $ hg log -G
818 818 @ changeset: 4:* (glob)
819 819 | tag: tip
820 820 | user: test
821 821 | date: Thu Jan 01 00:00:00 1970 +0000
822 822 | summary: subrepo
823 823 |
824 824 o changeset: 3:* (glob)
825 825 |\ parent: 2:55b05bdebf36
826 826 | | parent: 1:* (glob)
827 827 | | user: test
828 828 | | date: Thu Jan 01 00:00:00 1970 +0000
829 829 | | summary: merge
830 830 | |
831 831 | o changeset: 2:55b05bdebf36
832 832 | | parent: 0:8a9576c51c1f
833 833 | | user: test
834 834 | | date: Thu Jan 01 00:00:00 1970 +0000
835 835 | | summary: diverging
836 836 | |
837 837 o | changeset: 1:* (glob)
838 838 |/ user: test
839 839 | date: Thu Jan 01 00:00:00 1970 +0000
840 840 | summary: manychanges
841 841 |
842 842 o changeset: 0:8a9576c51c1f
843 843 user: test
844 844 date: Thu Jan 01 00:00:00 1970 +0000
845 845 summary: addfiles
846 846
847 847 $ hg status --change 0
848 848 A a1
849 849 A a2
850 850 A b1
851 851 A b2
852 852 $ hg status --change 1
853 853 M b2
854 854 A 1k
855 855 A 2k
856 856 A b2link (symlink !)
857 857 A bin
858 858 A c1
859 859 A con.xml (no-windows !)
860 860 R a2
861 861 $ hg status --change 2
862 862 M b2
863 863 $ hg status --change 3
864 864 M b2
865 865 A 1k
866 866 A 2k
867 867 A b2link (symlink !)
868 868 A bin
869 869 A c1
870 870 A con.xml (no-windows !)
871 871 R a2
872 872 $ hg status --change 4
873 873 A .hgsub
874 874 A .hgsubstate
875 875 $ hg status
876 876 A dos
877 877 A mac
878 878 A mixed
879 879 R con.xml (no-windows !)
880 880 ! a1
881 881 ? b2.orig
882 882 ? c3
883 883 ? unknown
884 884
885 885 Test files at -r0 should be filtered by files at wdir
886 886 -----------------------------------------------------
887 887
888 888 $ fileset -r0 'tracked() and revs("wdir()", tracked())'
889 889 a1
890 890 b1
891 891 b2
892 892
893 893 Test that "revs()" work at all
894 894 ------------------------------
895 895
896 896 $ fileset "revs('2', modified())"
897 897 b2
898 898
899 899 Test that "revs()" work for file missing in the working copy/current context
900 900 ----------------------------------------------------------------------------
901 901
902 902 (a2 not in working copy)
903 903
904 904 $ fileset "revs('0', added())"
905 905 a1
906 906 a2
907 907 b1
908 908 b2
909 909
910 910 (none of the file exist in "0")
911 911
912 912 $ fileset -r 0 "revs('4', added())"
913 913 .hgsub
914 914 .hgsubstate
915 915
916 916 Call with empty revset
917 917 --------------------------
918 918
919 919 $ fileset "revs('2-2', modified())"
920 920
921 921 Call with revset matching multiple revs
922 922 ---------------------------------------
923 923
924 924 $ fileset "revs('0+4', added())"
925 925 .hgsub
926 926 .hgsubstate
927 927 a1
928 928 a2
929 929 b1
930 930 b2
931 931
932 932 overlapping set
933 933
934 934 $ fileset "revs('1+2', modified())"
935 935 b2
936 936
937 937 test 'status(...)'
938 938 =================
939 939
940 940 Simple case
941 941 -----------
942 942
943 943 $ fileset "status(3, 4, added())"
944 944 .hgsub
945 945 .hgsubstate
946 946
947 947 use rev to restrict matched file
948 948 -----------------------------------------
949 949
950 950 $ hg status --removed --rev 0 --rev 1
951 951 R a2
952 952 $ fileset "status(0, 1, removed())"
953 953 a2
954 954 $ fileset "tracked() and status(0, 1, removed())"
955 955 $ fileset -r 4 "status(0, 1, removed())"
956 956 a2
957 957 $ fileset -r 4 "tracked() and status(0, 1, removed())"
958 958 $ fileset "revs('4', tracked() and status(0, 1, removed()))"
959 959 $ fileset "revs('0', tracked() and status(0, 1, removed()))"
960 960 a2
961 961
962 962 check wdir()
963 963 ------------
964 964
965 965 $ hg status --removed --rev 4
966 966 R con.xml (no-windows !)
967 967 $ fileset "status(4, 'wdir()', removed())"
968 968 con.xml (no-windows !)
969 969
970 970 $ hg status --removed --rev 2
971 971 R a2
972 972 $ fileset "status('2', 'wdir()', removed())"
973 973 a2
974 974
975 975 test backward status
976 976 --------------------
977 977
978 978 $ hg status --removed --rev 0 --rev 4
979 979 R a2
980 980 $ hg status --added --rev 4 --rev 0
981 981 A a2
982 982 $ fileset "status(4, 0, added())"
983 983 a2
984 984
985 985 test cross branch status
986 986 ------------------------
987 987
988 988 $ hg status --added --rev 1 --rev 2
989 989 A a2
990 990 $ fileset "status(1, 2, added())"
991 991 a2
992 992
993 993 test with multi revs revset
994 994 ---------------------------
995 995 $ hg status --added --rev 0:1 --rev 3:4
996 996 A .hgsub
997 997 A .hgsubstate
998 998 A 1k
999 999 A 2k
1000 1000 A b2link (symlink !)
1001 1001 A bin
1002 1002 A c1
1003 1003 A con.xml (no-windows !)
1004 1004 $ fileset "status('0:1', '3:4', added())"
1005 1005 .hgsub
1006 1006 .hgsubstate
1007 1007 1k
1008 1008 2k
1009 1009 b2link (symlink !)
1010 1010 bin
1011 1011 c1
1012 1012 con.xml (no-windows !)
1013 1013
1014 1014 tests with empty value
1015 1015 ----------------------
1016 1016
1017 1017 Fully empty revset
1018 1018
1019 1019 $ fileset "status('', '4', added())"
1020 1020 hg: parse error: first argument to status must be a revision
1021 1021 [10]
1022 1022 $ fileset "status('2', '', added())"
1023 1023 hg: parse error: second argument to status must be a revision
1024 1024 [10]
1025 1025
1026 1026 Empty revset will error at the revset layer
1027 1027
1028 1028 $ fileset "status(' ', '4', added())"
1029 1029 hg: parse error at 1: not a prefix: end
1030 1030 (
1031 1031 ^ here)
1032 1032 [10]
1033 1033 $ fileset "status('2', ' ', added())"
1034 1034 hg: parse error at 1: not a prefix: end
1035 1035 (
1036 1036 ^ here)
1037 1037 [10]
@@ -1,651 +1,651 b''
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 -v
32 32 * matcher:
33 33 <alwaysmatcher>
34 34 f beans/black beans/black
35 35 f beans/borlotti beans/borlotti
36 36 f beans/kidney beans/kidney
37 37 f beans/navy beans/navy
38 38 f beans/pinto beans/pinto
39 39 f beans/turtle beans/turtle
40 40 f fennel fennel
41 41 f fenugreek fenugreek
42 42 f fiddlehead fiddlehead
43 43 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
44 44 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
45 45 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
46 46 f mammals/skunk mammals/skunk
47 47 $ hg debugwalk -v -I.
48 48 * matcher:
49 49 <includematcher includes=''>
50 50 f beans/black beans/black
51 51 f beans/borlotti beans/borlotti
52 52 f beans/kidney beans/kidney
53 53 f beans/navy beans/navy
54 54 f beans/pinto beans/pinto
55 55 f beans/turtle beans/turtle
56 56 f fennel fennel
57 57 f fenugreek fenugreek
58 58 f fiddlehead fiddlehead
59 59 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
60 60 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
61 61 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
62 62 f mammals/skunk mammals/skunk
63 63
64 64 $ cd mammals
65 65 $ hg debugwalk -v
66 66 * matcher:
67 67 <alwaysmatcher>
68 68 f beans/black ../beans/black
69 69 f beans/borlotti ../beans/borlotti
70 70 f beans/kidney ../beans/kidney
71 71 f beans/navy ../beans/navy
72 72 f beans/pinto ../beans/pinto
73 73 f beans/turtle ../beans/turtle
74 74 f fennel ../fennel
75 75 f fenugreek ../fenugreek
76 76 f fiddlehead ../fiddlehead
77 77 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
78 78 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
79 79 f mammals/Procyonidae/raccoon Procyonidae/raccoon
80 80 f mammals/skunk skunk
81 81 $ hg debugwalk -v -X ../beans
82 82 * matcher:
83 83 <differencematcher
84 84 m1=<alwaysmatcher>,
85 85 m2=<includematcher includes='beans(?:/|$)'>>
86 86 f fennel ../fennel
87 87 f fenugreek ../fenugreek
88 88 f fiddlehead ../fiddlehead
89 89 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
90 90 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
91 91 f mammals/Procyonidae/raccoon Procyonidae/raccoon
92 92 f mammals/skunk skunk
93 93 $ hg debugwalk -v -I '*k'
94 94 * matcher:
95 95 <includematcher includes='mammals/[^/]*k(?:/|$)'>
96 96 f mammals/skunk skunk
97 97 $ hg debugwalk -v -I 'glob:*k'
98 98 * matcher:
99 99 <includematcher includes='mammals/[^/]*k(?:/|$)'>
100 100 f mammals/skunk skunk
101 101 $ hg debugwalk -v -I 'relglob:*k'
102 102 * matcher:
103 103 <includematcher includes='.*k(?:/|$)'>
104 104 f beans/black ../beans/black
105 105 f fenugreek ../fenugreek
106 106 f mammals/skunk skunk
107 107 $ hg debugwalk -v -I 'relglob:*k' .
108 108 * matcher:
109 109 <intersectionmatcher
110 110 m1=<patternmatcher patterns='mammals(?:/|$)'>,
111 111 m2=<includematcher includes='.*k(?:/|$)'>>
112 112 f mammals/skunk skunk
113 113 $ hg debugwalk -v -I 're:.*k$'
114 114 * matcher:
115 115 <includematcher includes='.*k$'>
116 116 f beans/black ../beans/black
117 117 f fenugreek ../fenugreek
118 118 f mammals/skunk skunk
119 119 $ hg debugwalk -v -I 'relre:.*k$'
120 120 * matcher:
121 121 <includematcher includes='.*.*k$'>
122 122 f beans/black ../beans/black
123 123 f fenugreek ../fenugreek
124 124 f mammals/skunk skunk
125 125 $ hg debugwalk -v -I 'path:beans'
126 126 * matcher:
127 127 <includematcher includes='beans(?:/|$)'>
128 128 f beans/black ../beans/black
129 129 f beans/borlotti ../beans/borlotti
130 130 f beans/kidney ../beans/kidney
131 131 f beans/navy ../beans/navy
132 132 f beans/pinto ../beans/pinto
133 133 f beans/turtle ../beans/turtle
134 134 $ hg debugwalk -v -I 'relpath:detour/../../beans'
135 135 * matcher:
136 136 <includematcher includes='beans(?:/|$)'>
137 137 f beans/black ../beans/black
138 138 f beans/borlotti ../beans/borlotti
139 139 f beans/kidney ../beans/kidney
140 140 f beans/navy ../beans/navy
141 141 f beans/pinto ../beans/pinto
142 142 f beans/turtle ../beans/turtle
143 143
144 144 $ hg debugwalk -v 'rootfilesin:'
145 145 * matcher:
146 146 <patternmatcher patterns="rootfilesin: ['.']">
147 147 f fennel ../fennel
148 148 f fenugreek ../fenugreek
149 149 f fiddlehead ../fiddlehead
150 150 $ hg debugwalk -v -I 'rootfilesin:'
151 151 * matcher:
152 152 <includematcher includes="rootfilesin: ['.']">
153 153 f fennel ../fennel
154 154 f fenugreek ../fenugreek
155 155 f fiddlehead ../fiddlehead
156 156 $ hg debugwalk -v 'rootfilesin:.'
157 157 * matcher:
158 158 <patternmatcher patterns="rootfilesin: ['.']">
159 159 f fennel ../fennel
160 160 f fenugreek ../fenugreek
161 161 f fiddlehead ../fiddlehead
162 162 $ hg debugwalk -v -I 'rootfilesin:.'
163 163 * matcher:
164 164 <includematcher includes="rootfilesin: ['.']">
165 165 f fennel ../fennel
166 166 f fenugreek ../fenugreek
167 167 f fiddlehead ../fiddlehead
168 168 $ hg debugwalk -v -X 'rootfilesin:'
169 169 * matcher:
170 170 <differencematcher
171 171 m1=<alwaysmatcher>,
172 172 m2=<includematcher includes="rootfilesin: ['.']">>
173 173 f beans/black ../beans/black
174 174 f beans/borlotti ../beans/borlotti
175 175 f beans/kidney ../beans/kidney
176 176 f beans/navy ../beans/navy
177 177 f beans/pinto ../beans/pinto
178 178 f beans/turtle ../beans/turtle
179 179 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
180 180 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
181 181 f mammals/Procyonidae/raccoon Procyonidae/raccoon
182 182 f mammals/skunk skunk
183 183 $ hg debugwalk -v 'rootfilesin:fennel'
184 184 * matcher:
185 185 <patternmatcher patterns="rootfilesin: ['fennel']">
186 186 $ hg debugwalk -v -I 'rootfilesin:fennel'
187 187 * matcher:
188 188 <includematcher includes="rootfilesin: ['fennel']">
189 189 $ hg debugwalk -v 'rootfilesin:skunk'
190 190 * matcher:
191 191 <patternmatcher patterns="rootfilesin: ['skunk']">
192 192 $ hg debugwalk -v -I 'rootfilesin:skunk'
193 193 * matcher:
194 194 <includematcher includes="rootfilesin: ['skunk']">
195 195 $ hg debugwalk -v 'rootfilesin:beans'
196 196 * matcher:
197 197 <patternmatcher patterns="rootfilesin: ['beans']">
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 $ hg debugwalk -v -I 'rootfilesin:beans'
205 205 * matcher:
206 206 <includematcher includes="rootfilesin: ['beans']">
207 207 f beans/black ../beans/black
208 208 f beans/borlotti ../beans/borlotti
209 209 f beans/kidney ../beans/kidney
210 210 f beans/navy ../beans/navy
211 211 f beans/pinto ../beans/pinto
212 212 f beans/turtle ../beans/turtle
213 213 $ hg debugwalk -v 'rootfilesin:mammals'
214 214 * matcher:
215 215 <patternmatcher patterns="rootfilesin: ['mammals']">
216 216 f mammals/skunk skunk
217 217 $ hg debugwalk -v -I 'rootfilesin:mammals'
218 218 * matcher:
219 219 <includematcher includes="rootfilesin: ['mammals']">
220 220 f mammals/skunk skunk
221 221 $ hg debugwalk -v 'rootfilesin:mammals/'
222 222 * matcher:
223 223 <patternmatcher patterns="rootfilesin: ['mammals']">
224 224 f mammals/skunk skunk
225 225 $ hg debugwalk -v -I 'rootfilesin:mammals/'
226 226 * matcher:
227 227 <includematcher includes="rootfilesin: ['mammals']">
228 228 f mammals/skunk skunk
229 229 $ hg debugwalk -v -X 'rootfilesin:mammals'
230 230 * matcher:
231 231 <differencematcher
232 232 m1=<alwaysmatcher>,
233 233 m2=<includematcher includes="rootfilesin: ['mammals']">>
234 234 f beans/black ../beans/black
235 235 f beans/borlotti ../beans/borlotti
236 236 f beans/kidney ../beans/kidney
237 237 f beans/navy ../beans/navy
238 238 f beans/pinto ../beans/pinto
239 239 f beans/turtle ../beans/turtle
240 240 f fennel ../fennel
241 241 f fenugreek ../fenugreek
242 242 f fiddlehead ../fiddlehead
243 243 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
244 244 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
245 245 f mammals/Procyonidae/raccoon Procyonidae/raccoon
246 246
247 247 $ hg debugwalk -v .
248 248 * matcher:
249 249 <patternmatcher patterns='mammals(?:/|$)'>
250 250 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
251 251 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
252 252 f mammals/Procyonidae/raccoon Procyonidae/raccoon
253 253 f mammals/skunk skunk
254 254 $ hg debugwalk -v -I.
255 255 * matcher:
256 256 <includematcher includes='mammals(?:/|$)'>
257 257 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
258 258 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
259 259 f mammals/Procyonidae/raccoon Procyonidae/raccoon
260 260 f mammals/skunk skunk
261 261 $ hg debugwalk -v Procyonidae
262 262 * matcher:
263 263 <patternmatcher patterns='mammals/Procyonidae(?:/|$)'>
264 264 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
265 265 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
266 266 f mammals/Procyonidae/raccoon Procyonidae/raccoon
267 267
268 268 $ cd Procyonidae
269 269 $ hg debugwalk -v .
270 270 * matcher:
271 271 <patternmatcher patterns='mammals/Procyonidae(?:/|$)'>
272 272 f mammals/Procyonidae/cacomistle cacomistle
273 273 f mammals/Procyonidae/coatimundi coatimundi
274 274 f mammals/Procyonidae/raccoon raccoon
275 275 $ hg debugwalk -v ..
276 276 * matcher:
277 277 <patternmatcher patterns='mammals(?:/|$)'>
278 278 f mammals/Procyonidae/cacomistle cacomistle
279 279 f mammals/Procyonidae/coatimundi coatimundi
280 280 f mammals/Procyonidae/raccoon raccoon
281 281 f mammals/skunk ../skunk
282 282 $ cd ..
283 283
284 284 $ hg debugwalk -v ../beans
285 285 * matcher:
286 286 <patternmatcher patterns='beans(?:/|$)'>
287 287 f beans/black ../beans/black
288 288 f beans/borlotti ../beans/borlotti
289 289 f beans/kidney ../beans/kidney
290 290 f beans/navy ../beans/navy
291 291 f beans/pinto ../beans/pinto
292 292 f beans/turtle ../beans/turtle
293 293 $ hg debugwalk -v .
294 294 * matcher:
295 295 <patternmatcher patterns='mammals(?:/|$)'>
296 296 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
297 297 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
298 298 f mammals/Procyonidae/raccoon Procyonidae/raccoon
299 299 f mammals/skunk skunk
300 300 $ hg debugwalk -v .hg
301 301 abort: path 'mammals/.hg' is inside nested repo 'mammals'
302 302 [10]
303 303 $ hg debugwalk -v ../.hg
304 304 abort: path contains illegal component: .hg
305 305 [10]
306 306 $ cd ..
307 307
308 308 $ hg debugwalk -v -Ibeans
309 309 * matcher:
310 310 <includematcher includes='beans(?:/|$)'>
311 311 f beans/black beans/black
312 312 f beans/borlotti beans/borlotti
313 313 f beans/kidney beans/kidney
314 314 f beans/navy beans/navy
315 315 f beans/pinto beans/pinto
316 316 f beans/turtle beans/turtle
317 317 $ hg debugwalk -v -I '{*,{b,m}*/*}k'
318 318 * matcher:
319 319 <includematcher includes='(?:[^/]*|(?:b|m)[^/]*/[^/]*)k(?:/|$)'>
320 320 f beans/black beans/black
321 321 f fenugreek fenugreek
322 322 f mammals/skunk mammals/skunk
323 323 $ hg debugwalk -v -Ibeans mammals
324 324 * matcher:
325 325 <intersectionmatcher
326 326 m1=<patternmatcher patterns='mammals(?:/|$)'>,
327 327 m2=<includematcher includes='beans(?:/|$)'>>
328 328 $ hg debugwalk -v -Inon-existent
329 329 * matcher:
330 330 <includematcher includes='non\\-existent(?:/|$)'>
331 331 $ hg debugwalk -v -Inon-existent -Ibeans/black
332 332 * matcher:
333 333 <includematcher includes='non\\-existent(?:/|$)|beans/black(?:/|$)'>
334 334 f beans/black beans/black
335 335 $ hg debugwalk -v -Ibeans beans/black
336 336 * matcher:
337 337 <intersectionmatcher
338 338 m1=<patternmatcher patterns='beans/black(?:/|$)'>,
339 339 m2=<includematcher includes='beans(?:/|$)'>>
340 340 f beans/black beans/black exact
341 341 $ hg debugwalk -v -Ibeans/black beans
342 342 * matcher:
343 343 <intersectionmatcher
344 344 m1=<patternmatcher patterns='beans(?:/|$)'>,
345 345 m2=<includematcher includes='beans/black(?:/|$)'>>
346 346 f beans/black beans/black
347 347 $ hg debugwalk -v -Xbeans/black beans
348 348 * matcher:
349 349 <differencematcher
350 350 m1=<patternmatcher patterns='beans(?:/|$)'>,
351 351 m2=<includematcher includes='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 -v -Xbeans/black -Ibeans
358 358 * matcher:
359 359 <differencematcher
360 360 m1=<includematcher includes='beans(?:/|$)'>,
361 361 m2=<includematcher includes='beans/black(?:/|$)'>>
362 362 f beans/borlotti beans/borlotti
363 363 f beans/kidney beans/kidney
364 364 f beans/navy beans/navy
365 365 f beans/pinto beans/pinto
366 366 f beans/turtle beans/turtle
367 367 $ hg debugwalk -v -Xbeans/black beans/black
368 368 * matcher:
369 369 <differencematcher
370 370 m1=<patternmatcher patterns='beans/black(?:/|$)'>,
371 371 m2=<includematcher includes='beans/black(?:/|$)'>>
372 372 $ hg debugwalk -v -Xbeans/black -Ibeans/black
373 373 * matcher:
374 374 <differencematcher
375 375 m1=<includematcher includes='beans/black(?:/|$)'>,
376 376 m2=<includematcher includes='beans/black(?:/|$)'>>
377 377 $ hg debugwalk -v -Xbeans beans/black
378 378 * matcher:
379 379 <differencematcher
380 380 m1=<patternmatcher patterns='beans/black(?:/|$)'>,
381 381 m2=<includematcher includes='beans(?:/|$)'>>
382 382 $ hg debugwalk -v -Xbeans -Ibeans/black
383 383 * matcher:
384 384 <differencematcher
385 385 m1=<includematcher includes='beans/black(?:/|$)'>,
386 386 m2=<includematcher includes='beans(?:/|$)'>>
387 387 $ hg debugwalk -v 'glob:mammals/../beans/b*'
388 388 * matcher:
389 389 <patternmatcher patterns='beans/b[^/]*$'>
390 390 f beans/black beans/black
391 391 f beans/borlotti beans/borlotti
392 392 $ hg debugwalk -v '-X*/Procyonidae' mammals
393 393 * matcher:
394 394 <differencematcher
395 395 m1=<patternmatcher patterns='mammals(?:/|$)'>,
396 396 m2=<includematcher includes='[^/]*/Procyonidae(?:/|$)'>>
397 397 f mammals/skunk mammals/skunk
398 398 $ hg debugwalk -v path:mammals
399 399 * matcher:
400 400 <patternmatcher patterns='mammals(?:/|$)'>
401 401 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
402 402 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
403 403 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
404 404 f mammals/skunk mammals/skunk
405 405 $ hg debugwalk -v ..
406 406 abort: .. not under root '$TESTTMP/t'
407 407 [255]
408 408 $ hg debugwalk -v beans/../..
409 409 abort: beans/../.. not under root '$TESTTMP/t'
410 410 [255]
411 411 $ hg debugwalk -v .hg
412 412 abort: path contains illegal component: .hg
413 413 [10]
414 414 $ hg debugwalk -v beans/../.hg
415 415 abort: path contains illegal component: .hg
416 416 [10]
417 417 $ hg debugwalk -v beans/../.hg/data
418 418 abort: path contains illegal component: .hg/data
419 419 [10]
420 420 $ hg debugwalk -v beans/.hg
421 421 abort: path 'beans/.hg' is inside nested repo 'beans'
422 422 [10]
423 423
424 424 Test explicit paths and excludes:
425 425
426 426 $ hg debugwalk -v fennel -X fennel
427 427 * matcher:
428 428 <differencematcher
429 429 m1=<patternmatcher patterns='fennel(?:/|$)'>,
430 430 m2=<includematcher includes='fennel(?:/|$)'>>
431 431 $ hg debugwalk -v fennel -X 'f*'
432 432 * matcher:
433 433 <differencematcher
434 434 m1=<patternmatcher patterns='fennel(?:/|$)'>,
435 435 m2=<includematcher includes='f[^/]*(?:/|$)'>>
436 436 $ hg debugwalk -v beans/black -X 'path:beans'
437 437 * matcher:
438 438 <differencematcher
439 439 m1=<patternmatcher patterns='beans/black(?:/|$)'>,
440 440 m2=<includematcher includes='beans(?:/|$)'>>
441 441 $ hg debugwalk -v -I 'path:beans/black' -X 'path:beans'
442 442 * matcher:
443 443 <differencematcher
444 444 m1=<includematcher includes='beans/black(?:/|$)'>,
445 445 m2=<includematcher includes='beans(?:/|$)'>>
446 446
447 447 Test absolute paths:
448 448
449 449 $ hg debugwalk -v `pwd`/beans
450 450 * matcher:
451 451 <patternmatcher patterns='beans(?:/|$)'>
452 452 f beans/black beans/black
453 453 f beans/borlotti beans/borlotti
454 454 f beans/kidney beans/kidney
455 455 f beans/navy beans/navy
456 456 f beans/pinto beans/pinto
457 457 f beans/turtle beans/turtle
458 458 $ hg debugwalk -v `pwd`/..
459 459 abort: $TESTTMP/t/.. not under root '$TESTTMP/t'
460 460 [255]
461 461
462 462 Test patterns:
463 463
464 464 $ hg debugwalk -v glob:\*
465 465 * matcher:
466 466 <patternmatcher patterns='[^/]*$'>
467 467 f fennel fennel
468 468 f fenugreek fenugreek
469 469 f fiddlehead fiddlehead
470 470 #if eol-in-paths
471 471 $ echo glob:glob > glob:glob
472 472 $ hg addremove
473 473 adding glob:glob
474 474 warning: filename contains ':', which is reserved on Windows: 'glob:glob'
475 475 $ hg debugwalk -v glob:\*
476 476 * matcher:
477 477 <patternmatcher patterns='[^/]*$'>
478 478 f fennel fennel
479 479 f fenugreek fenugreek
480 480 f fiddlehead fiddlehead
481 481 f glob:glob glob:glob
482 482 $ hg debugwalk -v glob:glob
483 483 * matcher:
484 484 <patternmatcher patterns='glob$'>
485 485 glob: $ENOENT$
486 486 $ hg debugwalk -v glob:glob:glob
487 487 * matcher:
488 488 <patternmatcher patterns='glob:glob$'>
489 489 f glob:glob glob:glob exact
490 490 $ hg debugwalk -v path:glob:glob
491 491 * matcher:
492 492 <patternmatcher patterns='glob:glob(?:/|$)'>
493 493 f glob:glob glob:glob exact
494 494 $ rm glob:glob
495 495 $ hg addremove
496 496 removing glob:glob
497 497 #endif
498 498
499 499 $ hg debugwalk -v 'glob:**e'
500 500 * matcher:
501 501 <patternmatcher patterns='.*e$'>
502 502 f beans/turtle beans/turtle
503 503 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
504 504
505 505 $ hg debugwalk -v 're:.*[kb]$'
506 506 * matcher:
507 507 <patternmatcher patterns='.*[kb]$'>
508 508 f beans/black beans/black
509 509 f fenugreek fenugreek
510 510 f mammals/skunk mammals/skunk
511 511
512 512 $ hg debugwalk -v path:beans/black
513 513 * matcher:
514 514 <patternmatcher patterns='beans/black(?:/|$)'>
515 515 f beans/black beans/black exact
516 516 $ hg debugwalk -v path:beans//black
517 517 * matcher:
518 518 <patternmatcher patterns='beans/black(?:/|$)'>
519 519 f beans/black beans/black exact
520 520
521 521 $ hg debugwalk -v relglob:Procyonidae
522 522 * matcher:
523 523 <patternmatcher patterns='(?:|.*/)Procyonidae$'>
524 524 $ hg debugwalk -v 'relglob:Procyonidae/**'
525 525 * matcher:
526 526 <patternmatcher patterns='(?:|.*/)Procyonidae/.*$'>
527 527 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
528 528 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
529 529 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
530 530 $ hg debugwalk -v 'relglob:Procyonidae/**' fennel
531 531 * matcher:
532 532 <patternmatcher patterns='(?:|.*/)Procyonidae/.*$|fennel(?:/|$)'>
533 533 f fennel fennel exact
534 534 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
535 535 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
536 536 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
537 537 $ hg debugwalk -v beans 'glob:beans/*'
538 538 * matcher:
539 <patternmatcher patterns='beans(?:/|$)|beans/[^/]*$'>
539 <patternmatcher patterns='beans/[^/]*$|beans(?:/|$)'>
540 540 f beans/black beans/black
541 541 f beans/borlotti beans/borlotti
542 542 f beans/kidney beans/kidney
543 543 f beans/navy beans/navy
544 544 f beans/pinto beans/pinto
545 545 f beans/turtle beans/turtle
546 546 $ hg debugwalk -v 'glob:mamm**'
547 547 * matcher:
548 548 <patternmatcher patterns='mamm.*$'>
549 549 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
550 550 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
551 551 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
552 552 f mammals/skunk mammals/skunk
553 553 $ hg debugwalk -v 'glob:mamm**' fennel
554 554 * matcher:
555 555 <patternmatcher patterns='mamm.*$|fennel(?:/|$)'>
556 556 f fennel fennel exact
557 557 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
558 558 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
559 559 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
560 560 f mammals/skunk mammals/skunk
561 561 $ hg debugwalk -v 'glob:j*'
562 562 * matcher:
563 563 <patternmatcher patterns='j[^/]*$'>
564 564 $ hg debugwalk -v NOEXIST
565 565 * matcher:
566 566 <patternmatcher patterns='NOEXIST(?:/|$)'>
567 567 NOEXIST: * (glob)
568 568
569 569 #if fifo
570 570 $ mkfifo fifo
571 571 $ hg debugwalk -v fifo
572 572 * matcher:
573 573 <patternmatcher patterns='fifo(?:/|$)'>
574 574 fifo: unsupported file type (type is fifo)
575 575 #endif
576 576
577 577 $ rm fenugreek
578 578 $ hg debugwalk -v fenugreek
579 579 * matcher:
580 580 <patternmatcher patterns='fenugreek(?:/|$)'>
581 581 f fenugreek fenugreek exact
582 582 $ hg rm fenugreek
583 583 $ hg debugwalk -v fenugreek
584 584 * matcher:
585 585 <patternmatcher patterns='fenugreek(?:/|$)'>
586 586 f fenugreek fenugreek exact
587 587 $ touch new
588 588 $ hg debugwalk -v new
589 589 * matcher:
590 590 <patternmatcher patterns='new(?:/|$)'>
591 591 f new new exact
592 592
593 593 $ mkdir ignored
594 594 $ touch ignored/file
595 595 $ echo '^ignored$' > .hgignore
596 596 $ hg debugwalk -v ignored
597 597 * matcher:
598 598 <patternmatcher patterns='ignored(?:/|$)'>
599 599 $ hg debugwalk -v ignored/file
600 600 * matcher:
601 601 <patternmatcher patterns='ignored/file(?:/|$)'>
602 602 f ignored/file ignored/file exact
603 603
604 604 Test listfile and listfile0
605 605
606 606 $ "$PYTHON" -c "open('listfile0', 'wb').write(b'fenugreek\0new\0')"
607 607 $ hg debugwalk -v -I 'listfile0:listfile0'
608 608 * matcher:
609 609 <includematcher includes='fenugreek(?:/|$)|new(?:/|$)'>
610 610 f fenugreek fenugreek
611 611 f new new
612 612 $ "$PYTHON" -c "open('listfile', 'wb').write(b'fenugreek\nnew\r\nmammals/skunk\n')"
613 613 $ hg debugwalk -v -I 'listfile:listfile'
614 614 * matcher:
615 615 <includematcher includes='fenugreek(?:/|$)|new(?:/|$)|mammals/skunk(?:/|$)'>
616 616 f fenugreek fenugreek
617 617 f mammals/skunk mammals/skunk
618 618 f new new
619 619
620 620 $ cd ..
621 621 $ hg debugwalk -v -R t t/mammals/skunk
622 622 * matcher:
623 623 <patternmatcher patterns='mammals/skunk(?:/|$)'>
624 624 f mammals/skunk t/mammals/skunk exact
625 625 $ mkdir t2
626 626 $ cd t2
627 627 $ hg debugwalk -v -R ../t ../t/mammals/skunk
628 628 * matcher:
629 629 <patternmatcher patterns='mammals/skunk(?:/|$)'>
630 630 f mammals/skunk ../t/mammals/skunk exact
631 631 $ hg debugwalk -v --cwd ../t mammals/skunk
632 632 * matcher:
633 633 <patternmatcher patterns='mammals/skunk(?:/|$)'>
634 634 f mammals/skunk mammals/skunk exact
635 635
636 636 $ cd ..
637 637
638 638 Test split patterns on overflow
639 639
640 640 $ cd t
641 641 $ echo fennel > overflow.list
642 642 $ cat >> printnum.py <<EOF
643 643 > for i in range(20000 // 100):
644 644 > print('x' * 100)
645 645 > EOF
646 646 $ "$PYTHON" printnum.py >> overflow.list
647 647 $ echo fenugreek >> overflow.list
648 648 $ hg debugwalk 'listfile:overflow.list' 2>&1 | egrep -v '^xxx'
649 649 f fennel fennel exact
650 650 f fenugreek fenugreek exact
651 651 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now