2014-02-07 19:43:49 +04:00
|
|
|
import os, stat, re, errno
|
2013-11-27 18:27:59 +04:00
|
|
|
|
|
|
|
from mercurial import dirstate
|
|
|
|
from mercurial import hg
|
|
|
|
from mercurial import ignore
|
|
|
|
from mercurial import match as matchmod
|
|
|
|
from mercurial import osutil
|
|
|
|
from mercurial import scmutil
|
|
|
|
# pathauditor moved to pathutil in 2.8
|
|
|
|
try:
|
|
|
|
from mercurial import pathutil
|
|
|
|
pathutil.pathauditor
|
|
|
|
except:
|
|
|
|
pathutil = scmutil
|
|
|
|
from mercurial import util
|
|
|
|
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
|
|
|
|
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(orig, root, files, warn, extrapatterns=None):
|
|
|
|
pats = ignore.readpats(root, files, warn)
|
|
|
|
allpats = []
|
|
|
|
if extrapatterns:
|
|
|
|
allpats.extend(extrapatterns)
|
|
|
|
for f, patlist in pats:
|
|
|
|
allpats.extend(patlist)
|
|
|
|
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)
|
|
|
|
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 ignore.ignore(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
|
|
|
|
full=True
|
|
|
|
|
|
|
|
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
|
|
|
|
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)
|
|
|
|
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()
|