[PATCH 3 of 4] match: enable 'relinclude:' syntax

Durham Goode durham at fb.com
Wed May 20 21:57:35 UTC 2015


# HG changeset patch
# User Durham Goode <durham at fb.com>
# Date 1431818705 25200
#      Sat May 16 16:25:05 2015 -0700
# Node ID 3bbfdda372e8e4204a5440009bfbc9e81f2a93e9
# Parent  28ac58249dbc906622e368357daadd4814f9c71c
match: enable 'relinclude:' syntax

This adds a new rule syntax that allows the user to include a pattern file, but
only have those patterns match against files underneath the subdirectory of the
pattern file.

This is useful when you have nested projects in a repository and the inner
projects wants to set up ignore rules that won't affect other projects in the
repository. It is also useful in high commit rate repositories for removing the
root .hgignore as a point of contention.

diff --git a/mercurial/match.py b/mercurial/match.py
--- a/mercurial/match.py
+++ b/mercurial/match.py
@@ -5,7 +5,7 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import re
+import os, re
 import util, pathutil
 from i18n import _
 
@@ -42,6 +42,25 @@ 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 = os.path.dirname(source)
+            path = os.path.join(sourceroot, pat)
+            newroot = os.path.dirname(path)
+            relmatcher = match(newroot, '', [], ['include:%s' % path])
+            prefix = os.path.relpath(newroot, root) + '/'
+            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 +95,8 @@ class match(object):
         'relre:<regexp>' - a regexp that needn't match the start of a name
         'set:<fileset>' - a fileset expression
         'include:<path>' - a file of patterns to read and include
+        'subinclude:<path>' - a file of patterns to match against files under
+                              the same directory
         '<something>' - a pattern of the specified default type
         """
 
@@ -349,7 +370,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
 
@@ -455,6 +476,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__)
@@ -551,7 +581,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,45 @@ 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



More information about the Mercurial-devel mailing list