Mercurial > hg > hg-git
view hggit/gitdirstate.py @ 1038:e3dab807e38c
compat: delay importing osutil until we know we need it
Since 006c837f9181 we don't need osutil on hg >= 4.3. When demandimport
is enabled, this extra import doesn't hurt anything, but it breaks
doctests.
author | Kevin Bullock <kbullock@ringworld.org> |
---|---|
date | Sun, 06 Aug 2017 09:45:35 -0500 |
parents | 006c837f9181 |
children | cf982a23e15c |
line wrap: on
line source
import os import stat import re import errno from mercurial import ( dirstate, match as matchmod, scmutil, util, ) try: from mercurial import ignore ignore.readpats ignoremod = True except (AttributeError, ImportError): # ignore module was removed in Mercurial 3.5 ignoremod = False # pathauditor moved to pathutil in 2.9 try: from mercurial import pathutil pathutil.pathauditor except (AttributeError, ImportError): pathutil = scmutil from mercurial.i18n import _ def gignorepats(orig, lines, root=None): '''parse lines (iterable) of .gitignore text, returning a tuple of (patterns, parse errors). These patterns should be given to compile() to be validated and converted into a match function.''' syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'} syntax = 'glob:' patterns = [] warnings = [] for line in lines: if "#" in line: _commentre = re.compile(r'((^|[^\\])(\\\\)*)#.*') # remove comments prefixed by an even number of escapes line = _commentre.sub(r'\1', line) # fixup properly escaped comments that survived the above line = line.replace("\\#", "#") line = line.rstrip() if not line: continue if line.startswith('!'): warnings.append(_("unsupported ignore pattern '%s'") % line) continue if re.match(r'(:?.*/)?\.hg(:?/|$)', line): continue rootprefix = '%s/' % root if root else '' if line.startswith('/'): line = line[1:] rootsuffixes = [''] else: rootsuffixes = ['', '**/'] for rootsuffix in rootsuffixes: pat = syntax + rootprefix + rootsuffix + line for s, rels in syntaxes.iteritems(): if line.startswith(rels): pat = line break elif line.startswith(s + ':'): pat = rels + line[len(s) + 1:] break patterns.append(pat) return patterns, warnings def gignore(root, files, warn, extrapatterns=None): allpats = [] pats = [] if ignoremod: pats = ignore.readpats(root, files, warn) for f, patlist in pats: allpats.extend(patlist) else: allpats.extend(['include:%s' % f for f in files]) if extrapatterns: allpats.extend(extrapatterns) if not allpats: return util.never try: ignorefunc = matchmod.match(root, '', [], allpats) except util.Abort: for f, patlist in pats: try: matchmod.match(root, '', [], patlist) except util.Abort, inst: raise util.Abort('%s: %s' % (f, inst[0])) if extrapatterns: try: matchmod.match(root, '', [], extrapatterns) except util.Abort, inst: raise util.Abort('%s: %s' % ('extra patterns', inst[0])) return ignorefunc class gitdirstate(dirstate.dirstate): @dirstate.rootcache('.hgignore') def _ignore(self): files = [self._join('.hgignore')] for name, path in self._ui.configitems("ui"): if name == 'ignore' or name.startswith('ignore.'): files.append(util.expandpath(path)) patterns = [] # Only use .gitignore if there's no .hgignore try: fp = open(files[0]) fp.close() except: fns = self._finddotgitignores() for fn in fns: d = os.path.dirname(fn) fn = self.pathto(fn) if not os.path.exists(fn): continue fp = open(fn) pats, warnings = gignorepats(None, fp, root=d) for warning in warnings: self._ui.warn("%s: %s\n" % (fn, warning)) patterns.extend(pats) return gignore(self._root, files, self._ui.warn, extrapatterns=patterns) def _finddotgitignores(self): """A copy of dirstate.walk. This is called from the new _ignore method, which is called by dirstate.walk, which would cause infinite recursion, except _finddotgitignores calls the superclass _ignore directly.""" match = matchmod.match(self._root, self.getcwd(), ['relglob:.gitignore']) # TODO: need subrepos? subrepos = [] unknown = True ignored = False def fwarn(f, msg): self._ui.warn('%s: %s\n' % (self.pathto(f), msg)) return False ignore = super(gitdirstate, self)._ignore dirignore = self._dirignore if ignored: ignore = util.never dirignore = util.never elif not unknown: # if unknown and ignored are False, skip step 2 ignore = util.always dirignore = util.always matchfn = match.matchfn matchalways = match.always() matchtdir = match.traversedir dmap = self._map # osutil moved in hg 4.3, but util re-exports listdir try: listdir = util.listdir except AttributeError: from mercurial import osutil listdir = osutil.listdir lstat = os.lstat dirkind = stat.S_IFDIR regkind = stat.S_IFREG lnkkind = stat.S_IFLNK join = self._join exact = skipstep3 = False if matchfn == match.exact: # match.exact exact = True dirignore = util.always # skip step 2 elif match.files() and not match.anypats(): # match.match, no patterns skipstep3 = True if not exact and self._checkcase: normalize = self._normalize skipstep3 = False else: normalize = None # step 1: find all explicit files results, work, dirsnotfound = self._walkexplicit(match, subrepos) skipstep3 = skipstep3 and not (work or dirsnotfound) if work and isinstance(work[0], tuple): # Mercurial >= 3.3.3 work = [nd for nd, d in work if not dirignore(d)] else: work = [d for d in work if not dirignore(d)] wadd = work.append # step 2: visit subdirectories while work: nd = work.pop() skip = None if nd == '.': nd = '' else: skip = '.hg' try: entries = listdir(join(nd), stat=True, skip=skip) except OSError, inst: if inst.errno in (errno.EACCES, errno.ENOENT): fwarn(nd, inst.strerror) continue raise for f, kind, st in entries: if normalize: nf = normalize(nd and (nd + "/" + f) or f, True, True) else: nf = nd and (nd + "/" + f) or f if nf not in results: if kind == dirkind: if not ignore(nf): if matchtdir: matchtdir(nf) wadd(nf) if nf in dmap and (matchalways or matchfn(nf)): results[nf] = None elif kind == regkind or kind == lnkkind: if nf in dmap: if matchalways or matchfn(nf): results[nf] = st elif (matchalways or matchfn(nf)) and not ignore(nf): results[nf] = st elif nf in dmap and (matchalways or matchfn(nf)): results[nf] = None for s in subrepos: del results[s] del results['.hg'] # step 3: report unseen items in the dmap hash if not skipstep3 and not exact: if not results and matchalways: visit = dmap.keys() else: visit = [f for f in dmap if f not in results and matchfn(f)] visit.sort() if unknown: # unknown == True means we walked the full directory tree # above. So if a file is not seen it was either a) not matching # matchfn b) ignored, c) missing, or d) under a symlink # directory. audit_path = pathutil.pathauditor(self._root) for nf in iter(visit): # Report ignored items in the dmap as long as they are not # under a symlink directory. if audit_path.check(nf): try: results[nf] = lstat(join(nf)) except OSError: # file doesn't exist results[nf] = None else: # It's either missing or under a symlink directory results[nf] = None else: # We may not have walked the full directory tree above, # so stat everything we missed. nf = iter(visit).next for st in util.statfiles([join(i) for i in visit]): results[nf()] = st return results.keys()