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