diff --git a/mercurial/match.py b/mercurial/match.py --- a/mercurial/match.py +++ b/mercurial/match.py @@ -42,6 +42,30 @@ def _expandsets(kindpats, ctx, listsubre other.append((kind, pat, source)) return fset, other +def _expandsubinclude(kindpats, root): + '''Returns the list of subinclude matchers and the kindpats without the + subincludes in it.''' + relmatchers = [] + other = [] + + for kind, pat, source in kindpats: + if kind == 'subinclude': + sourceroot = pathutil.dirname(source) + pat = util.pconvert(pat) + path = pathutil.join(sourceroot, pat) + + newroot = pathutil.dirname(path) + relmatcher = match(newroot, '', [], ['include:%s' % path]) + + prefix = pathutil.canonpath(root, root, newroot) + if prefix: + prefix += '/' + relmatchers.append((prefix, relmatcher)) + else: + other.append((kind, pat, source)) + + return relmatchers, other + def _kindpatsalwaysmatch(kindpats): """"Checks whether the kindspats match everything, as e.g. 'relpath:.' does. @@ -76,6 +100,8 @@ class match(object): 'relre:' - a regexp that needn't match the start of a name 'set:' - a fileset expression 'include:' - a file of patterns to read and include + 'subinclude:' - a file of patterns to match against files under + the same directory '' - a pattern of the specified default type """ @@ -375,7 +401,7 @@ def _patsplit(pattern, default): if ':' in pattern: kind, pat = pattern.split(':', 1) if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre', - 'listfile', 'listfile0', 'set', 'include'): + 'listfile', 'listfile0', 'set', 'include', 'subinclude'): return kind, pat return default, pattern @@ -481,6 +507,15 @@ def _buildmatch(ctx, kindpats, globsuffi globsuffix is appended to the regexp of globs.''' matchfuncs = [] + subincludes, kindpats = _expandsubinclude(kindpats, root) + if subincludes: + def matchsubinclude(f): + for prefix, mf in subincludes: + if f.startswith(prefix) and mf(f[len(prefix):]): + return True + return False + matchfuncs.append(matchsubinclude) + fset, kindpats = _expandsets(kindpats, ctx, listsubrepos) if fset: matchfuncs.append(fset.__contains__) @@ -577,7 +612,7 @@ def readpatternfile(filepath, warn): pattern # pattern of the current default type''' syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:', - 'include': 'include'} + 'include': 'include', 'subinclude': 'subinclude'} syntax = 'relre:' patterns = [] diff --git a/tests/test-hgignore.t b/tests/test-hgignore.t --- a/tests/test-hgignore.t +++ b/tests/test-hgignore.t @@ -190,7 +190,55 @@ Check recursive uses of 'include:' $ hg status A dir/b.o + $ cp otherignore goodignore $ echo "include:badignore" >> otherignore $ hg status skipping unreadable pattern file 'badignore': No such file or directory A dir/b.o + + $ mv goodignore otherignore + +Check including subincludes + + $ hg revert -q --all + $ hg purge --all --config extensions.purge= + $ echo ".hgignore" > .hgignore + $ mkdir dir1 dir2 + $ touch dir1/file1 dir1/file2 dir2/file1 dir2/file2 + $ echo "subinclude:dir2/.hgignore" >> .hgignore + $ echo "glob:file*2" > dir2/.hgignore + $ hg status + ? dir1/file1 + ? dir1/file2 + ? dir2/file1 + +Check including subincludes with regexs + + $ echo "subinclude:dir1/.hgignore" >> .hgignore + $ echo "regexp:f.le1" > dir1/.hgignore + + $ hg status + ? dir1/file2 + ? dir2/file1 + +Check multiple levels of sub-ignores + + $ mkdir dir1/subdir + $ touch dir1/subdir/subfile1 dir1/subdir/subfile3 dir1/subdir/subfile4 + $ echo "subinclude:subdir/.hgignore" >> dir1/.hgignore + $ echo "glob:subfil*3" >> dir1/subdir/.hgignore + + $ hg status + ? dir1/file2 + ? dir1/subdir/subfile4 + ? dir2/file1 + +Check include subignore at the same level + + $ mv dir1/subdir/.hgignore dir1/.hgignoretwo + $ echo "regexp:f.le1" > dir1/.hgignore + $ echo "subinclude:.hgignoretwo" >> dir1/.hgignore + $ echo "glob:file*2" > dir1/.hgignoretwo + + $ hg status | grep file2 + [1]