Mercurial > hg-git
view hggit/gitdirstate.py @ 1092:3799bf885c1d
compat: use newer read_pkt_refs from dulwich if possible
Beginning with dulwich 0.18, it now supports reporting the symrefs so we
no longer need to monkey patch (will require future patches to use the
new code, though).
author | Sean Farley <sean@farley.io> |
---|---|
date | Mon, 27 Nov 2017 17:45:51 -0500 |
parents | cf982a23e15c |
children | 94aaea5c65f7 |
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) else: pats = [(f, ['include:%s' % f]) for f in files] for f, patlist in pats: allpats.extend(patlist) 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: if not ignoremod: # in this case, patlist is ['include: FILE'], and # inst[0] should already include FILE raise 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()