mercurial/match.py
changeset 42341 27d6956d386b
parent 42329 c7652f7440d9
child 42343 d8e55c0c642c
equal deleted inserted replaced
42340:7ada598941d2 42341:27d6956d386b
   378         Example:
   378         Example:
   379           Assume matchers ['path:foo/bar', 'rootfilesin:qux'], we would return
   379           Assume matchers ['path:foo/bar', 'rootfilesin:qux'], we would return
   380           the following values (assuming the implementation of visitchildrenset
   380           the following values (assuming the implementation of visitchildrenset
   381           is capable of recognizing this; some implementations are not).
   381           is capable of recognizing this; some implementations are not).
   382 
   382 
   383           '.' -> {'foo', 'qux'}
   383           '' -> {'foo', 'qux'}
   384           'baz' -> set()
   384           'baz' -> set()
   385           'foo' -> {'bar'}
   385           'foo' -> {'bar'}
   386           # Ideally this would be 'all', but since the prefix nature of matchers
   386           # Ideally this would be 'all', but since the prefix nature of matchers
   387           # is applied to the entire matcher, we have to downgrade this to
   387           # is applied to the entire matcher, we have to downgrade this to
   388           # 'this' due to the non-prefix 'rootfilesin'-kind matcher being mixed
   388           # 'this' due to the non-prefix 'rootfilesin'-kind matcher being mixed
   481     def __repr__(self):
   481     def __repr__(self):
   482         s = (stringutil.buildrepr(self._predrepr)
   482         s = (stringutil.buildrepr(self._predrepr)
   483              or pycompat.byterepr(self.matchfn))
   483              or pycompat.byterepr(self.matchfn))
   484         return '<predicatenmatcher pred=%s>' % s
   484         return '<predicatenmatcher pred=%s>' % s
   485 
   485 
       
   486 def normalizerootdir(dir, funcname):
       
   487     if dir == '.':
       
   488         util.nouideprecwarn("match.%s() no longer accepts "
       
   489                             "'.', use '' instead." % funcname, '5.1')
       
   490         return ''
       
   491     return dir
       
   492 
       
   493 
   486 class patternmatcher(basematcher):
   494 class patternmatcher(basematcher):
   487     """Matches a set of (kind, pat, source) against a 'root' directory.
   495     """Matches a set of (kind, pat, source) against a 'root' directory.
   488 
   496 
   489     >>> kindpats = [
   497     >>> kindpats = [
   490     ...     (b're', br'.*\.c$', b''),
   498     ...     (b're', br'.*\.c$', b''),
   505     True
   513     True
   506     >>> m(b'lib.h')  # matches glob:*.h
   514     >>> m(b'lib.h')  # matches glob:*.h
   507     True
   515     True
   508 
   516 
   509     >>> m.files()
   517     >>> m.files()
   510     ['.', 'foo/a', 'b', '.']
   518     ['', 'foo/a', 'b', '']
   511     >>> m.exact(b'foo/a')
   519     >>> m.exact(b'foo/a')
   512     True
   520     True
   513     >>> m.exact(b'b')
   521     >>> m.exact(b'b')
   514     True
   522     True
   515     >>> m.exact(b'lib.h')  # exact matches are for (rel)path kinds
   523     >>> m.exact(b'lib.h')  # exact matches are for (rel)path kinds
   523         self._prefix = _prefix(kindpats)
   531         self._prefix = _prefix(kindpats)
   524         self._pats, self.matchfn = _buildmatch(kindpats, '$', root)
   532         self._pats, self.matchfn = _buildmatch(kindpats, '$', root)
   525 
   533 
   526     @propertycache
   534     @propertycache
   527     def _dirs(self):
   535     def _dirs(self):
   528         return set(util.dirs(self._fileset)) | {'.'}
   536         return set(util.dirs(self._fileset)) | {''}
   529 
   537 
   530     def visitdir(self, dir):
   538     def visitdir(self, dir):
       
   539         dir = normalizerootdir(dir, 'visitdir')
   531         if self._prefix and dir in self._fileset:
   540         if self._prefix and dir in self._fileset:
   532             return 'all'
   541             return 'all'
   533         return ('.' in self._fileset or
   542         return ('' in self._fileset or
   534                 dir in self._fileset or
   543                 dir in self._fileset or
   535                 dir in self._dirs or
   544                 dir in self._dirs or
   536                 any(parentdir in self._fileset
   545                 any(parentdir in self._fileset
   537                     for parentdir in util.finddirs(dir)))
   546                     for parentdir in util.finddirs(dir)))
   538 
   547 
   562         addpath = self.addpath
   571         addpath = self.addpath
   563         for f in paths:
   572         for f in paths:
   564             addpath(f)
   573             addpath(f)
   565 
   574 
   566     def addpath(self, path):
   575     def addpath(self, path):
   567         if path == '.':
   576         if path == '':
   568             return
   577             return
   569         dirs = self._dirs
   578         dirs = self._dirs
   570         findsplitdirs = _dirchildren._findsplitdirs
   579         findsplitdirs = _dirchildren._findsplitdirs
   571         for d, b in findsplitdirs(path):
   580         for d, b in findsplitdirs(path):
   572             if d not in self._onlyinclude:
   581             if d not in self._onlyinclude:
   578         # yields (dirname, basename) tuples, walking back to the root.  This is
   587         # yields (dirname, basename) tuples, walking back to the root.  This is
   579         # very similar to util.finddirs, except:
   588         # very similar to util.finddirs, except:
   580         #  - produces a (dirname, basename) tuple, not just 'dirname'
   589         #  - produces a (dirname, basename) tuple, not just 'dirname'
   581         #  - includes root dir
   590         #  - includes root dir
   582         # Unlike manifest._splittopdir, this does not suffix `dirname` with a
   591         # Unlike manifest._splittopdir, this does not suffix `dirname` with a
   583         # slash, and produces '.' for the root instead of ''.
   592         # slash.
   584         oldpos = len(path)
   593         oldpos = len(path)
   585         pos = path.rfind('/')
   594         pos = path.rfind('/')
   586         while pos != -1:
   595         while pos != -1:
   587             yield path[:pos], path[pos + 1:oldpos]
   596             yield path[:pos], path[pos + 1:oldpos]
   588             oldpos = pos
   597             oldpos = pos
   589             pos = path.rfind('/', 0, pos)
   598             pos = path.rfind('/', 0, pos)
   590         yield '.', path[:oldpos]
   599         yield '', path[:oldpos]
   591 
   600 
   592     def get(self, path):
   601     def get(self, path):
   593         return self._dirs.get(path, set())
   602         return self._dirs.get(path, set())
   594 
   603 
   595 class includematcher(basematcher):
   604 class includematcher(basematcher):
   607         # parents are directories which are non-recursively included because
   616         # parents are directories which are non-recursively included because
   608         # they are needed to get to items in _dirs or _roots.
   617         # they are needed to get to items in _dirs or _roots.
   609         self._parents = set(parents)
   618         self._parents = set(parents)
   610 
   619 
   611     def visitdir(self, dir):
   620     def visitdir(self, dir):
       
   621         dir = normalizerootdir(dir, 'visitdir')
   612         if self._prefix and dir in self._roots:
   622         if self._prefix and dir in self._roots:
   613             return 'all'
   623             return 'all'
   614         return ('.' in self._roots or
   624         return ('' in self._roots or
   615                 dir in self._roots or
   625                 dir in self._roots or
   616                 dir in self._dirs or
   626                 dir in self._dirs or
   617                 dir in self._parents or
   627                 dir in self._parents or
   618                 any(parentdir in self._roots
   628                 any(parentdir in self._roots
   619                     for parentdir in util.finddirs(dir)))
   629                     for parentdir in util.finddirs(dir)))
   633     def visitchildrenset(self, dir):
   643     def visitchildrenset(self, dir):
   634         if self._prefix and dir in self._roots:
   644         if self._prefix and dir in self._roots:
   635             return 'all'
   645             return 'all'
   636         # Note: this does *not* include the 'dir in self._parents' case from
   646         # Note: this does *not* include the 'dir in self._parents' case from
   637         # visitdir, that's handled below.
   647         # visitdir, that's handled below.
   638         if ('.' in self._roots or
   648         if ('' in self._roots or
   639             dir in self._roots or
   649             dir in self._roots or
   640             dir in self._dirs or
   650             dir in self._dirs or
   641             any(parentdir in self._roots
   651             any(parentdir in self._roots
   642                 for parentdir in util.finddirs(dir))):
   652                 for parentdir in util.finddirs(dir))):
   643             return 'this'
   653             return 'this'
   681 
   691 
   682     matchfn = basematcher.exact
   692     matchfn = basematcher.exact
   683 
   693 
   684     @propertycache
   694     @propertycache
   685     def _dirs(self):
   695     def _dirs(self):
   686         return set(util.dirs(self._fileset)) | {'.'}
   696         return set(util.dirs(self._fileset)) | {''}
   687 
   697 
   688     def visitdir(self, dir):
   698     def visitdir(self, dir):
       
   699         dir = normalizerootdir(dir, 'visitdir')
   689         return dir in self._dirs
   700         return dir in self._dirs
   690 
   701 
   691     def visitchildrenset(self, dir):
   702     def visitchildrenset(self, dir):
       
   703         dir = normalizerootdir(dir, 'visitchildrenset')
       
   704 
   692         if not self._fileset or dir not in self._dirs:
   705         if not self._fileset or dir not in self._dirs:
   693             return set()
   706             return set()
   694 
   707 
   695         candidates = self._fileset | self._dirs - {'.'}
   708         candidates = self._fileset | self._dirs - {''}
   696         if dir != '.':
   709         if dir != '':
   697             d = dir + '/'
   710             d = dir + '/'
   698             candidates = set(c[len(d):] for c in candidates if
   711             candidates = set(c[len(d):] for c in candidates if
   699                              c.startswith(d))
   712                              c.startswith(d))
   700         # self._dirs includes all of the directories, recursively, so if
   713         # self._dirs includes all of the directories, recursively, so if
   701         # we're attempting to match foo/bar/baz.txt, it'll have '.', 'foo',
   714         # we're attempting to match foo/bar/baz.txt, it'll have '', 'foo',
   702         # 'foo/bar' in it. Thus we can safely ignore a candidate that has a
   715         # 'foo/bar' in it. Thus we can safely ignore a candidate that has a
   703         # '/' in it, indicating a it's for a subdir-of-a-subdir; the
   716         # '/' in it, indicating a it's for a subdir-of-a-subdir; the
   704         # immediate subdir will be in there without a slash.
   717         # immediate subdir will be in there without a slash.
   705         ret = {c for c in candidates if '/' not in c}
   718         ret = {c for c in candidates if '/' not in c}
   706         # We really do not expect ret to be empty, since that would imply that
   719         # We really do not expect ret to be empty, since that would imply that
   770             # subdirectory.
   783             # subdirectory.
   771             return 'this'
   784             return 'this'
   772         # Possible values for m1:         set(...), set()
   785         # Possible values for m1:         set(...), set()
   773         # Possible values for m2: 'this', set(...)
   786         # Possible values for m2: 'this', set(...)
   774         # We ignore m2's set results. They're possibly incorrect:
   787         # We ignore m2's set results. They're possibly incorrect:
   775         #  m1 = path:dir/subdir, m2=rootfilesin:dir, visitchildrenset('.'):
   788         #  m1 = path:dir/subdir, m2=rootfilesin:dir, visitchildrenset(''):
   776         #    m1 returns {'dir'}, m2 returns {'dir'}, if we subtracted we'd
   789         #    m1 returns {'dir'}, m2 returns {'dir'}, if we subtracted we'd
   777         #    return set(), which is *not* correct, we still need to visit 'dir'!
   790         #    return set(), which is *not* correct, we still need to visit 'dir'!
   778         return m1_set
   791         return m1_set
   779 
   792 
   780     def isexact(self):
   793     def isexact(self):
   916         # from the inputs. Instead, we override matchfn() and visitdir() to
   929         # from the inputs. Instead, we override matchfn() and visitdir() to
   917         # call the original matcher with the subdirectory path prepended.
   930         # call the original matcher with the subdirectory path prepended.
   918         return self._matcher.matchfn(self._path + "/" + f)
   931         return self._matcher.matchfn(self._path + "/" + f)
   919 
   932 
   920     def visitdir(self, dir):
   933     def visitdir(self, dir):
   921         if dir == '.':
   934         dir = normalizerootdir(dir, 'visitdir')
       
   935         if dir == '':
   922             dir = self._path
   936             dir = self._path
   923         else:
   937         else:
   924             dir = self._path + "/" + dir
   938             dir = self._path + "/" + dir
   925         return self._matcher.visitdir(dir)
   939         return self._matcher.visitdir(dir)
   926 
   940 
   927     def visitchildrenset(self, dir):
   941     def visitchildrenset(self, dir):
   928         if dir == '.':
   942         dir = normalizerootdir(dir, 'visitchildrenset')
       
   943         if dir == '':
   929             dir = self._path
   944             dir = self._path
   930         else:
   945         else:
   931             dir = self._path + "/" + dir
   946             dir = self._path + "/" + dir
   932         return self._matcher.visitchildrenset(dir)
   947         return self._matcher.visitchildrenset(dir)
   933 
   948 
   992             return False
  1007             return False
   993         return self._matcher.matchfn(f[len(self._pathprefix):])
  1008         return self._matcher.matchfn(f[len(self._pathprefix):])
   994 
  1009 
   995     @propertycache
  1010     @propertycache
   996     def _pathdirs(self):
  1011     def _pathdirs(self):
   997         return set(util.finddirs(self._path)) | {'.'}
  1012         return set(util.finddirs(self._path)) | {''}
   998 
  1013 
   999     def visitdir(self, dir):
  1014     def visitdir(self, dir):
  1000         if dir == self._path:
  1015         if dir == self._path:
  1001             return self._matcher.visitdir('.')
  1016             return self._matcher.visitdir('')
  1002         if dir.startswith(self._pathprefix):
  1017         if dir.startswith(self._pathprefix):
  1003             return self._matcher.visitdir(dir[len(self._pathprefix):])
  1018             return self._matcher.visitdir(dir[len(self._pathprefix):])
  1004         return dir in self._pathdirs
  1019         return dir in self._pathdirs
  1005 
  1020 
  1006     def visitchildrenset(self, dir):
  1021     def visitchildrenset(self, dir):
  1007         if dir == self._path:
  1022         if dir == self._path:
  1008             return self._matcher.visitchildrenset('.')
  1023             return self._matcher.visitchildrenset('')
  1009         if dir.startswith(self._pathprefix):
  1024         if dir.startswith(self._pathprefix):
  1010             return self._matcher.visitchildrenset(dir[len(self._pathprefix):])
  1025             return self._matcher.visitchildrenset(dir[len(self._pathprefix):])
  1011         if dir in self._pathdirs:
  1026         if dir in self._pathdirs:
  1012             return 'this'
  1027             return 'this'
  1013         return set()
  1028         return set()
  1195         except rustext.filepatterns.PatternError:
  1210         except rustext.filepatterns.PatternError:
  1196             raise error.ProgrammingError(
  1211             raise error.ProgrammingError(
  1197                 'not a regex pattern: %s:%s' % (kind, pat)
  1212                 'not a regex pattern: %s:%s' % (kind, pat)
  1198             )
  1213             )
  1199 
  1214 
  1200     if not pat:
  1215     if not pat and kind in ('glob', 'relpath'):
  1201         return ''
  1216         return ''
  1202     if kind == 're':
  1217     if kind == 're':
  1203         return pat
  1218         return pat
  1204     if kind in ('path', 'relpath'):
  1219     if kind in ('path', 'relpath'):
  1205         if pat == '.':
  1220         if pat == '.':
  1339             root = []
  1354             root = []
  1340             for p in pat.split('/'):
  1355             for p in pat.split('/'):
  1341                 if '[' in p or '{' in p or '*' in p or '?' in p:
  1356                 if '[' in p or '{' in p or '*' in p or '?' in p:
  1342                     break
  1357                     break
  1343                 root.append(p)
  1358                 root.append(p)
  1344             r.append('/'.join(root) or '.')
  1359             r.append('/'.join(root))
  1345         elif kind in ('relpath', 'path'):
  1360         elif kind in ('relpath', 'path'):
  1346             r.append(pat or '.')
  1361             if pat == '.':
       
  1362                 pat = ''
       
  1363             r.append(pat)
  1347         elif kind in ('rootfilesin',):
  1364         elif kind in ('rootfilesin',):
  1348             d.append(pat or '.')
  1365             if pat == '.':
       
  1366                 pat = ''
       
  1367             d.append(pat)
  1349         else: # relglob, re, relre
  1368         else: # relglob, re, relre
  1350             r.append('.')
  1369             r.append('')
  1351     return r, d
  1370     return r, d
  1352 
  1371 
  1353 def _roots(kindpats):
  1372 def _roots(kindpats):
  1354     '''Returns root directories to match recursively from the given patterns.'''
  1373     '''Returns root directories to match recursively from the given patterns.'''
  1355     roots, dirs = _patternrootsanddirs(kindpats)
  1374     roots, dirs = _patternrootsanddirs(kindpats)
  1365     Returns a tuple of (roots, dirs, parents).
  1384     Returns a tuple of (roots, dirs, parents).
  1366 
  1385 
  1367     >>> _rootsdirsandparents(
  1386     >>> _rootsdirsandparents(
  1368     ...     [(b'glob', b'g/h/*', b''), (b'glob', b'g/h', b''),
  1387     ...     [(b'glob', b'g/h/*', b''), (b'glob', b'g/h', b''),
  1369     ...      (b'glob', b'g*', b'')])
  1388     ...      (b'glob', b'g*', b'')])
  1370     (['g/h', 'g/h', '.'], [], ['g', '.'])
  1389     (['g/h', 'g/h', ''], [], ['g', ''])
  1371     >>> _rootsdirsandparents(
  1390     >>> _rootsdirsandparents(
  1372     ...     [(b'rootfilesin', b'g/h', b''), (b'rootfilesin', b'', b'')])
  1391     ...     [(b'rootfilesin', b'g/h', b''), (b'rootfilesin', b'', b'')])
  1373     ([], ['g/h', '.'], ['g', '.'])
  1392     ([], ['g/h', ''], ['g', ''])
  1374     >>> _rootsdirsandparents(
  1393     >>> _rootsdirsandparents(
  1375     ...     [(b'relpath', b'r', b''), (b'path', b'p/p', b''),
  1394     ...     [(b'relpath', b'r', b''), (b'path', b'p/p', b''),
  1376     ...      (b'path', b'', b'')])
  1395     ...      (b'path', b'', b'')])
  1377     (['r', 'p/p', '.'], [], ['p', '.'])
  1396     (['r', 'p/p', ''], [], ['p', ''])
  1378     >>> _rootsdirsandparents(
  1397     >>> _rootsdirsandparents(
  1379     ...     [(b'relglob', b'rg*', b''), (b're', b're/', b''),
  1398     ...     [(b'relglob', b'rg*', b''), (b're', b're/', b''),
  1380     ...      (b'relre', b'rr', b'')])
  1399     ...      (b'relre', b'rr', b'')])
  1381     (['.', '.', '.'], [], ['.'])
  1400     (['', '', ''], [], [''])
  1382     '''
  1401     '''
  1383     r, d = _patternrootsanddirs(kindpats)
  1402     r, d = _patternrootsanddirs(kindpats)
  1384 
  1403 
  1385     p = []
  1404     p = []
  1386     # Append the parents as non-recursive/exact directories, since they must be
  1405     # Append the parents as non-recursive/exact directories, since they must be
  1387     # scanned to get to either the roots or the other exact directories.
  1406     # scanned to get to either the roots or the other exact directories.
  1388     p.extend(util.dirs(d))
  1407     p.extend(util.dirs(d))
  1389     p.extend(util.dirs(r))
  1408     p.extend(util.dirs(r))
  1390     # util.dirs() does not include the root directory, so add it manually
  1409     # util.dirs() does not include the root directory, so add it manually
  1391     p.append('.')
  1410     p.append('')
  1392 
  1411 
  1393     # FIXME: all uses of this function convert these to sets, do so before
  1412     # FIXME: all uses of this function convert these to sets, do so before
  1394     # returning.
  1413     # returning.
  1395     # FIXME: all uses of this function do not need anything in 'roots' and
  1414     # FIXME: all uses of this function do not need anything in 'roots' and
  1396     # 'dirs' to also be in 'parents', consider removing them before returning.
  1415     # 'dirs' to also be in 'parents', consider removing them before returning.