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