##// END OF EJS Templates
match: make sure `root` argument is always an absolute path (API)...
Martin von Zweigbergk -
r44401:8b1a9ba3 default
parent child Browse files
Show More
@@ -1,1614 +1,1615
1 1 # match.py - filename matching
2 2 #
3 3 # Copyright 2008, 2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import, print_function
9 9
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('filepatterns')
28 28
29 29 allpatternkinds = (
30 30 b're',
31 31 b'glob',
32 32 b'path',
33 33 b'relglob',
34 34 b'relpath',
35 35 b'relre',
36 36 b'rootglob',
37 37 b'listfile',
38 38 b'listfile0',
39 39 b'set',
40 40 b'include',
41 41 b'subinclude',
42 42 b'rootfilesin',
43 43 )
44 44 cwdrelativepatternkinds = (b'relpath', b'glob')
45 45
46 46 propertycache = util.propertycache
47 47
48 48
49 49 def _rematcher(regex):
50 50 '''compile the regexp with the best available regexp engine and return a
51 51 matcher function'''
52 52 m = util.re.compile(regex)
53 53 try:
54 54 # slightly faster, provided by facebook's re2 bindings
55 55 return m.test_match
56 56 except AttributeError:
57 57 return m.match
58 58
59 59
60 60 def _expandsets(kindpats, ctx=None, listsubrepos=False, badfn=None):
61 61 '''Returns the kindpats list with the 'set' patterns expanded to matchers'''
62 62 matchers = []
63 63 other = []
64 64
65 65 for kind, pat, source in kindpats:
66 66 if kind == b'set':
67 67 if ctx is None:
68 68 raise error.ProgrammingError(
69 69 b"fileset expression with no context"
70 70 )
71 71 matchers.append(ctx.matchfileset(pat, badfn=badfn))
72 72
73 73 if listsubrepos:
74 74 for subpath in ctx.substate:
75 75 sm = ctx.sub(subpath).matchfileset(pat, badfn=badfn)
76 76 pm = prefixdirmatcher(subpath, sm, badfn=badfn)
77 77 matchers.append(pm)
78 78
79 79 continue
80 80 other.append((kind, pat, source))
81 81 return matchers, other
82 82
83 83
84 84 def _expandsubinclude(kindpats, root):
85 85 '''Returns the list of subinclude matcher args and the kindpats without the
86 86 subincludes in it.'''
87 87 relmatchers = []
88 88 other = []
89 89
90 90 for kind, pat, source in kindpats:
91 91 if kind == b'subinclude':
92 92 sourceroot = pathutil.dirname(util.normpath(source))
93 93 pat = util.pconvert(pat)
94 94 path = pathutil.join(sourceroot, pat)
95 95
96 96 newroot = pathutil.dirname(path)
97 97 matcherargs = (newroot, b'', [], [b'include:%s' % path])
98 98
99 99 prefix = pathutil.canonpath(root, root, newroot)
100 100 if prefix:
101 101 prefix += b'/'
102 102 relmatchers.append((prefix, matcherargs))
103 103 else:
104 104 other.append((kind, pat, source))
105 105
106 106 return relmatchers, other
107 107
108 108
109 109 def _kindpatsalwaysmatch(kindpats):
110 110 """"Checks whether the kindspats match everything, as e.g.
111 111 'relpath:.' does.
112 112 """
113 113 for kind, pat, source in kindpats:
114 114 if pat != b'' or kind not in [b'relpath', b'glob']:
115 115 return False
116 116 return True
117 117
118 118
119 119 def _buildkindpatsmatcher(
120 120 matchercls, root, kindpats, ctx=None, listsubrepos=False, badfn=None
121 121 ):
122 122 matchers = []
123 123 fms, kindpats = _expandsets(
124 124 kindpats, ctx=ctx, listsubrepos=listsubrepos, badfn=badfn
125 125 )
126 126 if kindpats:
127 127 m = matchercls(root, kindpats, badfn=badfn)
128 128 matchers.append(m)
129 129 if fms:
130 130 matchers.extend(fms)
131 131 if not matchers:
132 132 return nevermatcher(badfn=badfn)
133 133 if len(matchers) == 1:
134 134 return matchers[0]
135 135 return unionmatcher(matchers)
136 136
137 137
138 138 def match(
139 139 root,
140 140 cwd,
141 141 patterns=None,
142 142 include=None,
143 143 exclude=None,
144 144 default=b'glob',
145 145 auditor=None,
146 146 ctx=None,
147 147 listsubrepos=False,
148 148 warn=None,
149 149 badfn=None,
150 150 icasefs=False,
151 151 ):
152 152 r"""build an object to match a set of file patterns
153 153
154 154 arguments:
155 155 root - the canonical root of the tree you're matching against
156 156 cwd - the current working directory, if relevant
157 157 patterns - patterns to find
158 158 include - patterns to include (unless they are excluded)
159 159 exclude - patterns to exclude (even if they are included)
160 160 default - if a pattern in patterns has no explicit type, assume this one
161 161 auditor - optional path auditor
162 162 ctx - optional changecontext
163 163 listsubrepos - if True, recurse into subrepositories
164 164 warn - optional function used for printing warnings
165 165 badfn - optional bad() callback for this matcher instead of the default
166 166 icasefs - make a matcher for wdir on case insensitive filesystems, which
167 167 normalizes the given patterns to the case in the filesystem
168 168
169 169 a pattern is one of:
170 170 'glob:<glob>' - a glob relative to cwd
171 171 're:<regexp>' - a regular expression
172 172 'path:<path>' - a path relative to repository root, which is matched
173 173 recursively
174 174 'rootfilesin:<path>' - a path relative to repository root, which is
175 175 matched non-recursively (will not match subdirectories)
176 176 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
177 177 'relpath:<path>' - a path relative to cwd
178 178 'relre:<regexp>' - a regexp that needn't match the start of a name
179 179 'set:<fileset>' - a fileset expression
180 180 'include:<path>' - a file of patterns to read and include
181 181 'subinclude:<path>' - a file of patterns to match against files under
182 182 the same directory
183 183 '<something>' - a pattern of the specified default type
184 184
185 185 Usually a patternmatcher is returned:
186 >>> match(b'foo', b'.', [b're:.*\.c$', b'path:foo/a', b'*.py'])
186 >>> match(b'/foo', b'.', [b're:.*\.c$', b'path:foo/a', b'*.py'])
187 187 <patternmatcher patterns='.*\\.c$|foo/a(?:/|$)|[^/]*\\.py$'>
188 188
189 189 Combining 'patterns' with 'include' (resp. 'exclude') gives an
190 190 intersectionmatcher (resp. a differencematcher):
191 >>> type(match(b'foo', b'.', [b're:.*\.c$'], include=[b'path:lib']))
191 >>> type(match(b'/foo', b'.', [b're:.*\.c$'], include=[b'path:lib']))
192 192 <class 'mercurial.match.intersectionmatcher'>
193 >>> type(match(b'foo', b'.', [b're:.*\.c$'], exclude=[b'path:build']))
193 >>> type(match(b'/foo', b'.', [b're:.*\.c$'], exclude=[b'path:build']))
194 194 <class 'mercurial.match.differencematcher'>
195 195
196 196 Notice that, if 'patterns' is empty, an alwaysmatcher is returned:
197 >>> match(b'foo', b'.', [])
197 >>> match(b'/foo', b'.', [])
198 198 <alwaysmatcher>
199 199
200 200 The 'default' argument determines which kind of pattern is assumed if a
201 201 pattern has no prefix:
202 >>> match(b'foo', b'.', [b'.*\.c$'], default=b're')
202 >>> match(b'/foo', b'.', [b'.*\.c$'], default=b're')
203 203 <patternmatcher patterns='.*\\.c$'>
204 >>> match(b'foo', b'.', [b'main.py'], default=b'relpath')
204 >>> match(b'/foo', b'.', [b'main.py'], default=b'relpath')
205 205 <patternmatcher patterns='main\\.py(?:/|$)'>
206 >>> match(b'foo', b'.', [b'main.py'], default=b're')
206 >>> match(b'/foo', b'.', [b'main.py'], default=b're')
207 207 <patternmatcher patterns='main.py'>
208 208
209 209 The primary use of matchers is to check whether a value (usually a file
210 210 name) matches againset one of the patterns given at initialization. There
211 211 are two ways of doing this check.
212 212
213 >>> m = match(b'foo', b'', [b're:.*\.c$', b'relpath:a'])
213 >>> m = match(b'/foo', b'', [b're:.*\.c$', b'relpath:a'])
214 214
215 215 1. Calling the matcher with a file name returns True if any pattern
216 216 matches that file name:
217 217 >>> m(b'a')
218 218 True
219 219 >>> m(b'main.c')
220 220 True
221 221 >>> m(b'test.py')
222 222 False
223 223
224 224 2. Using the exact() method only returns True if the file name matches one
225 225 of the exact patterns (i.e. not re: or glob: patterns):
226 226 >>> m.exact(b'a')
227 227 True
228 228 >>> m.exact(b'main.c')
229 229 False
230 230 """
231 assert os.path.isabs(root)
231 232 normalize = _donormalize
232 233 if icasefs:
233 234 dirstate = ctx.repo().dirstate
234 235 dsnormalize = dirstate.normalize
235 236
236 237 def normalize(patterns, default, root, cwd, auditor, warn):
237 238 kp = _donormalize(patterns, default, root, cwd, auditor, warn)
238 239 kindpats = []
239 240 for kind, pats, source in kp:
240 241 if kind not in (b're', b'relre'): # regex can't be normalized
241 242 p = pats
242 243 pats = dsnormalize(pats)
243 244
244 245 # Preserve the original to handle a case only rename.
245 246 if p != pats and p in dirstate:
246 247 kindpats.append((kind, p, source))
247 248
248 249 kindpats.append((kind, pats, source))
249 250 return kindpats
250 251
251 252 if patterns:
252 253 kindpats = normalize(patterns, default, root, cwd, auditor, warn)
253 254 if _kindpatsalwaysmatch(kindpats):
254 255 m = alwaysmatcher(badfn)
255 256 else:
256 257 m = _buildkindpatsmatcher(
257 258 patternmatcher,
258 259 root,
259 260 kindpats,
260 261 ctx=ctx,
261 262 listsubrepos=listsubrepos,
262 263 badfn=badfn,
263 264 )
264 265 else:
265 266 # It's a little strange that no patterns means to match everything.
266 267 # Consider changing this to match nothing (probably using nevermatcher).
267 268 m = alwaysmatcher(badfn)
268 269
269 270 if include:
270 271 kindpats = normalize(include, b'glob', root, cwd, auditor, warn)
271 272 im = _buildkindpatsmatcher(
272 273 includematcher,
273 274 root,
274 275 kindpats,
275 276 ctx=ctx,
276 277 listsubrepos=listsubrepos,
277 278 badfn=None,
278 279 )
279 280 m = intersectmatchers(m, im)
280 281 if exclude:
281 282 kindpats = normalize(exclude, b'glob', root, cwd, auditor, warn)
282 283 em = _buildkindpatsmatcher(
283 284 includematcher,
284 285 root,
285 286 kindpats,
286 287 ctx=ctx,
287 288 listsubrepos=listsubrepos,
288 289 badfn=None,
289 290 )
290 291 m = differencematcher(m, em)
291 292 return m
292 293
293 294
294 295 def exact(files, badfn=None):
295 296 return exactmatcher(files, badfn=badfn)
296 297
297 298
298 299 def always(badfn=None):
299 300 return alwaysmatcher(badfn)
300 301
301 302
302 303 def never(badfn=None):
303 304 return nevermatcher(badfn)
304 305
305 306
306 307 def badmatch(match, badfn):
307 308 """Make a copy of the given matcher, replacing its bad method with the given
308 309 one.
309 310 """
310 311 m = copy.copy(match)
311 312 m.bad = badfn
312 313 return m
313 314
314 315
315 316 def _donormalize(patterns, default, root, cwd, auditor=None, warn=None):
316 317 '''Convert 'kind:pat' from the patterns list to tuples with kind and
317 318 normalized and rooted patterns and with listfiles expanded.'''
318 319 kindpats = []
319 320 for kind, pat in [_patsplit(p, default) for p in patterns]:
320 321 if kind in cwdrelativepatternkinds:
321 322 pat = pathutil.canonpath(root, cwd, pat, auditor=auditor)
322 323 elif kind in (b'relglob', b'path', b'rootfilesin', b'rootglob'):
323 324 pat = util.normpath(pat)
324 325 elif kind in (b'listfile', b'listfile0'):
325 326 try:
326 327 files = util.readfile(pat)
327 328 if kind == b'listfile0':
328 329 files = files.split(b'\0')
329 330 else:
330 331 files = files.splitlines()
331 332 files = [f for f in files if f]
332 333 except EnvironmentError:
333 334 raise error.Abort(_(b"unable to read file list (%s)") % pat)
334 335 for k, p, source in _donormalize(
335 336 files, default, root, cwd, auditor, warn
336 337 ):
337 338 kindpats.append((k, p, pat))
338 339 continue
339 340 elif kind == b'include':
340 341 try:
341 342 fullpath = os.path.join(root, util.localpath(pat))
342 343 includepats = readpatternfile(fullpath, warn)
343 344 for k, p, source in _donormalize(
344 345 includepats, default, root, cwd, auditor, warn
345 346 ):
346 347 kindpats.append((k, p, source or pat))
347 348 except error.Abort as inst:
348 349 raise error.Abort(
349 350 b'%s: %s'
350 351 % (pat, inst[0]) # pytype: disable=unsupported-operands
351 352 )
352 353 except IOError as inst:
353 354 if warn:
354 355 warn(
355 356 _(b"skipping unreadable pattern file '%s': %s\n")
356 357 % (pat, stringutil.forcebytestr(inst.strerror))
357 358 )
358 359 continue
359 360 # else: re or relre - which cannot be normalized
360 361 kindpats.append((kind, pat, b''))
361 362 return kindpats
362 363
363 364
364 365 class basematcher(object):
365 366 def __init__(self, badfn=None):
366 367 if badfn is not None:
367 368 self.bad = badfn
368 369
369 370 def __call__(self, fn):
370 371 return self.matchfn(fn)
371 372
372 373 # Callbacks related to how the matcher is used by dirstate.walk.
373 374 # Subscribers to these events must monkeypatch the matcher object.
374 375 def bad(self, f, msg):
375 376 '''Callback from dirstate.walk for each explicit file that can't be
376 377 found/accessed, with an error message.'''
377 378
378 379 # If an traversedir is set, it will be called when a directory discovered
379 380 # by recursive traversal is visited.
380 381 traversedir = None
381 382
382 383 @propertycache
383 384 def _files(self):
384 385 return []
385 386
386 387 def files(self):
387 388 '''Explicitly listed files or patterns or roots:
388 389 if no patterns or .always(): empty list,
389 390 if exact: list exact files,
390 391 if not .anypats(): list all files and dirs,
391 392 else: optimal roots'''
392 393 return self._files
393 394
394 395 @propertycache
395 396 def _fileset(self):
396 397 return set(self._files)
397 398
398 399 def exact(self, f):
399 400 '''Returns True if f is in .files().'''
400 401 return f in self._fileset
401 402
402 403 def matchfn(self, f):
403 404 return False
404 405
405 406 def visitdir(self, dir):
406 407 '''Decides whether a directory should be visited based on whether it
407 408 has potential matches in it or one of its subdirectories. This is
408 409 based on the match's primary, included, and excluded patterns.
409 410
410 411 Returns the string 'all' if the given directory and all subdirectories
411 412 should be visited. Otherwise returns True or False indicating whether
412 413 the given directory should be visited.
413 414 '''
414 415 return True
415 416
416 417 def visitchildrenset(self, dir):
417 418 '''Decides whether a directory should be visited based on whether it
418 419 has potential matches in it or one of its subdirectories, and
419 420 potentially lists which subdirectories of that directory should be
420 421 visited. This is based on the match's primary, included, and excluded
421 422 patterns.
422 423
423 424 This function is very similar to 'visitdir', and the following mapping
424 425 can be applied:
425 426
426 427 visitdir | visitchildrenlist
427 428 ----------+-------------------
428 429 False | set()
429 430 'all' | 'all'
430 431 True | 'this' OR non-empty set of subdirs -or files- to visit
431 432
432 433 Example:
433 434 Assume matchers ['path:foo/bar', 'rootfilesin:qux'], we would return
434 435 the following values (assuming the implementation of visitchildrenset
435 436 is capable of recognizing this; some implementations are not).
436 437
437 438 '' -> {'foo', 'qux'}
438 439 'baz' -> set()
439 440 'foo' -> {'bar'}
440 441 # Ideally this would be 'all', but since the prefix nature of matchers
441 442 # is applied to the entire matcher, we have to downgrade this to
442 443 # 'this' due to the non-prefix 'rootfilesin'-kind matcher being mixed
443 444 # in.
444 445 'foo/bar' -> 'this'
445 446 'qux' -> 'this'
446 447
447 448 Important:
448 449 Most matchers do not know if they're representing files or
449 450 directories. They see ['path:dir/f'] and don't know whether 'f' is a
450 451 file or a directory, so visitchildrenset('dir') for most matchers will
451 452 return {'f'}, but if the matcher knows it's a file (like exactmatcher
452 453 does), it may return 'this'. Do not rely on the return being a set
453 454 indicating that there are no files in this dir to investigate (or
454 455 equivalently that if there are files to investigate in 'dir' that it
455 456 will always return 'this').
456 457 '''
457 458 return b'this'
458 459
459 460 def always(self):
460 461 '''Matcher will match everything and .files() will be empty --
461 462 optimization might be possible.'''
462 463 return False
463 464
464 465 def isexact(self):
465 466 '''Matcher will match exactly the list of files in .files() --
466 467 optimization might be possible.'''
467 468 return False
468 469
469 470 def prefix(self):
470 471 '''Matcher will match the paths in .files() recursively --
471 472 optimization might be possible.'''
472 473 return False
473 474
474 475 def anypats(self):
475 476 '''None of .always(), .isexact(), and .prefix() is true --
476 477 optimizations will be difficult.'''
477 478 return not self.always() and not self.isexact() and not self.prefix()
478 479
479 480
480 481 class alwaysmatcher(basematcher):
481 482 '''Matches everything.'''
482 483
483 484 def __init__(self, badfn=None):
484 485 super(alwaysmatcher, self).__init__(badfn)
485 486
486 487 def always(self):
487 488 return True
488 489
489 490 def matchfn(self, f):
490 491 return True
491 492
492 493 def visitdir(self, dir):
493 494 return b'all'
494 495
495 496 def visitchildrenset(self, dir):
496 497 return b'all'
497 498
498 499 def __repr__(self):
499 500 return r'<alwaysmatcher>'
500 501
501 502
502 503 class nevermatcher(basematcher):
503 504 '''Matches nothing.'''
504 505
505 506 def __init__(self, badfn=None):
506 507 super(nevermatcher, self).__init__(badfn)
507 508
508 509 # It's a little weird to say that the nevermatcher is an exact matcher
509 510 # or a prefix matcher, but it seems to make sense to let callers take
510 511 # fast paths based on either. There will be no exact matches, nor any
511 512 # prefixes (files() returns []), so fast paths iterating over them should
512 513 # be efficient (and correct).
513 514 def isexact(self):
514 515 return True
515 516
516 517 def prefix(self):
517 518 return True
518 519
519 520 def visitdir(self, dir):
520 521 return False
521 522
522 523 def visitchildrenset(self, dir):
523 524 return set()
524 525
525 526 def __repr__(self):
526 527 return r'<nevermatcher>'
527 528
528 529
529 530 class predicatematcher(basematcher):
530 531 """A matcher adapter for a simple boolean function"""
531 532
532 533 def __init__(self, predfn, predrepr=None, badfn=None):
533 534 super(predicatematcher, self).__init__(badfn)
534 535 self.matchfn = predfn
535 536 self._predrepr = predrepr
536 537
537 538 @encoding.strmethod
538 539 def __repr__(self):
539 540 s = stringutil.buildrepr(self._predrepr) or pycompat.byterepr(
540 541 self.matchfn
541 542 )
542 543 return b'<predicatenmatcher pred=%s>' % s
543 544
544 545
545 546 class patternmatcher(basematcher):
546 547 r"""Matches a set of (kind, pat, source) against a 'root' directory.
547 548
548 549 >>> kindpats = [
549 550 ... (b're', br'.*\.c$', b''),
550 551 ... (b'path', b'foo/a', b''),
551 552 ... (b'relpath', b'b', b''),
552 553 ... (b'glob', b'*.h', b''),
553 554 ... ]
554 555 >>> m = patternmatcher(b'foo', kindpats)
555 556 >>> m(b'main.c') # matches re:.*\.c$
556 557 True
557 558 >>> m(b'b.txt')
558 559 False
559 560 >>> m(b'foo/a') # matches path:foo/a
560 561 True
561 562 >>> m(b'a') # does not match path:b, since 'root' is 'foo'
562 563 False
563 564 >>> m(b'b') # matches relpath:b, since 'root' is 'foo'
564 565 True
565 566 >>> m(b'lib.h') # matches glob:*.h
566 567 True
567 568
568 569 >>> m.files()
569 570 ['', 'foo/a', 'b', '']
570 571 >>> m.exact(b'foo/a')
571 572 True
572 573 >>> m.exact(b'b')
573 574 True
574 575 >>> m.exact(b'lib.h') # exact matches are for (rel)path kinds
575 576 False
576 577 """
577 578
578 579 def __init__(self, root, kindpats, badfn=None):
579 580 super(patternmatcher, self).__init__(badfn)
580 581
581 582 self._files = _explicitfiles(kindpats)
582 583 self._prefix = _prefix(kindpats)
583 584 self._pats, self.matchfn = _buildmatch(kindpats, b'$', root)
584 585
585 586 @propertycache
586 587 def _dirs(self):
587 588 return set(pathutil.dirs(self._fileset))
588 589
589 590 def visitdir(self, dir):
590 591 if self._prefix and dir in self._fileset:
591 592 return b'all'
592 593 return (
593 594 dir in self._fileset
594 595 or dir in self._dirs
595 596 or any(
596 597 parentdir in self._fileset
597 598 for parentdir in pathutil.finddirs(dir)
598 599 )
599 600 )
600 601
601 602 def visitchildrenset(self, dir):
602 603 ret = self.visitdir(dir)
603 604 if ret is True:
604 605 return b'this'
605 606 elif not ret:
606 607 return set()
607 608 assert ret == b'all'
608 609 return b'all'
609 610
610 611 def prefix(self):
611 612 return self._prefix
612 613
613 614 @encoding.strmethod
614 615 def __repr__(self):
615 616 return b'<patternmatcher patterns=%r>' % pycompat.bytestr(self._pats)
616 617
617 618
618 619 # This is basically a reimplementation of pathutil.dirs that stores the
619 620 # children instead of just a count of them, plus a small optional optimization
620 621 # to avoid some directories we don't need.
621 622 class _dirchildren(object):
622 623 def __init__(self, paths, onlyinclude=None):
623 624 self._dirs = {}
624 625 self._onlyinclude = onlyinclude or []
625 626 addpath = self.addpath
626 627 for f in paths:
627 628 addpath(f)
628 629
629 630 def addpath(self, path):
630 631 if path == b'':
631 632 return
632 633 dirs = self._dirs
633 634 findsplitdirs = _dirchildren._findsplitdirs
634 635 for d, b in findsplitdirs(path):
635 636 if d not in self._onlyinclude:
636 637 continue
637 638 dirs.setdefault(d, set()).add(b)
638 639
639 640 @staticmethod
640 641 def _findsplitdirs(path):
641 642 # yields (dirname, basename) tuples, walking back to the root. This is
642 643 # very similar to pathutil.finddirs, except:
643 644 # - produces a (dirname, basename) tuple, not just 'dirname'
644 645 # Unlike manifest._splittopdir, this does not suffix `dirname` with a
645 646 # slash.
646 647 oldpos = len(path)
647 648 pos = path.rfind(b'/')
648 649 while pos != -1:
649 650 yield path[:pos], path[pos + 1 : oldpos]
650 651 oldpos = pos
651 652 pos = path.rfind(b'/', 0, pos)
652 653 yield b'', path[:oldpos]
653 654
654 655 def get(self, path):
655 656 return self._dirs.get(path, set())
656 657
657 658
658 659 class includematcher(basematcher):
659 660 def __init__(self, root, kindpats, badfn=None):
660 661 super(includematcher, self).__init__(badfn)
661 662
662 663 self._pats, self.matchfn = _buildmatch(kindpats, b'(?:/|$)', root)
663 664 self._prefix = _prefix(kindpats)
664 665 roots, dirs, parents = _rootsdirsandparents(kindpats)
665 666 # roots are directories which are recursively included.
666 667 self._roots = set(roots)
667 668 # dirs are directories which are non-recursively included.
668 669 self._dirs = set(dirs)
669 670 # parents are directories which are non-recursively included because
670 671 # they are needed to get to items in _dirs or _roots.
671 672 self._parents = parents
672 673
673 674 def visitdir(self, dir):
674 675 if self._prefix and dir in self._roots:
675 676 return b'all'
676 677 return (
677 678 dir in self._roots
678 679 or dir in self._dirs
679 680 or dir in self._parents
680 681 or any(
681 682 parentdir in self._roots for parentdir in pathutil.finddirs(dir)
682 683 )
683 684 )
684 685
685 686 @propertycache
686 687 def _allparentschildren(self):
687 688 # It may seem odd that we add dirs, roots, and parents, and then
688 689 # restrict to only parents. This is to catch the case of:
689 690 # dirs = ['foo/bar']
690 691 # parents = ['foo']
691 692 # if we asked for the children of 'foo', but had only added
692 693 # self._parents, we wouldn't be able to respond ['bar'].
693 694 return _dirchildren(
694 695 itertools.chain(self._dirs, self._roots, self._parents),
695 696 onlyinclude=self._parents,
696 697 )
697 698
698 699 def visitchildrenset(self, dir):
699 700 if self._prefix and dir in self._roots:
700 701 return b'all'
701 702 # Note: this does *not* include the 'dir in self._parents' case from
702 703 # visitdir, that's handled below.
703 704 if (
704 705 b'' in self._roots
705 706 or dir in self._roots
706 707 or dir in self._dirs
707 708 or any(
708 709 parentdir in self._roots for parentdir in pathutil.finddirs(dir)
709 710 )
710 711 ):
711 712 return b'this'
712 713
713 714 if dir in self._parents:
714 715 return self._allparentschildren.get(dir) or set()
715 716 return set()
716 717
717 718 @encoding.strmethod
718 719 def __repr__(self):
719 720 return b'<includematcher includes=%r>' % pycompat.bytestr(self._pats)
720 721
721 722
722 723 class exactmatcher(basematcher):
723 724 r'''Matches the input files exactly. They are interpreted as paths, not
724 725 patterns (so no kind-prefixes).
725 726
726 727 >>> m = exactmatcher([b'a.txt', br're:.*\.c$'])
727 728 >>> m(b'a.txt')
728 729 True
729 730 >>> m(b'b.txt')
730 731 False
731 732
732 733 Input files that would be matched are exactly those returned by .files()
733 734 >>> m.files()
734 735 ['a.txt', 're:.*\\.c$']
735 736
736 737 So pattern 're:.*\.c$' is not considered as a regex, but as a file name
737 738 >>> m(b'main.c')
738 739 False
739 740 >>> m(br're:.*\.c$')
740 741 True
741 742 '''
742 743
743 744 def __init__(self, files, badfn=None):
744 745 super(exactmatcher, self).__init__(badfn)
745 746
746 747 if isinstance(files, list):
747 748 self._files = files
748 749 else:
749 750 self._files = list(files)
750 751
751 752 matchfn = basematcher.exact
752 753
753 754 @propertycache
754 755 def _dirs(self):
755 756 return set(pathutil.dirs(self._fileset))
756 757
757 758 def visitdir(self, dir):
758 759 return dir in self._dirs
759 760
760 761 def visitchildrenset(self, dir):
761 762 if not self._fileset or dir not in self._dirs:
762 763 return set()
763 764
764 765 candidates = self._fileset | self._dirs - {b''}
765 766 if dir != b'':
766 767 d = dir + b'/'
767 768 candidates = set(c[len(d) :] for c in candidates if c.startswith(d))
768 769 # self._dirs includes all of the directories, recursively, so if
769 770 # we're attempting to match foo/bar/baz.txt, it'll have '', 'foo',
770 771 # 'foo/bar' in it. Thus we can safely ignore a candidate that has a
771 772 # '/' in it, indicating a it's for a subdir-of-a-subdir; the
772 773 # immediate subdir will be in there without a slash.
773 774 ret = {c for c in candidates if b'/' not in c}
774 775 # We really do not expect ret to be empty, since that would imply that
775 776 # there's something in _dirs that didn't have a file in _fileset.
776 777 assert ret
777 778 return ret
778 779
779 780 def isexact(self):
780 781 return True
781 782
782 783 @encoding.strmethod
783 784 def __repr__(self):
784 785 return b'<exactmatcher files=%r>' % self._files
785 786
786 787
787 788 class differencematcher(basematcher):
788 789 '''Composes two matchers by matching if the first matches and the second
789 790 does not.
790 791
791 792 The second matcher's non-matching-attributes (bad, traversedir) are ignored.
792 793 '''
793 794
794 795 def __init__(self, m1, m2):
795 796 super(differencematcher, self).__init__()
796 797 self._m1 = m1
797 798 self._m2 = m2
798 799 self.bad = m1.bad
799 800 self.traversedir = m1.traversedir
800 801
801 802 def matchfn(self, f):
802 803 return self._m1(f) and not self._m2(f)
803 804
804 805 @propertycache
805 806 def _files(self):
806 807 if self.isexact():
807 808 return [f for f in self._m1.files() if self(f)]
808 809 # If m1 is not an exact matcher, we can't easily figure out the set of
809 810 # files, because its files() are not always files. For example, if
810 811 # m1 is "path:dir" and m2 is "rootfileins:.", we don't
811 812 # want to remove "dir" from the set even though it would match m2,
812 813 # because the "dir" in m1 may not be a file.
813 814 return self._m1.files()
814 815
815 816 def visitdir(self, dir):
816 817 if self._m2.visitdir(dir) == b'all':
817 818 return False
818 819 elif not self._m2.visitdir(dir):
819 820 # m2 does not match dir, we can return 'all' here if possible
820 821 return self._m1.visitdir(dir)
821 822 return bool(self._m1.visitdir(dir))
822 823
823 824 def visitchildrenset(self, dir):
824 825 m2_set = self._m2.visitchildrenset(dir)
825 826 if m2_set == b'all':
826 827 return set()
827 828 m1_set = self._m1.visitchildrenset(dir)
828 829 # Possible values for m1: 'all', 'this', set(...), set()
829 830 # Possible values for m2: 'this', set(...), set()
830 831 # If m2 has nothing under here that we care about, return m1, even if
831 832 # it's 'all'. This is a change in behavior from visitdir, which would
832 833 # return True, not 'all', for some reason.
833 834 if not m2_set:
834 835 return m1_set
835 836 if m1_set in [b'all', b'this']:
836 837 # Never return 'all' here if m2_set is any kind of non-empty (either
837 838 # 'this' or set(foo)), since m2 might return set() for a
838 839 # subdirectory.
839 840 return b'this'
840 841 # Possible values for m1: set(...), set()
841 842 # Possible values for m2: 'this', set(...)
842 843 # We ignore m2's set results. They're possibly incorrect:
843 844 # m1 = path:dir/subdir, m2=rootfilesin:dir, visitchildrenset(''):
844 845 # m1 returns {'dir'}, m2 returns {'dir'}, if we subtracted we'd
845 846 # return set(), which is *not* correct, we still need to visit 'dir'!
846 847 return m1_set
847 848
848 849 def isexact(self):
849 850 return self._m1.isexact()
850 851
851 852 @encoding.strmethod
852 853 def __repr__(self):
853 854 return b'<differencematcher m1=%r, m2=%r>' % (self._m1, self._m2)
854 855
855 856
856 857 def intersectmatchers(m1, m2):
857 858 '''Composes two matchers by matching if both of them match.
858 859
859 860 The second matcher's non-matching-attributes (bad, traversedir) are ignored.
860 861 '''
861 862 if m1 is None or m2 is None:
862 863 return m1 or m2
863 864 if m1.always():
864 865 m = copy.copy(m2)
865 866 # TODO: Consider encapsulating these things in a class so there's only
866 867 # one thing to copy from m1.
867 868 m.bad = m1.bad
868 869 m.traversedir = m1.traversedir
869 870 return m
870 871 if m2.always():
871 872 m = copy.copy(m1)
872 873 return m
873 874 return intersectionmatcher(m1, m2)
874 875
875 876
876 877 class intersectionmatcher(basematcher):
877 878 def __init__(self, m1, m2):
878 879 super(intersectionmatcher, self).__init__()
879 880 self._m1 = m1
880 881 self._m2 = m2
881 882 self.bad = m1.bad
882 883 self.traversedir = m1.traversedir
883 884
884 885 @propertycache
885 886 def _files(self):
886 887 if self.isexact():
887 888 m1, m2 = self._m1, self._m2
888 889 if not m1.isexact():
889 890 m1, m2 = m2, m1
890 891 return [f for f in m1.files() if m2(f)]
891 892 # It neither m1 nor m2 is an exact matcher, we can't easily intersect
892 893 # the set of files, because their files() are not always files. For
893 894 # example, if intersecting a matcher "-I glob:foo.txt" with matcher of
894 895 # "path:dir2", we don't want to remove "dir2" from the set.
895 896 return self._m1.files() + self._m2.files()
896 897
897 898 def matchfn(self, f):
898 899 return self._m1(f) and self._m2(f)
899 900
900 901 def visitdir(self, dir):
901 902 visit1 = self._m1.visitdir(dir)
902 903 if visit1 == b'all':
903 904 return self._m2.visitdir(dir)
904 905 # bool() because visit1=True + visit2='all' should not be 'all'
905 906 return bool(visit1 and self._m2.visitdir(dir))
906 907
907 908 def visitchildrenset(self, dir):
908 909 m1_set = self._m1.visitchildrenset(dir)
909 910 if not m1_set:
910 911 return set()
911 912 m2_set = self._m2.visitchildrenset(dir)
912 913 if not m2_set:
913 914 return set()
914 915
915 916 if m1_set == b'all':
916 917 return m2_set
917 918 elif m2_set == b'all':
918 919 return m1_set
919 920
920 921 if m1_set == b'this' or m2_set == b'this':
921 922 return b'this'
922 923
923 924 assert isinstance(m1_set, set) and isinstance(m2_set, set)
924 925 return m1_set.intersection(m2_set)
925 926
926 927 def always(self):
927 928 return self._m1.always() and self._m2.always()
928 929
929 930 def isexact(self):
930 931 return self._m1.isexact() or self._m2.isexact()
931 932
932 933 @encoding.strmethod
933 934 def __repr__(self):
934 935 return b'<intersectionmatcher m1=%r, m2=%r>' % (self._m1, self._m2)
935 936
936 937
937 938 class subdirmatcher(basematcher):
938 939 """Adapt a matcher to work on a subdirectory only.
939 940
940 941 The paths are remapped to remove/insert the path as needed:
941 942
942 943 >>> from . import pycompat
943 >>> m1 = match(b'root', b'', [b'a.txt', b'sub/b.txt'])
944 >>> m1 = match(b'/root', b'', [b'a.txt', b'sub/b.txt'])
944 945 >>> m2 = subdirmatcher(b'sub', m1)
945 946 >>> m2(b'a.txt')
946 947 False
947 948 >>> m2(b'b.txt')
948 949 True
949 950 >>> m2.matchfn(b'a.txt')
950 951 False
951 952 >>> m2.matchfn(b'b.txt')
952 953 True
953 954 >>> m2.files()
954 955 ['b.txt']
955 956 >>> m2.exact(b'b.txt')
956 957 True
957 958 >>> def bad(f, msg):
958 959 ... print(pycompat.sysstr(b"%s: %s" % (f, msg)))
959 960 >>> m1.bad = bad
960 961 >>> m2.bad(b'x.txt', b'No such file')
961 962 sub/x.txt: No such file
962 963 """
963 964
964 965 def __init__(self, path, matcher):
965 966 super(subdirmatcher, self).__init__()
966 967 self._path = path
967 968 self._matcher = matcher
968 969 self._always = matcher.always()
969 970
970 971 self._files = [
971 972 f[len(path) + 1 :]
972 973 for f in matcher._files
973 974 if f.startswith(path + b"/")
974 975 ]
975 976
976 977 # If the parent repo had a path to this subrepo and the matcher is
977 978 # a prefix matcher, this submatcher always matches.
978 979 if matcher.prefix():
979 980 self._always = any(f == path for f in matcher._files)
980 981
981 982 def bad(self, f, msg):
982 983 self._matcher.bad(self._path + b"/" + f, msg)
983 984
984 985 def matchfn(self, f):
985 986 # Some information is lost in the superclass's constructor, so we
986 987 # can not accurately create the matching function for the subdirectory
987 988 # from the inputs. Instead, we override matchfn() and visitdir() to
988 989 # call the original matcher with the subdirectory path prepended.
989 990 return self._matcher.matchfn(self._path + b"/" + f)
990 991
991 992 def visitdir(self, dir):
992 993 if dir == b'':
993 994 dir = self._path
994 995 else:
995 996 dir = self._path + b"/" + dir
996 997 return self._matcher.visitdir(dir)
997 998
998 999 def visitchildrenset(self, dir):
999 1000 if dir == b'':
1000 1001 dir = self._path
1001 1002 else:
1002 1003 dir = self._path + b"/" + dir
1003 1004 return self._matcher.visitchildrenset(dir)
1004 1005
1005 1006 def always(self):
1006 1007 return self._always
1007 1008
1008 1009 def prefix(self):
1009 1010 return self._matcher.prefix() and not self._always
1010 1011
1011 1012 @encoding.strmethod
1012 1013 def __repr__(self):
1013 1014 return b'<subdirmatcher path=%r, matcher=%r>' % (
1014 1015 self._path,
1015 1016 self._matcher,
1016 1017 )
1017 1018
1018 1019
1019 1020 class prefixdirmatcher(basematcher):
1020 1021 """Adapt a matcher to work on a parent directory.
1021 1022
1022 1023 The matcher's non-matching-attributes (bad, traversedir) are ignored.
1023 1024
1024 1025 The prefix path should usually be the relative path from the root of
1025 1026 this matcher to the root of the wrapped matcher.
1026 1027
1027 >>> m1 = match(util.localpath(b'root/d/e'), b'f', [b'../a.txt', b'b.txt'])
1028 >>> m1 = match(util.localpath(b'/root/d/e'), b'f', [b'../a.txt', b'b.txt'], auditor=lambda name: None)
1028 1029 >>> m2 = prefixdirmatcher(b'd/e', m1)
1029 1030 >>> m2(b'a.txt')
1030 1031 False
1031 1032 >>> m2(b'd/e/a.txt')
1032 1033 True
1033 1034 >>> m2(b'd/e/b.txt')
1034 1035 False
1035 1036 >>> m2.files()
1036 1037 ['d/e/a.txt', 'd/e/f/b.txt']
1037 1038 >>> m2.exact(b'd/e/a.txt')
1038 1039 True
1039 1040 >>> m2.visitdir(b'd')
1040 1041 True
1041 1042 >>> m2.visitdir(b'd/e')
1042 1043 True
1043 1044 >>> m2.visitdir(b'd/e/f')
1044 1045 True
1045 1046 >>> m2.visitdir(b'd/e/g')
1046 1047 False
1047 1048 >>> m2.visitdir(b'd/ef')
1048 1049 False
1049 1050 """
1050 1051
1051 1052 def __init__(self, path, matcher, badfn=None):
1052 1053 super(prefixdirmatcher, self).__init__(badfn)
1053 1054 if not path:
1054 1055 raise error.ProgrammingError(b'prefix path must not be empty')
1055 1056 self._path = path
1056 1057 self._pathprefix = path + b'/'
1057 1058 self._matcher = matcher
1058 1059
1059 1060 @propertycache
1060 1061 def _files(self):
1061 1062 return [self._pathprefix + f for f in self._matcher._files]
1062 1063
1063 1064 def matchfn(self, f):
1064 1065 if not f.startswith(self._pathprefix):
1065 1066 return False
1066 1067 return self._matcher.matchfn(f[len(self._pathprefix) :])
1067 1068
1068 1069 @propertycache
1069 1070 def _pathdirs(self):
1070 1071 return set(pathutil.finddirs(self._path))
1071 1072
1072 1073 def visitdir(self, dir):
1073 1074 if dir == self._path:
1074 1075 return self._matcher.visitdir(b'')
1075 1076 if dir.startswith(self._pathprefix):
1076 1077 return self._matcher.visitdir(dir[len(self._pathprefix) :])
1077 1078 return dir in self._pathdirs
1078 1079
1079 1080 def visitchildrenset(self, dir):
1080 1081 if dir == self._path:
1081 1082 return self._matcher.visitchildrenset(b'')
1082 1083 if dir.startswith(self._pathprefix):
1083 1084 return self._matcher.visitchildrenset(dir[len(self._pathprefix) :])
1084 1085 if dir in self._pathdirs:
1085 1086 return b'this'
1086 1087 return set()
1087 1088
1088 1089 def isexact(self):
1089 1090 return self._matcher.isexact()
1090 1091
1091 1092 def prefix(self):
1092 1093 return self._matcher.prefix()
1093 1094
1094 1095 @encoding.strmethod
1095 1096 def __repr__(self):
1096 1097 return b'<prefixdirmatcher path=%r, matcher=%r>' % (
1097 1098 pycompat.bytestr(self._path),
1098 1099 self._matcher,
1099 1100 )
1100 1101
1101 1102
1102 1103 class unionmatcher(basematcher):
1103 1104 """A matcher that is the union of several matchers.
1104 1105
1105 1106 The non-matching-attributes (bad, traversedir) are taken from the first
1106 1107 matcher.
1107 1108 """
1108 1109
1109 1110 def __init__(self, matchers):
1110 1111 m1 = matchers[0]
1111 1112 super(unionmatcher, self).__init__()
1112 1113 self.traversedir = m1.traversedir
1113 1114 self._matchers = matchers
1114 1115
1115 1116 def matchfn(self, f):
1116 1117 for match in self._matchers:
1117 1118 if match(f):
1118 1119 return True
1119 1120 return False
1120 1121
1121 1122 def visitdir(self, dir):
1122 1123 r = False
1123 1124 for m in self._matchers:
1124 1125 v = m.visitdir(dir)
1125 1126 if v == b'all':
1126 1127 return v
1127 1128 r |= v
1128 1129 return r
1129 1130
1130 1131 def visitchildrenset(self, dir):
1131 1132 r = set()
1132 1133 this = False
1133 1134 for m in self._matchers:
1134 1135 v = m.visitchildrenset(dir)
1135 1136 if not v:
1136 1137 continue
1137 1138 if v == b'all':
1138 1139 return v
1139 1140 if this or v == b'this':
1140 1141 this = True
1141 1142 # don't break, we might have an 'all' in here.
1142 1143 continue
1143 1144 assert isinstance(v, set)
1144 1145 r = r.union(v)
1145 1146 if this:
1146 1147 return b'this'
1147 1148 return r
1148 1149
1149 1150 @encoding.strmethod
1150 1151 def __repr__(self):
1151 1152 return b'<unionmatcher matchers=%r>' % self._matchers
1152 1153
1153 1154
1154 1155 def patkind(pattern, default=None):
1155 1156 r'''If pattern is 'kind:pat' with a known kind, return kind.
1156 1157
1157 1158 >>> patkind(br're:.*\.c$')
1158 1159 're'
1159 1160 >>> patkind(b'glob:*.c')
1160 1161 'glob'
1161 1162 >>> patkind(b'relpath:test.py')
1162 1163 'relpath'
1163 1164 >>> patkind(b'main.py')
1164 1165 >>> patkind(b'main.py', default=b're')
1165 1166 're'
1166 1167 '''
1167 1168 return _patsplit(pattern, default)[0]
1168 1169
1169 1170
1170 1171 def _patsplit(pattern, default):
1171 1172 """Split a string into the optional pattern kind prefix and the actual
1172 1173 pattern."""
1173 1174 if b':' in pattern:
1174 1175 kind, pat = pattern.split(b':', 1)
1175 1176 if kind in allpatternkinds:
1176 1177 return kind, pat
1177 1178 return default, pattern
1178 1179
1179 1180
1180 1181 def _globre(pat):
1181 1182 r'''Convert an extended glob string to a regexp string.
1182 1183
1183 1184 >>> from . import pycompat
1184 1185 >>> def bprint(s):
1185 1186 ... print(pycompat.sysstr(s))
1186 1187 >>> bprint(_globre(br'?'))
1187 1188 .
1188 1189 >>> bprint(_globre(br'*'))
1189 1190 [^/]*
1190 1191 >>> bprint(_globre(br'**'))
1191 1192 .*
1192 1193 >>> bprint(_globre(br'**/a'))
1193 1194 (?:.*/)?a
1194 1195 >>> bprint(_globre(br'a/**/b'))
1195 1196 a/(?:.*/)?b
1196 1197 >>> bprint(_globre(br'[a*?!^][^b][!c]'))
1197 1198 [a*?!^][\^b][^c]
1198 1199 >>> bprint(_globre(br'{a,b}'))
1199 1200 (?:a|b)
1200 1201 >>> bprint(_globre(br'.\*\?'))
1201 1202 \.\*\?
1202 1203 '''
1203 1204 i, n = 0, len(pat)
1204 1205 res = b''
1205 1206 group = 0
1206 1207 escape = util.stringutil.regexbytesescapemap.get
1207 1208
1208 1209 def peek():
1209 1210 return i < n and pat[i : i + 1]
1210 1211
1211 1212 while i < n:
1212 1213 c = pat[i : i + 1]
1213 1214 i += 1
1214 1215 if c not in b'*?[{},\\':
1215 1216 res += escape(c, c)
1216 1217 elif c == b'*':
1217 1218 if peek() == b'*':
1218 1219 i += 1
1219 1220 if peek() == b'/':
1220 1221 i += 1
1221 1222 res += b'(?:.*/)?'
1222 1223 else:
1223 1224 res += b'.*'
1224 1225 else:
1225 1226 res += b'[^/]*'
1226 1227 elif c == b'?':
1227 1228 res += b'.'
1228 1229 elif c == b'[':
1229 1230 j = i
1230 1231 if j < n and pat[j : j + 1] in b'!]':
1231 1232 j += 1
1232 1233 while j < n and pat[j : j + 1] != b']':
1233 1234 j += 1
1234 1235 if j >= n:
1235 1236 res += b'\\['
1236 1237 else:
1237 1238 stuff = pat[i:j].replace(b'\\', b'\\\\')
1238 1239 i = j + 1
1239 1240 if stuff[0:1] == b'!':
1240 1241 stuff = b'^' + stuff[1:]
1241 1242 elif stuff[0:1] == b'^':
1242 1243 stuff = b'\\' + stuff
1243 1244 res = b'%s[%s]' % (res, stuff)
1244 1245 elif c == b'{':
1245 1246 group += 1
1246 1247 res += b'(?:'
1247 1248 elif c == b'}' and group:
1248 1249 res += b')'
1249 1250 group -= 1
1250 1251 elif c == b',' and group:
1251 1252 res += b'|'
1252 1253 elif c == b'\\':
1253 1254 p = peek()
1254 1255 if p:
1255 1256 i += 1
1256 1257 res += escape(p, p)
1257 1258 else:
1258 1259 res += escape(c, c)
1259 1260 else:
1260 1261 res += escape(c, c)
1261 1262 return res
1262 1263
1263 1264
1264 1265 def _regex(kind, pat, globsuffix):
1265 1266 '''Convert a (normalized) pattern of any kind into a
1266 1267 regular expression.
1267 1268 globsuffix is appended to the regexp of globs.'''
1268 1269
1269 1270 if rustmod is not None:
1270 1271 try:
1271 1272 return rustmod.build_single_regex(kind, pat, globsuffix)
1272 1273 except rustmod.PatternError:
1273 1274 raise error.ProgrammingError(
1274 1275 b'not a regex pattern: %s:%s' % (kind, pat)
1275 1276 )
1276 1277
1277 1278 if not pat and kind in (b'glob', b'relpath'):
1278 1279 return b''
1279 1280 if kind == b're':
1280 1281 return pat
1281 1282 if kind in (b'path', b'relpath'):
1282 1283 if pat == b'.':
1283 1284 return b''
1284 1285 return util.stringutil.reescape(pat) + b'(?:/|$)'
1285 1286 if kind == b'rootfilesin':
1286 1287 if pat == b'.':
1287 1288 escaped = b''
1288 1289 else:
1289 1290 # Pattern is a directory name.
1290 1291 escaped = util.stringutil.reescape(pat) + b'/'
1291 1292 # Anything after the pattern must be a non-directory.
1292 1293 return escaped + b'[^/]+$'
1293 1294 if kind == b'relglob':
1294 1295 globre = _globre(pat)
1295 1296 if globre.startswith(b'[^/]*'):
1296 1297 # When pat has the form *XYZ (common), make the returned regex more
1297 1298 # legible by returning the regex for **XYZ instead of **/*XYZ.
1298 1299 return b'.*' + globre[len(b'[^/]*') :] + globsuffix
1299 1300 return b'(?:|.*/)' + globre + globsuffix
1300 1301 if kind == b'relre':
1301 1302 if pat.startswith(b'^'):
1302 1303 return pat
1303 1304 return b'.*' + pat
1304 1305 if kind in (b'glob', b'rootglob'):
1305 1306 return _globre(pat) + globsuffix
1306 1307 raise error.ProgrammingError(b'not a regex pattern: %s:%s' % (kind, pat))
1307 1308
1308 1309
1309 1310 def _buildmatch(kindpats, globsuffix, root):
1310 1311 '''Return regexp string and a matcher function for kindpats.
1311 1312 globsuffix is appended to the regexp of globs.'''
1312 1313 matchfuncs = []
1313 1314
1314 1315 subincludes, kindpats = _expandsubinclude(kindpats, root)
1315 1316 if subincludes:
1316 1317 submatchers = {}
1317 1318
1318 1319 def matchsubinclude(f):
1319 1320 for prefix, matcherargs in subincludes:
1320 1321 if f.startswith(prefix):
1321 1322 mf = submatchers.get(prefix)
1322 1323 if mf is None:
1323 1324 mf = match(*matcherargs)
1324 1325 submatchers[prefix] = mf
1325 1326
1326 1327 if mf(f[len(prefix) :]):
1327 1328 return True
1328 1329 return False
1329 1330
1330 1331 matchfuncs.append(matchsubinclude)
1331 1332
1332 1333 regex = b''
1333 1334 if kindpats:
1334 1335 if all(k == b'rootfilesin' for k, p, s in kindpats):
1335 1336 dirs = {p for k, p, s in kindpats}
1336 1337
1337 1338 def mf(f):
1338 1339 i = f.rfind(b'/')
1339 1340 if i >= 0:
1340 1341 dir = f[:i]
1341 1342 else:
1342 1343 dir = b'.'
1343 1344 return dir in dirs
1344 1345
1345 1346 regex = b'rootfilesin: %s' % stringutil.pprint(list(sorted(dirs)))
1346 1347 matchfuncs.append(mf)
1347 1348 else:
1348 1349 regex, mf = _buildregexmatch(kindpats, globsuffix)
1349 1350 matchfuncs.append(mf)
1350 1351
1351 1352 if len(matchfuncs) == 1:
1352 1353 return regex, matchfuncs[0]
1353 1354 else:
1354 1355 return regex, lambda f: any(mf(f) for mf in matchfuncs)
1355 1356
1356 1357
1357 1358 MAX_RE_SIZE = 20000
1358 1359
1359 1360
1360 1361 def _joinregexes(regexps):
1361 1362 """gather multiple regular expressions into a single one"""
1362 1363 return b'|'.join(regexps)
1363 1364
1364 1365
1365 1366 def _buildregexmatch(kindpats, globsuffix):
1366 1367 """Build a match function from a list of kinds and kindpats,
1367 1368 return regexp string and a matcher function.
1368 1369
1369 1370 Test too large input
1370 1371 >>> _buildregexmatch([
1371 1372 ... (b'relglob', b'?' * MAX_RE_SIZE, b'')
1372 1373 ... ], b'$')
1373 1374 Traceback (most recent call last):
1374 1375 ...
1375 1376 Abort: matcher pattern is too long (20009 bytes)
1376 1377 """
1377 1378 try:
1378 1379 allgroups = []
1379 1380 regexps = [_regex(k, p, globsuffix) for (k, p, s) in kindpats]
1380 1381 fullregexp = _joinregexes(regexps)
1381 1382
1382 1383 startidx = 0
1383 1384 groupsize = 0
1384 1385 for idx, r in enumerate(regexps):
1385 1386 piecesize = len(r)
1386 1387 if piecesize > MAX_RE_SIZE:
1387 1388 msg = _(b"matcher pattern is too long (%d bytes)") % piecesize
1388 1389 raise error.Abort(msg)
1389 1390 elif (groupsize + piecesize) > MAX_RE_SIZE:
1390 1391 group = regexps[startidx:idx]
1391 1392 allgroups.append(_joinregexes(group))
1392 1393 startidx = idx
1393 1394 groupsize = 0
1394 1395 groupsize += piecesize + 1
1395 1396
1396 1397 if startidx == 0:
1397 1398 matcher = _rematcher(fullregexp)
1398 1399 func = lambda s: bool(matcher(s))
1399 1400 else:
1400 1401 group = regexps[startidx:]
1401 1402 allgroups.append(_joinregexes(group))
1402 1403 allmatchers = [_rematcher(g) for g in allgroups]
1403 1404 func = lambda s: any(m(s) for m in allmatchers)
1404 1405 return fullregexp, func
1405 1406 except re.error:
1406 1407 for k, p, s in kindpats:
1407 1408 try:
1408 1409 _rematcher(_regex(k, p, globsuffix))
1409 1410 except re.error:
1410 1411 if s:
1411 1412 raise error.Abort(
1412 1413 _(b"%s: invalid pattern (%s): %s") % (s, k, p)
1413 1414 )
1414 1415 else:
1415 1416 raise error.Abort(_(b"invalid pattern (%s): %s") % (k, p))
1416 1417 raise error.Abort(_(b"invalid pattern"))
1417 1418
1418 1419
1419 1420 def _patternrootsanddirs(kindpats):
1420 1421 '''Returns roots and directories corresponding to each pattern.
1421 1422
1422 1423 This calculates the roots and directories exactly matching the patterns and
1423 1424 returns a tuple of (roots, dirs) for each. It does not return other
1424 1425 directories which may also need to be considered, like the parent
1425 1426 directories.
1426 1427 '''
1427 1428 r = []
1428 1429 d = []
1429 1430 for kind, pat, source in kindpats:
1430 1431 if kind in (b'glob', b'rootglob'): # find the non-glob prefix
1431 1432 root = []
1432 1433 for p in pat.split(b'/'):
1433 1434 if b'[' in p or b'{' in p or b'*' in p or b'?' in p:
1434 1435 break
1435 1436 root.append(p)
1436 1437 r.append(b'/'.join(root))
1437 1438 elif kind in (b'relpath', b'path'):
1438 1439 if pat == b'.':
1439 1440 pat = b''
1440 1441 r.append(pat)
1441 1442 elif kind in (b'rootfilesin',):
1442 1443 if pat == b'.':
1443 1444 pat = b''
1444 1445 d.append(pat)
1445 1446 else: # relglob, re, relre
1446 1447 r.append(b'')
1447 1448 return r, d
1448 1449
1449 1450
1450 1451 def _roots(kindpats):
1451 1452 '''Returns root directories to match recursively from the given patterns.'''
1452 1453 roots, dirs = _patternrootsanddirs(kindpats)
1453 1454 return roots
1454 1455
1455 1456
1456 1457 def _rootsdirsandparents(kindpats):
1457 1458 '''Returns roots and exact directories from patterns.
1458 1459
1459 1460 `roots` are directories to match recursively, `dirs` should
1460 1461 be matched non-recursively, and `parents` are the implicitly required
1461 1462 directories to walk to items in either roots or dirs.
1462 1463
1463 1464 Returns a tuple of (roots, dirs, parents).
1464 1465
1465 1466 >>> r = _rootsdirsandparents(
1466 1467 ... [(b'glob', b'g/h/*', b''), (b'glob', b'g/h', b''),
1467 1468 ... (b'glob', b'g*', b'')])
1468 1469 >>> print(r[0:2], sorted(r[2])) # the set has an unstable output
1469 1470 (['g/h', 'g/h', ''], []) ['', 'g']
1470 1471 >>> r = _rootsdirsandparents(
1471 1472 ... [(b'rootfilesin', b'g/h', b''), (b'rootfilesin', b'', b'')])
1472 1473 >>> print(r[0:2], sorted(r[2])) # the set has an unstable output
1473 1474 ([], ['g/h', '']) ['', 'g']
1474 1475 >>> r = _rootsdirsandparents(
1475 1476 ... [(b'relpath', b'r', b''), (b'path', b'p/p', b''),
1476 1477 ... (b'path', b'', b'')])
1477 1478 >>> print(r[0:2], sorted(r[2])) # the set has an unstable output
1478 1479 (['r', 'p/p', ''], []) ['', 'p']
1479 1480 >>> r = _rootsdirsandparents(
1480 1481 ... [(b'relglob', b'rg*', b''), (b're', b're/', b''),
1481 1482 ... (b'relre', b'rr', b'')])
1482 1483 >>> print(r[0:2], sorted(r[2])) # the set has an unstable output
1483 1484 (['', '', ''], []) ['']
1484 1485 '''
1485 1486 r, d = _patternrootsanddirs(kindpats)
1486 1487
1487 1488 p = set()
1488 1489 # Add the parents as non-recursive/exact directories, since they must be
1489 1490 # scanned to get to either the roots or the other exact directories.
1490 1491 p.update(pathutil.dirs(d))
1491 1492 p.update(pathutil.dirs(r))
1492 1493
1493 1494 # FIXME: all uses of this function convert these to sets, do so before
1494 1495 # returning.
1495 1496 # FIXME: all uses of this function do not need anything in 'roots' and
1496 1497 # 'dirs' to also be in 'parents', consider removing them before returning.
1497 1498 return r, d, p
1498 1499
1499 1500
1500 1501 def _explicitfiles(kindpats):
1501 1502 '''Returns the potential explicit filenames from the patterns.
1502 1503
1503 1504 >>> _explicitfiles([(b'path', b'foo/bar', b'')])
1504 1505 ['foo/bar']
1505 1506 >>> _explicitfiles([(b'rootfilesin', b'foo/bar', b'')])
1506 1507 []
1507 1508 '''
1508 1509 # Keep only the pattern kinds where one can specify filenames (vs only
1509 1510 # directory names).
1510 1511 filable = [kp for kp in kindpats if kp[0] not in (b'rootfilesin',)]
1511 1512 return _roots(filable)
1512 1513
1513 1514
1514 1515 def _prefix(kindpats):
1515 1516 '''Whether all the patterns match a prefix (i.e. recursively)'''
1516 1517 for kind, pat, source in kindpats:
1517 1518 if kind not in (b'path', b'relpath'):
1518 1519 return False
1519 1520 return True
1520 1521
1521 1522
1522 1523 _commentre = None
1523 1524
1524 1525
1525 1526 def readpatternfile(filepath, warn, sourceinfo=False):
1526 1527 '''parse a pattern file, returning a list of
1527 1528 patterns. These patterns should be given to compile()
1528 1529 to be validated and converted into a match function.
1529 1530
1530 1531 trailing white space is dropped.
1531 1532 the escape character is backslash.
1532 1533 comments start with #.
1533 1534 empty lines are skipped.
1534 1535
1535 1536 lines can be of the following formats:
1536 1537
1537 1538 syntax: regexp # defaults following lines to non-rooted regexps
1538 1539 syntax: glob # defaults following lines to non-rooted globs
1539 1540 re:pattern # non-rooted regular expression
1540 1541 glob:pattern # non-rooted glob
1541 1542 rootglob:pat # rooted glob (same root as ^ in regexps)
1542 1543 pattern # pattern of the current default type
1543 1544
1544 1545 if sourceinfo is set, returns a list of tuples:
1545 1546 (pattern, lineno, originalline).
1546 1547 This is useful to debug ignore patterns.
1547 1548 '''
1548 1549
1549 1550 if rustmod is not None:
1550 1551 result, warnings = rustmod.read_pattern_file(
1551 1552 filepath, bool(warn), sourceinfo,
1552 1553 )
1553 1554
1554 1555 for warning_params in warnings:
1555 1556 # Can't be easily emitted from Rust, because it would require
1556 1557 # a mechanism for both gettext and calling the `warn` function.
1557 1558 warn(_(b"%s: ignoring invalid syntax '%s'\n") % warning_params)
1558 1559
1559 1560 return result
1560 1561
1561 1562 syntaxes = {
1562 1563 b're': b'relre:',
1563 1564 b'regexp': b'relre:',
1564 1565 b'glob': b'relglob:',
1565 1566 b'rootglob': b'rootglob:',
1566 1567 b'include': b'include',
1567 1568 b'subinclude': b'subinclude',
1568 1569 }
1569 1570 syntax = b'relre:'
1570 1571 patterns = []
1571 1572
1572 1573 fp = open(filepath, b'rb')
1573 1574 for lineno, line in enumerate(util.iterfile(fp), start=1):
1574 1575 if b"#" in line:
1575 1576 global _commentre
1576 1577 if not _commentre:
1577 1578 _commentre = util.re.compile(br'((?:^|[^\\])(?:\\\\)*)#.*')
1578 1579 # remove comments prefixed by an even number of escapes
1579 1580 m = _commentre.search(line)
1580 1581 if m:
1581 1582 line = line[: m.end(1)]
1582 1583 # fixup properly escaped comments that survived the above
1583 1584 line = line.replace(b"\\#", b"#")
1584 1585 line = line.rstrip()
1585 1586 if not line:
1586 1587 continue
1587 1588
1588 1589 if line.startswith(b'syntax:'):
1589 1590 s = line[7:].strip()
1590 1591 try:
1591 1592 syntax = syntaxes[s]
1592 1593 except KeyError:
1593 1594 if warn:
1594 1595 warn(
1595 1596 _(b"%s: ignoring invalid syntax '%s'\n") % (filepath, s)
1596 1597 )
1597 1598 continue
1598 1599
1599 1600 linesyntax = syntax
1600 1601 for s, rels in pycompat.iteritems(syntaxes):
1601 1602 if line.startswith(rels):
1602 1603 linesyntax = rels
1603 1604 line = line[len(rels) :]
1604 1605 break
1605 1606 elif line.startswith(s + b':'):
1606 1607 linesyntax = rels
1607 1608 line = line[len(s) + 1 :]
1608 1609 break
1609 1610 if sourceinfo:
1610 1611 patterns.append((linesyntax + line, lineno, line))
1611 1612 else:
1612 1613 patterns.append(linesyntax + line)
1613 1614 fp.close()
1614 1615 return patterns
@@ -1,476 +1,476
1 1 from __future__ import absolute_import
2 2
3 3 import binascii
4 4 import itertools
5 5 import silenttestrunner
6 6 import unittest
7 7 import zlib
8 8
9 9 from mercurial import (
10 10 manifest as manifestmod,
11 11 match as matchmod,
12 12 )
13 13
14 14 EMTPY_MANIFEST = b''
15 15
16 16 HASH_1 = b'1' * 40
17 17 BIN_HASH_1 = binascii.unhexlify(HASH_1)
18 18 HASH_2 = b'f' * 40
19 19 BIN_HASH_2 = binascii.unhexlify(HASH_2)
20 20 HASH_3 = b'1234567890abcdef0987654321deadbeef0fcafe'
21 21 BIN_HASH_3 = binascii.unhexlify(HASH_3)
22 22 A_SHORT_MANIFEST = (
23 23 b'bar/baz/qux.py\0%(hash2)s%(flag2)s\n' b'foo\0%(hash1)s%(flag1)s\n'
24 24 ) % {b'hash1': HASH_1, b'flag1': b'', b'hash2': HASH_2, b'flag2': b'l',}
25 25
26 26 A_DEEPER_MANIFEST = (
27 27 b'a/b/c/bar.py\0%(hash3)s%(flag1)s\n'
28 28 b'a/b/c/bar.txt\0%(hash1)s%(flag1)s\n'
29 29 b'a/b/c/foo.py\0%(hash3)s%(flag1)s\n'
30 30 b'a/b/c/foo.txt\0%(hash2)s%(flag2)s\n'
31 31 b'a/b/d/baz.py\0%(hash3)s%(flag1)s\n'
32 32 b'a/b/d/qux.py\0%(hash1)s%(flag2)s\n'
33 33 b'a/b/d/ten.txt\0%(hash3)s%(flag2)s\n'
34 34 b'a/b/dog.py\0%(hash3)s%(flag1)s\n'
35 35 b'a/b/fish.py\0%(hash2)s%(flag1)s\n'
36 36 b'a/c/london.py\0%(hash3)s%(flag2)s\n'
37 37 b'a/c/paper.txt\0%(hash2)s%(flag2)s\n'
38 38 b'a/c/paris.py\0%(hash2)s%(flag1)s\n'
39 39 b'a/d/apple.py\0%(hash3)s%(flag1)s\n'
40 40 b'a/d/pizza.py\0%(hash3)s%(flag2)s\n'
41 41 b'a/green.py\0%(hash1)s%(flag2)s\n'
42 42 b'a/purple.py\0%(hash2)s%(flag1)s\n'
43 43 b'app.py\0%(hash3)s%(flag1)s\n'
44 44 b'readme.txt\0%(hash2)s%(flag1)s\n'
45 45 ) % {
46 46 b'hash1': HASH_1,
47 47 b'flag1': b'',
48 48 b'hash2': HASH_2,
49 49 b'flag2': b'l',
50 50 b'hash3': HASH_3,
51 51 }
52 52
53 53 HUGE_MANIFEST_ENTRIES = 200001
54 54
55 55 izip = getattr(itertools, 'izip', zip)
56 56 if 'xrange' not in globals():
57 57 xrange = range
58 58
59 59 A_HUGE_MANIFEST = b''.join(
60 60 sorted(
61 61 b'file%d\0%s%s\n' % (i, h, f)
62 62 for i, h, f in izip(
63 63 xrange(200001),
64 64 itertools.cycle((HASH_1, HASH_2)),
65 65 itertools.cycle((b'', b'x', b'l')),
66 66 )
67 67 )
68 68 )
69 69
70 70
71 71 class basemanifesttests(object):
72 72 def parsemanifest(self, text):
73 73 raise NotImplementedError('parsemanifest not implemented by test case')
74 74
75 75 def testEmptyManifest(self):
76 76 m = self.parsemanifest(EMTPY_MANIFEST)
77 77 self.assertEqual(0, len(m))
78 78 self.assertEqual([], list(m))
79 79
80 80 def testManifest(self):
81 81 m = self.parsemanifest(A_SHORT_MANIFEST)
82 82 self.assertEqual([b'bar/baz/qux.py', b'foo'], list(m))
83 83 self.assertEqual(BIN_HASH_2, m[b'bar/baz/qux.py'])
84 84 self.assertEqual(b'l', m.flags(b'bar/baz/qux.py'))
85 85 self.assertEqual(BIN_HASH_1, m[b'foo'])
86 86 self.assertEqual(b'', m.flags(b'foo'))
87 87 with self.assertRaises(KeyError):
88 88 m[b'wat']
89 89
90 90 def testSetItem(self):
91 91 want = BIN_HASH_1
92 92
93 93 m = self.parsemanifest(EMTPY_MANIFEST)
94 94 m[b'a'] = want
95 95 self.assertIn(b'a', m)
96 96 self.assertEqual(want, m[b'a'])
97 97 self.assertEqual(b'a\0' + HASH_1 + b'\n', m.text())
98 98
99 99 m = self.parsemanifest(A_SHORT_MANIFEST)
100 100 m[b'a'] = want
101 101 self.assertEqual(want, m[b'a'])
102 102 self.assertEqual(b'a\0' + HASH_1 + b'\n' + A_SHORT_MANIFEST, m.text())
103 103
104 104 def testSetFlag(self):
105 105 want = b'x'
106 106
107 107 m = self.parsemanifest(EMTPY_MANIFEST)
108 108 # first add a file; a file-less flag makes no sense
109 109 m[b'a'] = BIN_HASH_1
110 110 m.setflag(b'a', want)
111 111 self.assertEqual(want, m.flags(b'a'))
112 112 self.assertEqual(b'a\0' + HASH_1 + want + b'\n', m.text())
113 113
114 114 m = self.parsemanifest(A_SHORT_MANIFEST)
115 115 # first add a file; a file-less flag makes no sense
116 116 m[b'a'] = BIN_HASH_1
117 117 m.setflag(b'a', want)
118 118 self.assertEqual(want, m.flags(b'a'))
119 119 self.assertEqual(
120 120 b'a\0' + HASH_1 + want + b'\n' + A_SHORT_MANIFEST, m.text()
121 121 )
122 122
123 123 def testCopy(self):
124 124 m = self.parsemanifest(A_SHORT_MANIFEST)
125 125 m[b'a'] = BIN_HASH_1
126 126 m2 = m.copy()
127 127 del m
128 128 del m2 # make sure we don't double free() anything
129 129
130 130 def testCompaction(self):
131 131 unhex = binascii.unhexlify
132 132 h1, h2 = unhex(HASH_1), unhex(HASH_2)
133 133 m = self.parsemanifest(A_SHORT_MANIFEST)
134 134 m[b'alpha'] = h1
135 135 m[b'beta'] = h2
136 136 del m[b'foo']
137 137 want = b'alpha\0%s\nbar/baz/qux.py\0%sl\nbeta\0%s\n' % (
138 138 HASH_1,
139 139 HASH_2,
140 140 HASH_2,
141 141 )
142 142 self.assertEqual(want, m.text())
143 143 self.assertEqual(3, len(m))
144 144 self.assertEqual([b'alpha', b'bar/baz/qux.py', b'beta'], list(m))
145 145 self.assertEqual(h1, m[b'alpha'])
146 146 self.assertEqual(h2, m[b'bar/baz/qux.py'])
147 147 self.assertEqual(h2, m[b'beta'])
148 148 self.assertEqual(b'', m.flags(b'alpha'))
149 149 self.assertEqual(b'l', m.flags(b'bar/baz/qux.py'))
150 150 self.assertEqual(b'', m.flags(b'beta'))
151 151 with self.assertRaises(KeyError):
152 152 m[b'foo']
153 153
154 154 def testSetGetNodeSuffix(self):
155 155 clean = self.parsemanifest(A_SHORT_MANIFEST)
156 156 m = self.parsemanifest(A_SHORT_MANIFEST)
157 157 h = m[b'foo']
158 158 f = m.flags(b'foo')
159 159 want = h + b'a'
160 160 # Merge code wants to set 21-byte fake hashes at times
161 161 m[b'foo'] = want
162 162 self.assertEqual(want, m[b'foo'])
163 163 self.assertEqual(
164 164 [(b'bar/baz/qux.py', BIN_HASH_2), (b'foo', BIN_HASH_1 + b'a')],
165 165 list(m.items()),
166 166 )
167 167 # Sometimes it even tries a 22-byte fake hash, but we can
168 168 # return 21 and it'll work out
169 169 m[b'foo'] = want + b'+'
170 170 self.assertEqual(want, m[b'foo'])
171 171 # make sure the suffix survives a copy
172 match = matchmod.match(b'', b'', [b're:foo'])
172 match = matchmod.match(b'/repo', b'', [b're:foo'])
173 173 m2 = m.matches(match)
174 174 self.assertEqual(want, m2[b'foo'])
175 175 self.assertEqual(1, len(m2))
176 176 m2 = m.copy()
177 177 self.assertEqual(want, m2[b'foo'])
178 178 # suffix with iteration
179 179 self.assertEqual(
180 180 [(b'bar/baz/qux.py', BIN_HASH_2), (b'foo', want)], list(m.items())
181 181 )
182 182
183 183 # shows up in diff
184 184 self.assertEqual({b'foo': ((want, f), (h, b''))}, m.diff(clean))
185 185 self.assertEqual({b'foo': ((h, b''), (want, f))}, clean.diff(m))
186 186
187 187 def testMatchException(self):
188 188 m = self.parsemanifest(A_SHORT_MANIFEST)
189 match = matchmod.match(b'', b'', [b're:.*'])
189 match = matchmod.match(b'/repo', b'', [b're:.*'])
190 190
191 191 def filt(path):
192 192 if path == b'foo':
193 193 assert False
194 194 return True
195 195
196 196 match.matchfn = filt
197 197 with self.assertRaises(AssertionError):
198 198 m.matches(match)
199 199
200 200 def testRemoveItem(self):
201 201 m = self.parsemanifest(A_SHORT_MANIFEST)
202 202 del m[b'foo']
203 203 with self.assertRaises(KeyError):
204 204 m[b'foo']
205 205 self.assertEqual(1, len(m))
206 206 self.assertEqual(1, len(list(m)))
207 207 # now restore and make sure everything works right
208 208 m[b'foo'] = b'a' * 20
209 209 self.assertEqual(2, len(m))
210 210 self.assertEqual(2, len(list(m)))
211 211
212 212 def testManifestDiff(self):
213 213 MISSING = (None, b'')
214 214 addl = b'z-only-in-left\0' + HASH_1 + b'\n'
215 215 addr = b'z-only-in-right\0' + HASH_2 + b'x\n'
216 216 left = self.parsemanifest(
217 217 A_SHORT_MANIFEST.replace(HASH_1, HASH_3 + b'x') + addl
218 218 )
219 219 right = self.parsemanifest(A_SHORT_MANIFEST + addr)
220 220 want = {
221 221 b'foo': ((BIN_HASH_3, b'x'), (BIN_HASH_1, b'')),
222 222 b'z-only-in-left': ((BIN_HASH_1, b''), MISSING),
223 223 b'z-only-in-right': (MISSING, (BIN_HASH_2, b'x')),
224 224 }
225 225 self.assertEqual(want, left.diff(right))
226 226
227 227 want = {
228 228 b'bar/baz/qux.py': (MISSING, (BIN_HASH_2, b'l')),
229 229 b'foo': (MISSING, (BIN_HASH_3, b'x')),
230 230 b'z-only-in-left': (MISSING, (BIN_HASH_1, b'')),
231 231 }
232 232 self.assertEqual(want, self.parsemanifest(EMTPY_MANIFEST).diff(left))
233 233
234 234 want = {
235 235 b'bar/baz/qux.py': ((BIN_HASH_2, b'l'), MISSING),
236 236 b'foo': ((BIN_HASH_3, b'x'), MISSING),
237 237 b'z-only-in-left': ((BIN_HASH_1, b''), MISSING),
238 238 }
239 239 self.assertEqual(want, left.diff(self.parsemanifest(EMTPY_MANIFEST)))
240 240 copy = right.copy()
241 241 del copy[b'z-only-in-right']
242 242 del right[b'foo']
243 243 want = {
244 244 b'foo': (MISSING, (BIN_HASH_1, b'')),
245 245 b'z-only-in-right': ((BIN_HASH_2, b'x'), MISSING),
246 246 }
247 247 self.assertEqual(want, right.diff(copy))
248 248
249 249 short = self.parsemanifest(A_SHORT_MANIFEST)
250 250 pruned = short.copy()
251 251 del pruned[b'foo']
252 252 want = {
253 253 b'foo': ((BIN_HASH_1, b''), MISSING),
254 254 }
255 255 self.assertEqual(want, short.diff(pruned))
256 256 want = {
257 257 b'foo': (MISSING, (BIN_HASH_1, b'')),
258 258 }
259 259 self.assertEqual(want, pruned.diff(short))
260 260 want = {
261 261 b'bar/baz/qux.py': None,
262 262 b'foo': (MISSING, (BIN_HASH_1, b'')),
263 263 }
264 264 self.assertEqual(want, pruned.diff(short, clean=True))
265 265
266 266 def testReversedLines(self):
267 267 backwards = b''.join(
268 268 l + b'\n' for l in reversed(A_SHORT_MANIFEST.split(b'\n')) if l
269 269 )
270 270 try:
271 271 self.parsemanifest(backwards)
272 272 self.fail('Should have raised ValueError')
273 273 except ValueError as v:
274 274 self.assertIn('Manifest lines not in sorted order.', str(v))
275 275
276 276 def testNoTerminalNewline(self):
277 277 try:
278 278 self.parsemanifest(A_SHORT_MANIFEST + b'wat')
279 279 self.fail('Should have raised ValueError')
280 280 except ValueError as v:
281 281 self.assertIn('Manifest did not end in a newline.', str(v))
282 282
283 283 def testNoNewLineAtAll(self):
284 284 try:
285 285 self.parsemanifest(b'wat')
286 286 self.fail('Should have raised ValueError')
287 287 except ValueError as v:
288 288 self.assertIn('Manifest did not end in a newline.', str(v))
289 289
290 290 def testHugeManifest(self):
291 291 m = self.parsemanifest(A_HUGE_MANIFEST)
292 292 self.assertEqual(HUGE_MANIFEST_ENTRIES, len(m))
293 293 self.assertEqual(len(m), len(list(m)))
294 294
295 295 def testMatchesMetadata(self):
296 296 '''Tests matches() for a few specific files to make sure that both
297 297 the set of files as well as their flags and nodeids are correct in
298 298 the resulting manifest.'''
299 299 m = self.parsemanifest(A_HUGE_MANIFEST)
300 300
301 301 match = matchmod.exact([b'file1', b'file200', b'file300'])
302 302 m2 = m.matches(match)
303 303
304 304 w = (b'file1\0%sx\n' b'file200\0%sl\n' b'file300\0%s\n') % (
305 305 HASH_2,
306 306 HASH_1,
307 307 HASH_1,
308 308 )
309 309 self.assertEqual(w, m2.text())
310 310
311 311 def testMatchesNonexistentFile(self):
312 312 '''Tests matches() for a small set of specific files, including one
313 313 nonexistent file to make sure in only matches against existing files.
314 314 '''
315 315 m = self.parsemanifest(A_DEEPER_MANIFEST)
316 316
317 317 match = matchmod.exact(
318 318 [b'a/b/c/bar.txt', b'a/b/d/qux.py', b'readme.txt', b'nonexistent']
319 319 )
320 320 m2 = m.matches(match)
321 321
322 322 self.assertEqual(
323 323 [b'a/b/c/bar.txt', b'a/b/d/qux.py', b'readme.txt'], m2.keys()
324 324 )
325 325
326 326 def testMatchesNonexistentDirectory(self):
327 327 '''Tests matches() for a relpath match on a directory that doesn't
328 328 actually exist.'''
329 329 m = self.parsemanifest(A_DEEPER_MANIFEST)
330 330
331 match = matchmod.match(b'/', b'', [b'a/f'], default=b'relpath')
331 match = matchmod.match(b'/repo', b'', [b'a/f'], default=b'relpath')
332 332 m2 = m.matches(match)
333 333
334 334 self.assertEqual([], m2.keys())
335 335
336 336 def testMatchesExactLarge(self):
337 337 '''Tests matches() for files matching a large list of exact files.
338 338 '''
339 339 m = self.parsemanifest(A_HUGE_MANIFEST)
340 340
341 341 flist = m.keys()[80:300]
342 342 match = matchmod.exact(flist)
343 343 m2 = m.matches(match)
344 344
345 345 self.assertEqual(flist, m2.keys())
346 346
347 347 def testMatchesFull(self):
348 348 '''Tests matches() for what should be a full match.'''
349 349 m = self.parsemanifest(A_DEEPER_MANIFEST)
350 350
351 match = matchmod.match(b'/', b'', [b''])
351 match = matchmod.match(b'/repo', b'', [b''])
352 352 m2 = m.matches(match)
353 353
354 354 self.assertEqual(m.keys(), m2.keys())
355 355
356 356 def testMatchesDirectory(self):
357 357 '''Tests matches() on a relpath match on a directory, which should
358 358 match against all files within said directory.'''
359 359 m = self.parsemanifest(A_DEEPER_MANIFEST)
360 360
361 match = matchmod.match(b'/', b'', [b'a/b'], default=b'relpath')
361 match = matchmod.match(b'/repo', b'', [b'a/b'], default=b'relpath')
362 362 m2 = m.matches(match)
363 363
364 364 self.assertEqual(
365 365 [
366 366 b'a/b/c/bar.py',
367 367 b'a/b/c/bar.txt',
368 368 b'a/b/c/foo.py',
369 369 b'a/b/c/foo.txt',
370 370 b'a/b/d/baz.py',
371 371 b'a/b/d/qux.py',
372 372 b'a/b/d/ten.txt',
373 373 b'a/b/dog.py',
374 374 b'a/b/fish.py',
375 375 ],
376 376 m2.keys(),
377 377 )
378 378
379 379 def testMatchesExactPath(self):
380 380 '''Tests matches() on an exact match on a directory, which should
381 381 result in an empty manifest because you can't perform an exact match
382 382 against a directory.'''
383 383 m = self.parsemanifest(A_DEEPER_MANIFEST)
384 384
385 385 match = matchmod.exact([b'a/b'])
386 386 m2 = m.matches(match)
387 387
388 388 self.assertEqual([], m2.keys())
389 389
390 390 def testMatchesCwd(self):
391 391 '''Tests matches() on a relpath match with the current directory ('.')
392 392 when not in the root directory.'''
393 393 m = self.parsemanifest(A_DEEPER_MANIFEST)
394 394
395 match = matchmod.match(b'/', b'a/b', [b'.'], default=b'relpath')
395 match = matchmod.match(b'/repo', b'a/b', [b'.'], default=b'relpath')
396 396 m2 = m.matches(match)
397 397
398 398 self.assertEqual(
399 399 [
400 400 b'a/b/c/bar.py',
401 401 b'a/b/c/bar.txt',
402 402 b'a/b/c/foo.py',
403 403 b'a/b/c/foo.txt',
404 404 b'a/b/d/baz.py',
405 405 b'a/b/d/qux.py',
406 406 b'a/b/d/ten.txt',
407 407 b'a/b/dog.py',
408 408 b'a/b/fish.py',
409 409 ],
410 410 m2.keys(),
411 411 )
412 412
413 413 def testMatchesWithPattern(self):
414 414 '''Tests matches() for files matching a pattern that reside
415 415 deeper than the specified directory.'''
416 416 m = self.parsemanifest(A_DEEPER_MANIFEST)
417 417
418 match = matchmod.match(b'/', b'', [b'a/b/*/*.txt'])
418 match = matchmod.match(b'/repo', b'', [b'a/b/*/*.txt'])
419 419 m2 = m.matches(match)
420 420
421 421 self.assertEqual(
422 422 [b'a/b/c/bar.txt', b'a/b/c/foo.txt', b'a/b/d/ten.txt'], m2.keys()
423 423 )
424 424
425 425
426 426 class testmanifestdict(unittest.TestCase, basemanifesttests):
427 427 def parsemanifest(self, text):
428 428 return manifestmod.manifestdict(text)
429 429
430 430 def testObviouslyBogusManifest(self):
431 431 # This is a 163k manifest that came from oss-fuzz. It was a
432 432 # timeout there, but when run normally it doesn't seem to
433 433 # present any particular slowness.
434 434 data = zlib.decompress(
435 435 b'x\x9c\xed\xce;\n\x83\x00\x10\x04\xd0\x8deNa\x93~\xf1\x03\xc9q\xf4'
436 436 b'\x14\xeaU\xbdB\xda\xd4\xe6Cj\xc1FA\xde+\x86\xe9f\xa2\xfci\xbb\xfb'
437 437 b'\xa3\xef\xea\xba\xca\x7fk\x86q\x9a\xc6\xc8\xcc&\xb3\xcf\xf8\xb8|#'
438 438 b'\x8a9\x00\xd8\xe6v\xf4\x01N\xe1\n\x00\x00\x00\x00\x00\x00\x00\x00'
439 439 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
440 440 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
441 441 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
442 442 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
443 443 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
444 444 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
445 445 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
446 446 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
447 447 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
448 448 b'\x00\x00\xc0\x8aey\x1d}\x01\xd8\xe0\xb9\xf3\xde\x1b\xcf\x17'
449 449 b'\xac\xbe'
450 450 )
451 451 with self.assertRaises(ValueError):
452 452 self.parsemanifest(data)
453 453
454 454
455 455 class testtreemanifest(unittest.TestCase, basemanifesttests):
456 456 def parsemanifest(self, text):
457 457 return manifestmod.treemanifest(b'', text)
458 458
459 459 def testWalkSubtrees(self):
460 460 m = self.parsemanifest(A_DEEPER_MANIFEST)
461 461
462 462 dirs = [s._dir for s in m.walksubtrees()]
463 463 self.assertEqual(
464 464 sorted(
465 465 [b'', b'a/', b'a/c/', b'a/d/', b'a/b/', b'a/b/c/', b'a/b/d/']
466 466 ),
467 467 sorted(dirs),
468 468 )
469 469
470 match = matchmod.match(b'/', b'', [b'path:a/b/'])
470 match = matchmod.match(b'/repo', b'', [b'path:a/b/'])
471 471 dirs = [s._dir for s in m.walksubtrees(matcher=match)]
472 472 self.assertEqual(sorted([b'a/b/', b'a/b/c/', b'a/b/d/']), sorted(dirs))
473 473
474 474
475 475 if __name__ == '__main__':
476 476 silenttestrunner.main(__name__)
@@ -1,840 +1,849
1 1 from __future__ import absolute_import
2 2
3 3 import unittest
4 4
5 5 import silenttestrunner
6 6
7 7 from mercurial import (
8 8 match as matchmod,
9 9 util,
10 10 )
11 11
12 12
13 noop_auditor = lambda name: None
14
15
13 16 class BaseMatcherTests(unittest.TestCase):
14 17 def testVisitdir(self):
15 18 m = matchmod.basematcher()
16 19 self.assertTrue(m.visitdir(b''))
17 20 self.assertTrue(m.visitdir(b'dir'))
18 21
19 22 def testVisitchildrenset(self):
20 23 m = matchmod.basematcher()
21 24 self.assertEqual(m.visitchildrenset(b''), b'this')
22 25 self.assertEqual(m.visitchildrenset(b'dir'), b'this')
23 26
24 27
25 28 class AlwaysMatcherTests(unittest.TestCase):
26 29 def testVisitdir(self):
27 30 m = matchmod.alwaysmatcher()
28 31 self.assertEqual(m.visitdir(b''), b'all')
29 32 self.assertEqual(m.visitdir(b'dir'), b'all')
30 33
31 34 def testVisitchildrenset(self):
32 35 m = matchmod.alwaysmatcher()
33 36 self.assertEqual(m.visitchildrenset(b''), b'all')
34 37 self.assertEqual(m.visitchildrenset(b'dir'), b'all')
35 38
36 39
37 40 class NeverMatcherTests(unittest.TestCase):
38 41 def testVisitdir(self):
39 42 m = matchmod.nevermatcher()
40 43 self.assertFalse(m.visitdir(b''))
41 44 self.assertFalse(m.visitdir(b'dir'))
42 45
43 46 def testVisitchildrenset(self):
44 47 m = matchmod.nevermatcher()
45 48 self.assertEqual(m.visitchildrenset(b''), set())
46 49 self.assertEqual(m.visitchildrenset(b'dir'), set())
47 50
48 51
49 52 class PredicateMatcherTests(unittest.TestCase):
50 53 # predicatematcher does not currently define either of these methods, so
51 54 # this is equivalent to BaseMatcherTests.
52 55
53 56 def testVisitdir(self):
54 57 m = matchmod.predicatematcher(lambda *a: False)
55 58 self.assertTrue(m.visitdir(b''))
56 59 self.assertTrue(m.visitdir(b'dir'))
57 60
58 61 def testVisitchildrenset(self):
59 62 m = matchmod.predicatematcher(lambda *a: False)
60 63 self.assertEqual(m.visitchildrenset(b''), b'this')
61 64 self.assertEqual(m.visitchildrenset(b'dir'), b'this')
62 65
63 66
64 67 class PatternMatcherTests(unittest.TestCase):
65 68 def testVisitdirPrefix(self):
66 m = matchmod.match(b'x', b'', patterns=[b'path:dir/subdir'])
69 m = matchmod.match(b'/repo', b'', patterns=[b'path:dir/subdir'])
67 70 assert isinstance(m, matchmod.patternmatcher)
68 71 self.assertTrue(m.visitdir(b''))
69 72 self.assertTrue(m.visitdir(b'dir'))
70 73 self.assertEqual(m.visitdir(b'dir/subdir'), b'all')
71 74 # OPT: This should probably be 'all' if its parent is?
72 75 self.assertTrue(m.visitdir(b'dir/subdir/x'))
73 76 self.assertFalse(m.visitdir(b'folder'))
74 77
75 78 def testVisitchildrensetPrefix(self):
76 m = matchmod.match(b'x', b'', patterns=[b'path:dir/subdir'])
79 m = matchmod.match(b'/repo', b'', patterns=[b'path:dir/subdir'])
77 80 assert isinstance(m, matchmod.patternmatcher)
78 81 self.assertEqual(m.visitchildrenset(b''), b'this')
79 82 self.assertEqual(m.visitchildrenset(b'dir'), b'this')
80 83 self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'all')
81 84 # OPT: This should probably be 'all' if its parent is?
82 85 self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), b'this')
83 86 self.assertEqual(m.visitchildrenset(b'folder'), set())
84 87
85 88 def testVisitdirRootfilesin(self):
86 m = matchmod.match(b'x', b'', patterns=[b'rootfilesin:dir/subdir'])
89 m = matchmod.match(b'/repo', b'', patterns=[b'rootfilesin:dir/subdir'])
87 90 assert isinstance(m, matchmod.patternmatcher)
88 91 self.assertFalse(m.visitdir(b'dir/subdir/x'))
89 92 self.assertFalse(m.visitdir(b'folder'))
90 93 # FIXME: These should probably be True.
91 94 self.assertFalse(m.visitdir(b''))
92 95 self.assertFalse(m.visitdir(b'dir'))
93 96 self.assertFalse(m.visitdir(b'dir/subdir'))
94 97
95 98 def testVisitchildrensetRootfilesin(self):
96 m = matchmod.match(b'x', b'', patterns=[b'rootfilesin:dir/subdir'])
99 m = matchmod.match(b'/repo', b'', patterns=[b'rootfilesin:dir/subdir'])
97 100 assert isinstance(m, matchmod.patternmatcher)
98 101 self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), set())
99 102 self.assertEqual(m.visitchildrenset(b'folder'), set())
100 103 # FIXME: These should probably be {'dir'}, {'subdir'} and 'this',
101 104 # respectively, or at least 'this' for all three.
102 105 self.assertEqual(m.visitchildrenset(b''), set())
103 106 self.assertEqual(m.visitchildrenset(b'dir'), set())
104 107 self.assertEqual(m.visitchildrenset(b'dir/subdir'), set())
105 108
106 109 def testVisitdirGlob(self):
107 m = matchmod.match(b'x', b'', patterns=[b'glob:dir/z*'])
110 m = matchmod.match(b'/repo', b'', patterns=[b'glob:dir/z*'])
108 111 assert isinstance(m, matchmod.patternmatcher)
109 112 self.assertTrue(m.visitdir(b''))
110 113 self.assertTrue(m.visitdir(b'dir'))
111 114 self.assertFalse(m.visitdir(b'folder'))
112 115 # OPT: these should probably be False.
113 116 self.assertTrue(m.visitdir(b'dir/subdir'))
114 117 self.assertTrue(m.visitdir(b'dir/subdir/x'))
115 118
116 119 def testVisitchildrensetGlob(self):
117 m = matchmod.match(b'x', b'', patterns=[b'glob:dir/z*'])
120 m = matchmod.match(b'/repo', b'', patterns=[b'glob:dir/z*'])
118 121 assert isinstance(m, matchmod.patternmatcher)
119 122 self.assertEqual(m.visitchildrenset(b''), b'this')
120 123 self.assertEqual(m.visitchildrenset(b'folder'), set())
121 124 self.assertEqual(m.visitchildrenset(b'dir'), b'this')
122 125 # OPT: these should probably be set().
123 126 self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'this')
124 127 self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), b'this')
125 128
126 129
127 130 class IncludeMatcherTests(unittest.TestCase):
128 131 def testVisitdirPrefix(self):
129 m = matchmod.match(b'x', b'', include=[b'path:dir/subdir'])
132 m = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
130 133 assert isinstance(m, matchmod.includematcher)
131 134 self.assertTrue(m.visitdir(b''))
132 135 self.assertTrue(m.visitdir(b'dir'))
133 136 self.assertEqual(m.visitdir(b'dir/subdir'), b'all')
134 137 # OPT: This should probably be 'all' if its parent is?
135 138 self.assertTrue(m.visitdir(b'dir/subdir/x'))
136 139 self.assertFalse(m.visitdir(b'folder'))
137 140
138 141 def testVisitchildrensetPrefix(self):
139 m = matchmod.match(b'x', b'', include=[b'path:dir/subdir'])
142 m = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
140 143 assert isinstance(m, matchmod.includematcher)
141 144 self.assertEqual(m.visitchildrenset(b''), {b'dir'})
142 145 self.assertEqual(m.visitchildrenset(b'dir'), {b'subdir'})
143 146 self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'all')
144 147 # OPT: This should probably be 'all' if its parent is?
145 148 self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), b'this')
146 149 self.assertEqual(m.visitchildrenset(b'folder'), set())
147 150
148 151 def testVisitdirRootfilesin(self):
149 m = matchmod.match(b'x', b'', include=[b'rootfilesin:dir/subdir'])
152 m = matchmod.match(b'/repo', b'', include=[b'rootfilesin:dir/subdir'])
150 153 assert isinstance(m, matchmod.includematcher)
151 154 self.assertTrue(m.visitdir(b''))
152 155 self.assertTrue(m.visitdir(b'dir'))
153 156 self.assertTrue(m.visitdir(b'dir/subdir'))
154 157 self.assertFalse(m.visitdir(b'dir/subdir/x'))
155 158 self.assertFalse(m.visitdir(b'folder'))
156 159
157 160 def testVisitchildrensetRootfilesin(self):
158 m = matchmod.match(b'x', b'', include=[b'rootfilesin:dir/subdir'])
161 m = matchmod.match(b'/repo', b'', include=[b'rootfilesin:dir/subdir'])
159 162 assert isinstance(m, matchmod.includematcher)
160 163 self.assertEqual(m.visitchildrenset(b''), {b'dir'})
161 164 self.assertEqual(m.visitchildrenset(b'dir'), {b'subdir'})
162 165 self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'this')
163 166 self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), set())
164 167 self.assertEqual(m.visitchildrenset(b'folder'), set())
165 168
166 169 def testVisitdirGlob(self):
167 m = matchmod.match(b'x', b'', include=[b'glob:dir/z*'])
170 m = matchmod.match(b'/repo', b'', include=[b'glob:dir/z*'])
168 171 assert isinstance(m, matchmod.includematcher)
169 172 self.assertTrue(m.visitdir(b''))
170 173 self.assertTrue(m.visitdir(b'dir'))
171 174 self.assertFalse(m.visitdir(b'folder'))
172 175 # OPT: these should probably be False.
173 176 self.assertTrue(m.visitdir(b'dir/subdir'))
174 177 self.assertTrue(m.visitdir(b'dir/subdir/x'))
175 178
176 179 def testVisitchildrensetGlob(self):
177 m = matchmod.match(b'x', b'', include=[b'glob:dir/z*'])
180 m = matchmod.match(b'/repo', b'', include=[b'glob:dir/z*'])
178 181 assert isinstance(m, matchmod.includematcher)
179 182 self.assertEqual(m.visitchildrenset(b''), {b'dir'})
180 183 self.assertEqual(m.visitchildrenset(b'folder'), set())
181 184 self.assertEqual(m.visitchildrenset(b'dir'), b'this')
182 185 # OPT: these should probably be set().
183 186 self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'this')
184 187 self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), b'this')
185 188
186 189
187 190 class ExactMatcherTests(unittest.TestCase):
188 191 def testVisitdir(self):
189 192 m = matchmod.exact(files=[b'dir/subdir/foo.txt'])
190 193 assert isinstance(m, matchmod.exactmatcher)
191 194 self.assertTrue(m.visitdir(b''))
192 195 self.assertTrue(m.visitdir(b'dir'))
193 196 self.assertTrue(m.visitdir(b'dir/subdir'))
194 197 self.assertFalse(m.visitdir(b'dir/subdir/foo.txt'))
195 198 self.assertFalse(m.visitdir(b'dir/foo'))
196 199 self.assertFalse(m.visitdir(b'dir/subdir/x'))
197 200 self.assertFalse(m.visitdir(b'folder'))
198 201
199 202 def testVisitchildrenset(self):
200 203 m = matchmod.exact(files=[b'dir/subdir/foo.txt'])
201 204 assert isinstance(m, matchmod.exactmatcher)
202 205 self.assertEqual(m.visitchildrenset(b''), {b'dir'})
203 206 self.assertEqual(m.visitchildrenset(b'dir'), {b'subdir'})
204 207 self.assertEqual(m.visitchildrenset(b'dir/subdir'), {b'foo.txt'})
205 208 self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), set())
206 209 self.assertEqual(m.visitchildrenset(b'dir/subdir/foo.txt'), set())
207 210 self.assertEqual(m.visitchildrenset(b'folder'), set())
208 211
209 212 def testVisitchildrensetFilesAndDirs(self):
210 213 m = matchmod.exact(
211 214 files=[
212 215 b'rootfile.txt',
213 216 b'a/file1.txt',
214 217 b'a/b/file2.txt',
215 218 # no file in a/b/c
216 219 b'a/b/c/d/file4.txt',
217 220 ]
218 221 )
219 222 assert isinstance(m, matchmod.exactmatcher)
220 223 self.assertEqual(m.visitchildrenset(b''), {b'a', b'rootfile.txt'})
221 224 self.assertEqual(m.visitchildrenset(b'a'), {b'b', b'file1.txt'})
222 225 self.assertEqual(m.visitchildrenset(b'a/b'), {b'c', b'file2.txt'})
223 226 self.assertEqual(m.visitchildrenset(b'a/b/c'), {b'd'})
224 227 self.assertEqual(m.visitchildrenset(b'a/b/c/d'), {b'file4.txt'})
225 228 self.assertEqual(m.visitchildrenset(b'a/b/c/d/e'), set())
226 229 self.assertEqual(m.visitchildrenset(b'folder'), set())
227 230
228 231
229 232 class DifferenceMatcherTests(unittest.TestCase):
230 233 def testVisitdirM2always(self):
231 234 m1 = matchmod.alwaysmatcher()
232 235 m2 = matchmod.alwaysmatcher()
233 236 dm = matchmod.differencematcher(m1, m2)
234 237 # dm should be equivalent to a nevermatcher.
235 238 self.assertFalse(dm.visitdir(b''))
236 239 self.assertFalse(dm.visitdir(b'dir'))
237 240 self.assertFalse(dm.visitdir(b'dir/subdir'))
238 241 self.assertFalse(dm.visitdir(b'dir/subdir/z'))
239 242 self.assertFalse(dm.visitdir(b'dir/foo'))
240 243 self.assertFalse(dm.visitdir(b'dir/subdir/x'))
241 244 self.assertFalse(dm.visitdir(b'folder'))
242 245
243 246 def testVisitchildrensetM2always(self):
244 247 m1 = matchmod.alwaysmatcher()
245 248 m2 = matchmod.alwaysmatcher()
246 249 dm = matchmod.differencematcher(m1, m2)
247 250 # dm should be equivalent to a nevermatcher.
248 251 self.assertEqual(dm.visitchildrenset(b''), set())
249 252 self.assertEqual(dm.visitchildrenset(b'dir'), set())
250 253 self.assertEqual(dm.visitchildrenset(b'dir/subdir'), set())
251 254 self.assertEqual(dm.visitchildrenset(b'dir/subdir/z'), set())
252 255 self.assertEqual(dm.visitchildrenset(b'dir/foo'), set())
253 256 self.assertEqual(dm.visitchildrenset(b'dir/subdir/x'), set())
254 257 self.assertEqual(dm.visitchildrenset(b'folder'), set())
255 258
256 259 def testVisitdirM2never(self):
257 260 m1 = matchmod.alwaysmatcher()
258 261 m2 = matchmod.nevermatcher()
259 262 dm = matchmod.differencematcher(m1, m2)
260 263 # dm should be equivalent to a alwaysmatcher.
261 264 #
262 265 # We're testing Equal-to-True instead of just 'assertTrue' since
263 266 # assertTrue does NOT verify that it's a bool, just that it's truthy.
264 267 # While we may want to eventually make these return 'all', they should
265 268 # not currently do so.
266 269 self.assertEqual(dm.visitdir(b''), b'all')
267 270 self.assertEqual(dm.visitdir(b'dir'), b'all')
268 271 self.assertEqual(dm.visitdir(b'dir/subdir'), b'all')
269 272 self.assertEqual(dm.visitdir(b'dir/subdir/z'), b'all')
270 273 self.assertEqual(dm.visitdir(b'dir/foo'), b'all')
271 274 self.assertEqual(dm.visitdir(b'dir/subdir/x'), b'all')
272 275 self.assertEqual(dm.visitdir(b'folder'), b'all')
273 276
274 277 def testVisitchildrensetM2never(self):
275 278 m1 = matchmod.alwaysmatcher()
276 279 m2 = matchmod.nevermatcher()
277 280 dm = matchmod.differencematcher(m1, m2)
278 281 # dm should be equivalent to a alwaysmatcher.
279 282 self.assertEqual(dm.visitchildrenset(b''), b'all')
280 283 self.assertEqual(dm.visitchildrenset(b'dir'), b'all')
281 284 self.assertEqual(dm.visitchildrenset(b'dir/subdir'), b'all')
282 285 self.assertEqual(dm.visitchildrenset(b'dir/subdir/z'), b'all')
283 286 self.assertEqual(dm.visitchildrenset(b'dir/foo'), b'all')
284 287 self.assertEqual(dm.visitchildrenset(b'dir/subdir/x'), b'all')
285 288 self.assertEqual(dm.visitchildrenset(b'folder'), b'all')
286 289
287 290 def testVisitdirM2SubdirPrefix(self):
288 291 m1 = matchmod.alwaysmatcher()
289 m2 = matchmod.match(b'', b'', patterns=[b'path:dir/subdir'])
292 m2 = matchmod.match(b'/repo', b'', patterns=[b'path:dir/subdir'])
290 293 dm = matchmod.differencematcher(m1, m2)
291 294 self.assertEqual(dm.visitdir(b''), True)
292 295 self.assertEqual(dm.visitdir(b'dir'), True)
293 296 self.assertFalse(dm.visitdir(b'dir/subdir'))
294 297 # OPT: We should probably return False for these; we don't because
295 298 # patternmatcher.visitdir() (our m2) doesn't return 'all' for subdirs of
296 299 # an 'all' pattern, just True.
297 300 self.assertEqual(dm.visitdir(b'dir/subdir/z'), True)
298 301 self.assertEqual(dm.visitdir(b'dir/subdir/x'), True)
299 302 self.assertEqual(dm.visitdir(b'dir/foo'), b'all')
300 303 self.assertEqual(dm.visitdir(b'folder'), b'all')
301 304
302 305 def testVisitchildrensetM2SubdirPrefix(self):
303 306 m1 = matchmod.alwaysmatcher()
304 m2 = matchmod.match(b'', b'', patterns=[b'path:dir/subdir'])
307 m2 = matchmod.match(b'/repo', b'', patterns=[b'path:dir/subdir'])
305 308 dm = matchmod.differencematcher(m1, m2)
306 309 self.assertEqual(dm.visitchildrenset(b''), b'this')
307 310 self.assertEqual(dm.visitchildrenset(b'dir'), b'this')
308 311 self.assertEqual(dm.visitchildrenset(b'dir/subdir'), set())
309 312 self.assertEqual(dm.visitchildrenset(b'dir/foo'), b'all')
310 313 self.assertEqual(dm.visitchildrenset(b'folder'), b'all')
311 314 # OPT: We should probably return set() for these; we don't because
312 315 # patternmatcher.visitdir() (our m2) doesn't return 'all' for subdirs of
313 316 # an 'all' pattern, just 'this'.
314 317 self.assertEqual(dm.visitchildrenset(b'dir/subdir/z'), b'this')
315 318 self.assertEqual(dm.visitchildrenset(b'dir/subdir/x'), b'this')
316 319
317 320 # We're using includematcher instead of patterns because it behaves slightly
318 321 # better (giving narrower results) than patternmatcher.
319 322 def testVisitdirIncludeInclude(self):
320 m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
321 m2 = matchmod.match(b'', b'', include=[b'rootfilesin:dir'])
323 m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
324 m2 = matchmod.match(b'/repo', b'', include=[b'rootfilesin:dir'])
322 325 dm = matchmod.differencematcher(m1, m2)
323 326 self.assertEqual(dm.visitdir(b''), True)
324 327 self.assertEqual(dm.visitdir(b'dir'), True)
325 328 self.assertEqual(dm.visitdir(b'dir/subdir'), b'all')
326 329 self.assertFalse(dm.visitdir(b'dir/foo'))
327 330 self.assertFalse(dm.visitdir(b'folder'))
328 331 # OPT: We should probably return False for these; we don't because
329 332 # patternmatcher.visitdir() (our m2) doesn't return 'all' for subdirs of
330 333 # an 'all' pattern, just True.
331 334 self.assertEqual(dm.visitdir(b'dir/subdir/z'), True)
332 335 self.assertEqual(dm.visitdir(b'dir/subdir/x'), True)
333 336
334 337 def testVisitchildrensetIncludeInclude(self):
335 m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
336 m2 = matchmod.match(b'', b'', include=[b'rootfilesin:dir'])
338 m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
339 m2 = matchmod.match(b'/repo', b'', include=[b'rootfilesin:dir'])
337 340 dm = matchmod.differencematcher(m1, m2)
338 341 self.assertEqual(dm.visitchildrenset(b''), {b'dir'})
339 342 self.assertEqual(dm.visitchildrenset(b'dir'), {b'subdir'})
340 343 self.assertEqual(dm.visitchildrenset(b'dir/subdir'), b'all')
341 344 self.assertEqual(dm.visitchildrenset(b'dir/foo'), set())
342 345 self.assertEqual(dm.visitchildrenset(b'folder'), set())
343 346 # OPT: We should probably return set() for these; we don't because
344 347 # patternmatcher.visitdir() (our m2) doesn't return 'all' for subdirs of
345 348 # an 'all' pattern, just 'this'.
346 349 self.assertEqual(dm.visitchildrenset(b'dir/subdir/z'), b'this')
347 350 self.assertEqual(dm.visitchildrenset(b'dir/subdir/x'), b'this')
348 351
349 352
350 353 class IntersectionMatcherTests(unittest.TestCase):
351 354 def testVisitdirM2always(self):
352 355 m1 = matchmod.alwaysmatcher()
353 356 m2 = matchmod.alwaysmatcher()
354 357 im = matchmod.intersectmatchers(m1, m2)
355 358 # im should be equivalent to a alwaysmatcher.
356 359 self.assertEqual(im.visitdir(b''), b'all')
357 360 self.assertEqual(im.visitdir(b'dir'), b'all')
358 361 self.assertEqual(im.visitdir(b'dir/subdir'), b'all')
359 362 self.assertEqual(im.visitdir(b'dir/subdir/z'), b'all')
360 363 self.assertEqual(im.visitdir(b'dir/foo'), b'all')
361 364 self.assertEqual(im.visitdir(b'dir/subdir/x'), b'all')
362 365 self.assertEqual(im.visitdir(b'folder'), b'all')
363 366
364 367 def testVisitchildrensetM2always(self):
365 368 m1 = matchmod.alwaysmatcher()
366 369 m2 = matchmod.alwaysmatcher()
367 370 im = matchmod.intersectmatchers(m1, m2)
368 371 # im should be equivalent to a alwaysmatcher.
369 372 self.assertEqual(im.visitchildrenset(b''), b'all')
370 373 self.assertEqual(im.visitchildrenset(b'dir'), b'all')
371 374 self.assertEqual(im.visitchildrenset(b'dir/subdir'), b'all')
372 375 self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), b'all')
373 376 self.assertEqual(im.visitchildrenset(b'dir/foo'), b'all')
374 377 self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), b'all')
375 378 self.assertEqual(im.visitchildrenset(b'folder'), b'all')
376 379
377 380 def testVisitdirM2never(self):
378 381 m1 = matchmod.alwaysmatcher()
379 382 m2 = matchmod.nevermatcher()
380 383 im = matchmod.intersectmatchers(m1, m2)
381 384 # im should be equivalent to a nevermatcher.
382 385 self.assertFalse(im.visitdir(b''))
383 386 self.assertFalse(im.visitdir(b'dir'))
384 387 self.assertFalse(im.visitdir(b'dir/subdir'))
385 388 self.assertFalse(im.visitdir(b'dir/subdir/z'))
386 389 self.assertFalse(im.visitdir(b'dir/foo'))
387 390 self.assertFalse(im.visitdir(b'dir/subdir/x'))
388 391 self.assertFalse(im.visitdir(b'folder'))
389 392
390 393 def testVisitchildrensetM2never(self):
391 394 m1 = matchmod.alwaysmatcher()
392 395 m2 = matchmod.nevermatcher()
393 396 im = matchmod.intersectmatchers(m1, m2)
394 397 # im should be equivalent to a nevermqtcher.
395 398 self.assertEqual(im.visitchildrenset(b''), set())
396 399 self.assertEqual(im.visitchildrenset(b'dir'), set())
397 400 self.assertEqual(im.visitchildrenset(b'dir/subdir'), set())
398 401 self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), set())
399 402 self.assertEqual(im.visitchildrenset(b'dir/foo'), set())
400 403 self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), set())
401 404 self.assertEqual(im.visitchildrenset(b'folder'), set())
402 405
403 406 def testVisitdirM2SubdirPrefix(self):
404 407 m1 = matchmod.alwaysmatcher()
405 m2 = matchmod.match(b'', b'', patterns=[b'path:dir/subdir'])
408 m2 = matchmod.match(b'/repo', b'', patterns=[b'path:dir/subdir'])
406 409 im = matchmod.intersectmatchers(m1, m2)
407 410 self.assertEqual(im.visitdir(b''), True)
408 411 self.assertEqual(im.visitdir(b'dir'), True)
409 412 self.assertEqual(im.visitdir(b'dir/subdir'), b'all')
410 413 self.assertFalse(im.visitdir(b'dir/foo'))
411 414 self.assertFalse(im.visitdir(b'folder'))
412 415 # OPT: We should probably return 'all' for these; we don't because
413 416 # patternmatcher.visitdir() (our m2) doesn't return 'all' for subdirs of
414 417 # an 'all' pattern, just True.
415 418 self.assertEqual(im.visitdir(b'dir/subdir/z'), True)
416 419 self.assertEqual(im.visitdir(b'dir/subdir/x'), True)
417 420
418 421 def testVisitchildrensetM2SubdirPrefix(self):
419 422 m1 = matchmod.alwaysmatcher()
420 m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
423 m2 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
421 424 im = matchmod.intersectmatchers(m1, m2)
422 425 self.assertEqual(im.visitchildrenset(b''), {b'dir'})
423 426 self.assertEqual(im.visitchildrenset(b'dir'), {b'subdir'})
424 427 self.assertEqual(im.visitchildrenset(b'dir/subdir'), b'all')
425 428 self.assertEqual(im.visitchildrenset(b'dir/foo'), set())
426 429 self.assertEqual(im.visitchildrenset(b'folder'), set())
427 430 # OPT: We should probably return 'all' for these
428 431 self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), b'this')
429 432 self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), b'this')
430 433
431 434 # We're using includematcher instead of patterns because it behaves slightly
432 435 # better (giving narrower results) than patternmatcher.
433 436 def testVisitdirIncludeInclude(self):
434 m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
435 m2 = matchmod.match(b'', b'', include=[b'rootfilesin:dir'])
437 m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
438 m2 = matchmod.match(b'/repo', b'', include=[b'rootfilesin:dir'])
436 439 im = matchmod.intersectmatchers(m1, m2)
437 440 self.assertEqual(im.visitdir(b''), True)
438 441 self.assertEqual(im.visitdir(b'dir'), True)
439 442 self.assertFalse(im.visitdir(b'dir/subdir'))
440 443 self.assertFalse(im.visitdir(b'dir/foo'))
441 444 self.assertFalse(im.visitdir(b'folder'))
442 445 self.assertFalse(im.visitdir(b'dir/subdir/z'))
443 446 self.assertFalse(im.visitdir(b'dir/subdir/x'))
444 447
445 448 def testVisitchildrensetIncludeInclude(self):
446 m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
447 m2 = matchmod.match(b'', b'', include=[b'rootfilesin:dir'])
449 m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
450 m2 = matchmod.match(b'/repo', b'', include=[b'rootfilesin:dir'])
448 451 im = matchmod.intersectmatchers(m1, m2)
449 452 self.assertEqual(im.visitchildrenset(b''), {b'dir'})
450 453 self.assertEqual(im.visitchildrenset(b'dir'), b'this')
451 454 self.assertEqual(im.visitchildrenset(b'dir/subdir'), set())
452 455 self.assertEqual(im.visitchildrenset(b'dir/foo'), set())
453 456 self.assertEqual(im.visitchildrenset(b'folder'), set())
454 457 self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), set())
455 458 self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), set())
456 459
457 460 # We're using includematcher instead of patterns because it behaves slightly
458 461 # better (giving narrower results) than patternmatcher.
459 462 def testVisitdirIncludeInclude2(self):
460 m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
461 m2 = matchmod.match(b'', b'', include=[b'path:folder'])
463 m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
464 m2 = matchmod.match(b'/repo', b'', include=[b'path:folder'])
462 465 im = matchmod.intersectmatchers(m1, m2)
463 466 # FIXME: is True correct here?
464 467 self.assertEqual(im.visitdir(b''), True)
465 468 self.assertFalse(im.visitdir(b'dir'))
466 469 self.assertFalse(im.visitdir(b'dir/subdir'))
467 470 self.assertFalse(im.visitdir(b'dir/foo'))
468 471 self.assertFalse(im.visitdir(b'folder'))
469 472 self.assertFalse(im.visitdir(b'dir/subdir/z'))
470 473 self.assertFalse(im.visitdir(b'dir/subdir/x'))
471 474
472 475 def testVisitchildrensetIncludeInclude2(self):
473 m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
474 m2 = matchmod.match(b'', b'', include=[b'path:folder'])
476 m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
477 m2 = matchmod.match(b'/repo', b'', include=[b'path:folder'])
475 478 im = matchmod.intersectmatchers(m1, m2)
476 479 # FIXME: is set() correct here?
477 480 self.assertEqual(im.visitchildrenset(b''), set())
478 481 self.assertEqual(im.visitchildrenset(b'dir'), set())
479 482 self.assertEqual(im.visitchildrenset(b'dir/subdir'), set())
480 483 self.assertEqual(im.visitchildrenset(b'dir/foo'), set())
481 484 self.assertEqual(im.visitchildrenset(b'folder'), set())
482 485 self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), set())
483 486 self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), set())
484 487
485 488 # We're using includematcher instead of patterns because it behaves slightly
486 489 # better (giving narrower results) than patternmatcher.
487 490 def testVisitdirIncludeInclude3(self):
488 m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir/x'])
489 m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
491 m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir/x'])
492 m2 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
490 493 im = matchmod.intersectmatchers(m1, m2)
491 494 self.assertEqual(im.visitdir(b''), True)
492 495 self.assertEqual(im.visitdir(b'dir'), True)
493 496 self.assertEqual(im.visitdir(b'dir/subdir'), True)
494 497 self.assertFalse(im.visitdir(b'dir/foo'))
495 498 self.assertFalse(im.visitdir(b'folder'))
496 499 self.assertFalse(im.visitdir(b'dir/subdir/z'))
497 500 # OPT: this should probably be 'all' not True.
498 501 self.assertEqual(im.visitdir(b'dir/subdir/x'), True)
499 502
500 503 def testVisitchildrensetIncludeInclude3(self):
501 m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir/x'])
502 m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
504 m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir/x'])
505 m2 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
503 506 im = matchmod.intersectmatchers(m1, m2)
504 507 self.assertEqual(im.visitchildrenset(b''), {b'dir'})
505 508 self.assertEqual(im.visitchildrenset(b'dir'), {b'subdir'})
506 509 self.assertEqual(im.visitchildrenset(b'dir/subdir'), {b'x'})
507 510 self.assertEqual(im.visitchildrenset(b'dir/foo'), set())
508 511 self.assertEqual(im.visitchildrenset(b'folder'), set())
509 512 self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), set())
510 513 # OPT: this should probably be 'all' not 'this'.
511 514 self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), b'this')
512 515
513 516 # We're using includematcher instead of patterns because it behaves slightly
514 517 # better (giving narrower results) than patternmatcher.
515 518 def testVisitdirIncludeInclude4(self):
516 m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir/x'])
517 m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir/z'])
519 m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir/x'])
520 m2 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir/z'])
518 521 im = matchmod.intersectmatchers(m1, m2)
519 522 # OPT: these next three could probably be False as well.
520 523 self.assertEqual(im.visitdir(b''), True)
521 524 self.assertEqual(im.visitdir(b'dir'), True)
522 525 self.assertEqual(im.visitdir(b'dir/subdir'), True)
523 526 self.assertFalse(im.visitdir(b'dir/foo'))
524 527 self.assertFalse(im.visitdir(b'folder'))
525 528 self.assertFalse(im.visitdir(b'dir/subdir/z'))
526 529 self.assertFalse(im.visitdir(b'dir/subdir/x'))
527 530
528 531 def testVisitchildrensetIncludeInclude4(self):
529 m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir/x'])
530 m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir/z'])
532 m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir/x'])
533 m2 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir/z'])
531 534 im = matchmod.intersectmatchers(m1, m2)
532 535 # OPT: these next two could probably be set() as well.
533 536 self.assertEqual(im.visitchildrenset(b''), {b'dir'})
534 537 self.assertEqual(im.visitchildrenset(b'dir'), {b'subdir'})
535 538 self.assertEqual(im.visitchildrenset(b'dir/subdir'), set())
536 539 self.assertEqual(im.visitchildrenset(b'dir/foo'), set())
537 540 self.assertEqual(im.visitchildrenset(b'folder'), set())
538 541 self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), set())
539 542 self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), set())
540 543
541 544
542 545 class UnionMatcherTests(unittest.TestCase):
543 546 def testVisitdirM2always(self):
544 547 m1 = matchmod.alwaysmatcher()
545 548 m2 = matchmod.alwaysmatcher()
546 549 um = matchmod.unionmatcher([m1, m2])
547 550 # um should be equivalent to a alwaysmatcher.
548 551 self.assertEqual(um.visitdir(b''), b'all')
549 552 self.assertEqual(um.visitdir(b'dir'), b'all')
550 553 self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
551 554 self.assertEqual(um.visitdir(b'dir/subdir/z'), b'all')
552 555 self.assertEqual(um.visitdir(b'dir/foo'), b'all')
553 556 self.assertEqual(um.visitdir(b'dir/subdir/x'), b'all')
554 557 self.assertEqual(um.visitdir(b'folder'), b'all')
555 558
556 559 def testVisitchildrensetM2always(self):
557 560 m1 = matchmod.alwaysmatcher()
558 561 m2 = matchmod.alwaysmatcher()
559 562 um = matchmod.unionmatcher([m1, m2])
560 563 # um should be equivalent to a alwaysmatcher.
561 564 self.assertEqual(um.visitchildrenset(b''), b'all')
562 565 self.assertEqual(um.visitchildrenset(b'dir'), b'all')
563 566 self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
564 567 self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'all')
565 568 self.assertEqual(um.visitchildrenset(b'dir/foo'), b'all')
566 569 self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'all')
567 570 self.assertEqual(um.visitchildrenset(b'folder'), b'all')
568 571
569 572 def testVisitdirM1never(self):
570 573 m1 = matchmod.nevermatcher()
571 574 m2 = matchmod.alwaysmatcher()
572 575 um = matchmod.unionmatcher([m1, m2])
573 576 # um should be equivalent to a alwaysmatcher.
574 577 self.assertEqual(um.visitdir(b''), b'all')
575 578 self.assertEqual(um.visitdir(b'dir'), b'all')
576 579 self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
577 580 self.assertEqual(um.visitdir(b'dir/subdir/z'), b'all')
578 581 self.assertEqual(um.visitdir(b'dir/foo'), b'all')
579 582 self.assertEqual(um.visitdir(b'dir/subdir/x'), b'all')
580 583 self.assertEqual(um.visitdir(b'folder'), b'all')
581 584
582 585 def testVisitchildrensetM1never(self):
583 586 m1 = matchmod.nevermatcher()
584 587 m2 = matchmod.alwaysmatcher()
585 588 um = matchmod.unionmatcher([m1, m2])
586 589 # um should be equivalent to a alwaysmatcher.
587 590 self.assertEqual(um.visitchildrenset(b''), b'all')
588 591 self.assertEqual(um.visitchildrenset(b'dir'), b'all')
589 592 self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
590 593 self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'all')
591 594 self.assertEqual(um.visitchildrenset(b'dir/foo'), b'all')
592 595 self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'all')
593 596 self.assertEqual(um.visitchildrenset(b'folder'), b'all')
594 597
595 598 def testVisitdirM2never(self):
596 599 m1 = matchmod.alwaysmatcher()
597 600 m2 = matchmod.nevermatcher()
598 601 um = matchmod.unionmatcher([m1, m2])
599 602 # um should be equivalent to a alwaysmatcher.
600 603 self.assertEqual(um.visitdir(b''), b'all')
601 604 self.assertEqual(um.visitdir(b'dir'), b'all')
602 605 self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
603 606 self.assertEqual(um.visitdir(b'dir/subdir/z'), b'all')
604 607 self.assertEqual(um.visitdir(b'dir/foo'), b'all')
605 608 self.assertEqual(um.visitdir(b'dir/subdir/x'), b'all')
606 609 self.assertEqual(um.visitdir(b'folder'), b'all')
607 610
608 611 def testVisitchildrensetM2never(self):
609 612 m1 = matchmod.alwaysmatcher()
610 613 m2 = matchmod.nevermatcher()
611 614 um = matchmod.unionmatcher([m1, m2])
612 615 # um should be equivalent to a alwaysmatcher.
613 616 self.assertEqual(um.visitchildrenset(b''), b'all')
614 617 self.assertEqual(um.visitchildrenset(b'dir'), b'all')
615 618 self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
616 619 self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'all')
617 620 self.assertEqual(um.visitchildrenset(b'dir/foo'), b'all')
618 621 self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'all')
619 622 self.assertEqual(um.visitchildrenset(b'folder'), b'all')
620 623
621 624 def testVisitdirM2SubdirPrefix(self):
622 625 m1 = matchmod.alwaysmatcher()
623 m2 = matchmod.match(b'', b'', patterns=[b'path:dir/subdir'])
626 m2 = matchmod.match(b'/repo', b'', patterns=[b'path:dir/subdir'])
624 627 um = matchmod.unionmatcher([m1, m2])
625 628 self.assertEqual(um.visitdir(b''), b'all')
626 629 self.assertEqual(um.visitdir(b'dir'), b'all')
627 630 self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
628 631 self.assertEqual(um.visitdir(b'dir/foo'), b'all')
629 632 self.assertEqual(um.visitdir(b'folder'), b'all')
630 633 self.assertEqual(um.visitdir(b'dir/subdir/z'), b'all')
631 634 self.assertEqual(um.visitdir(b'dir/subdir/x'), b'all')
632 635
633 636 def testVisitchildrensetM2SubdirPrefix(self):
634 637 m1 = matchmod.alwaysmatcher()
635 m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
638 m2 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
636 639 um = matchmod.unionmatcher([m1, m2])
637 640 self.assertEqual(um.visitchildrenset(b''), b'all')
638 641 self.assertEqual(um.visitchildrenset(b'dir'), b'all')
639 642 self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
640 643 self.assertEqual(um.visitchildrenset(b'dir/foo'), b'all')
641 644 self.assertEqual(um.visitchildrenset(b'folder'), b'all')
642 645 self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'all')
643 646 self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'all')
644 647
645 648 # We're using includematcher instead of patterns because it behaves slightly
646 649 # better (giving narrower results) than patternmatcher.
647 650 def testVisitdirIncludeInclude(self):
648 m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
649 m2 = matchmod.match(b'', b'', include=[b'rootfilesin:dir'])
651 m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
652 m2 = matchmod.match(b'/repo', b'', include=[b'rootfilesin:dir'])
650 653 um = matchmod.unionmatcher([m1, m2])
651 654 self.assertEqual(um.visitdir(b''), True)
652 655 self.assertEqual(um.visitdir(b'dir'), True)
653 656 self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
654 657 self.assertFalse(um.visitdir(b'dir/foo'))
655 658 self.assertFalse(um.visitdir(b'folder'))
656 659 # OPT: These two should probably be 'all' not True.
657 660 self.assertEqual(um.visitdir(b'dir/subdir/z'), True)
658 661 self.assertEqual(um.visitdir(b'dir/subdir/x'), True)
659 662
660 663 def testVisitchildrensetIncludeInclude(self):
661 m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
662 m2 = matchmod.match(b'', b'', include=[b'rootfilesin:dir'])
664 m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
665 m2 = matchmod.match(b'/repo', b'', include=[b'rootfilesin:dir'])
663 666 um = matchmod.unionmatcher([m1, m2])
664 667 self.assertEqual(um.visitchildrenset(b''), {b'dir'})
665 668 self.assertEqual(um.visitchildrenset(b'dir'), b'this')
666 669 self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
667 670 self.assertEqual(um.visitchildrenset(b'dir/foo'), set())
668 671 self.assertEqual(um.visitchildrenset(b'folder'), set())
669 672 # OPT: These next two could be 'all' instead of 'this'.
670 673 self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'this')
671 674 self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'this')
672 675
673 676 # We're using includematcher instead of patterns because it behaves slightly
674 677 # better (giving narrower results) than patternmatcher.
675 678 def testVisitdirIncludeInclude2(self):
676 m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
677 m2 = matchmod.match(b'', b'', include=[b'path:folder'])
679 m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
680 m2 = matchmod.match(b'/repo', b'', include=[b'path:folder'])
678 681 um = matchmod.unionmatcher([m1, m2])
679 682 self.assertEqual(um.visitdir(b''), True)
680 683 self.assertEqual(um.visitdir(b'dir'), True)
681 684 self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
682 685 self.assertFalse(um.visitdir(b'dir/foo'))
683 686 self.assertEqual(um.visitdir(b'folder'), b'all')
684 687 # OPT: These should probably be 'all' not True.
685 688 self.assertEqual(um.visitdir(b'dir/subdir/z'), True)
686 689 self.assertEqual(um.visitdir(b'dir/subdir/x'), True)
687 690
688 691 def testVisitchildrensetIncludeInclude2(self):
689 m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
690 m2 = matchmod.match(b'', b'', include=[b'path:folder'])
692 m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
693 m2 = matchmod.match(b'/repo', b'', include=[b'path:folder'])
691 694 um = matchmod.unionmatcher([m1, m2])
692 695 self.assertEqual(um.visitchildrenset(b''), {b'folder', b'dir'})
693 696 self.assertEqual(um.visitchildrenset(b'dir'), {b'subdir'})
694 697 self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
695 698 self.assertEqual(um.visitchildrenset(b'dir/foo'), set())
696 699 self.assertEqual(um.visitchildrenset(b'folder'), b'all')
697 700 # OPT: These next two could be 'all' instead of 'this'.
698 701 self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'this')
699 702 self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'this')
700 703
701 704 # We're using includematcher instead of patterns because it behaves slightly
702 705 # better (giving narrower results) than patternmatcher.
703 706 def testVisitdirIncludeInclude3(self):
704 m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir/x'])
705 m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
707 m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir/x'])
708 m2 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
706 709 um = matchmod.unionmatcher([m1, m2])
707 710 self.assertEqual(um.visitdir(b''), True)
708 711 self.assertEqual(um.visitdir(b'dir'), True)
709 712 self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
710 713 self.assertFalse(um.visitdir(b'dir/foo'))
711 714 self.assertFalse(um.visitdir(b'folder'))
712 715 self.assertEqual(um.visitdir(b'dir/subdir/x'), b'all')
713 716 # OPT: this should probably be 'all' not True.
714 717 self.assertEqual(um.visitdir(b'dir/subdir/z'), True)
715 718
716 719 def testVisitchildrensetIncludeInclude3(self):
717 m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir/x'])
718 m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
720 m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir/x'])
721 m2 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
719 722 um = matchmod.unionmatcher([m1, m2])
720 723 self.assertEqual(um.visitchildrenset(b''), {b'dir'})
721 724 self.assertEqual(um.visitchildrenset(b'dir'), {b'subdir'})
722 725 self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
723 726 self.assertEqual(um.visitchildrenset(b'dir/foo'), set())
724 727 self.assertEqual(um.visitchildrenset(b'folder'), set())
725 728 self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'all')
726 729 # OPT: this should probably be 'all' not 'this'.
727 730 self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'this')
728 731
729 732 # We're using includematcher instead of patterns because it behaves slightly
730 733 # better (giving narrower results) than patternmatcher.
731 734 def testVisitdirIncludeInclude4(self):
732 m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir/x'])
733 m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir/z'])
735 m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir/x'])
736 m2 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir/z'])
734 737 um = matchmod.unionmatcher([m1, m2])
735 738 # OPT: these next three could probably be False as well.
736 739 self.assertEqual(um.visitdir(b''), True)
737 740 self.assertEqual(um.visitdir(b'dir'), True)
738 741 self.assertEqual(um.visitdir(b'dir/subdir'), True)
739 742 self.assertFalse(um.visitdir(b'dir/foo'))
740 743 self.assertFalse(um.visitdir(b'folder'))
741 744 self.assertEqual(um.visitdir(b'dir/subdir/z'), b'all')
742 745 self.assertEqual(um.visitdir(b'dir/subdir/x'), b'all')
743 746
744 747 def testVisitchildrensetIncludeInclude4(self):
745 m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir/x'])
746 m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir/z'])
748 m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir/x'])
749 m2 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir/z'])
747 750 um = matchmod.unionmatcher([m1, m2])
748 751 self.assertEqual(um.visitchildrenset(b''), {b'dir'})
749 752 self.assertEqual(um.visitchildrenset(b'dir'), {b'subdir'})
750 753 self.assertEqual(um.visitchildrenset(b'dir/subdir'), {b'x', b'z'})
751 754 self.assertEqual(um.visitchildrenset(b'dir/foo'), set())
752 755 self.assertEqual(um.visitchildrenset(b'folder'), set())
753 756 self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'all')
754 757 self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'all')
755 758
756 759
757 760 class SubdirMatcherTests(unittest.TestCase):
758 761 def testVisitdir(self):
759 m = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
762 m = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
760 763 sm = matchmod.subdirmatcher(b'dir', m)
761 764
762 765 self.assertEqual(sm.visitdir(b''), True)
763 766 self.assertEqual(sm.visitdir(b'subdir'), b'all')
764 767 # OPT: These next two should probably be 'all' not True.
765 768 self.assertEqual(sm.visitdir(b'subdir/x'), True)
766 769 self.assertEqual(sm.visitdir(b'subdir/z'), True)
767 770 self.assertFalse(sm.visitdir(b'foo'))
768 771
769 772 def testVisitchildrenset(self):
770 m = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
773 m = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
771 774 sm = matchmod.subdirmatcher(b'dir', m)
772 775
773 776 self.assertEqual(sm.visitchildrenset(b''), {b'subdir'})
774 777 self.assertEqual(sm.visitchildrenset(b'subdir'), b'all')
775 778 # OPT: These next two should probably be 'all' not 'this'.
776 779 self.assertEqual(sm.visitchildrenset(b'subdir/x'), b'this')
777 780 self.assertEqual(sm.visitchildrenset(b'subdir/z'), b'this')
778 781 self.assertEqual(sm.visitchildrenset(b'foo'), set())
779 782
780 783
781 784 class PrefixdirMatcherTests(unittest.TestCase):
782 785 def testVisitdir(self):
783 786 m = matchmod.match(
784 util.localpath(b'root/d'), b'e/f', [b'../a.txt', b'b.txt']
787 util.localpath(b'/root/d'),
788 b'e/f',
789 [b'../a.txt', b'b.txt'],
790 auditor=noop_auditor,
785 791 )
786 792 pm = matchmod.prefixdirmatcher(b'd', m)
787 793
788 794 # `m` elides 'd' because it's part of the root, and the rest of the
789 795 # patterns are relative.
790 796 self.assertEqual(bool(m(b'a.txt')), False)
791 797 self.assertEqual(bool(m(b'b.txt')), False)
792 798 self.assertEqual(bool(m(b'e/a.txt')), True)
793 799 self.assertEqual(bool(m(b'e/b.txt')), False)
794 800 self.assertEqual(bool(m(b'e/f/b.txt')), True)
795 801
796 802 # The prefix matcher re-adds 'd' to the paths, so they need to be
797 803 # specified when using the prefixdirmatcher.
798 804 self.assertEqual(bool(pm(b'a.txt')), False)
799 805 self.assertEqual(bool(pm(b'b.txt')), False)
800 806 self.assertEqual(bool(pm(b'd/e/a.txt')), True)
801 807 self.assertEqual(bool(pm(b'd/e/b.txt')), False)
802 808 self.assertEqual(bool(pm(b'd/e/f/b.txt')), True)
803 809
804 810 self.assertEqual(m.visitdir(b''), True)
805 811 self.assertEqual(m.visitdir(b'e'), True)
806 812 self.assertEqual(m.visitdir(b'e/f'), True)
807 813 self.assertEqual(m.visitdir(b'e/f/g'), False)
808 814
809 815 self.assertEqual(pm.visitdir(b''), True)
810 816 self.assertEqual(pm.visitdir(b'd'), True)
811 817 self.assertEqual(pm.visitdir(b'd/e'), True)
812 818 self.assertEqual(pm.visitdir(b'd/e/f'), True)
813 819 self.assertEqual(pm.visitdir(b'd/e/f/g'), False)
814 820
815 821 def testVisitchildrenset(self):
816 822 m = matchmod.match(
817 util.localpath(b'root/d'), b'e/f', [b'../a.txt', b'b.txt']
823 util.localpath(b'/root/d'),
824 b'e/f',
825 [b'../a.txt', b'b.txt'],
826 auditor=noop_auditor,
818 827 )
819 828 pm = matchmod.prefixdirmatcher(b'd', m)
820 829
821 830 # OPT: visitchildrenset could possibly return {'e'} and {'f'} for these
822 831 # next two, respectively; patternmatcher does not have this
823 832 # optimization.
824 833 self.assertEqual(m.visitchildrenset(b''), b'this')
825 834 self.assertEqual(m.visitchildrenset(b'e'), b'this')
826 835 self.assertEqual(m.visitchildrenset(b'e/f'), b'this')
827 836 self.assertEqual(m.visitchildrenset(b'e/f/g'), set())
828 837
829 838 # OPT: visitchildrenset could possibly return {'d'}, {'e'}, and {'f'}
830 839 # for these next three, respectively; patternmatcher does not have this
831 840 # optimization.
832 841 self.assertEqual(pm.visitchildrenset(b''), b'this')
833 842 self.assertEqual(pm.visitchildrenset(b'd'), b'this')
834 843 self.assertEqual(pm.visitchildrenset(b'd/e'), b'this')
835 844 self.assertEqual(pm.visitchildrenset(b'd/e/f'), b'this')
836 845 self.assertEqual(pm.visitchildrenset(b'd/e/f/g'), set())
837 846
838 847
839 848 if __name__ == '__main__':
840 849 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now