Patchwork match: have visitdir() consider includes and excludes

login
register
mail settings
Submitter Drew Gottlieb
Date May 20, 2015, 11:20 p.m.
Message ID <682db56c7b8687aa2033.1432164014@waste.org>
Download mbox | patch
Permalink /patch/9203/
State Accepted
Headers show

Comments

Drew Gottlieb - May 20, 2015, 11:20 p.m.
# HG changeset patch
# User Drew Gottlieb <drgott@google.com>
# Date 1431984560 25200
#      Mon May 18 14:29:20 2015 -0700
# Node ID 682db56c7b8687aa2033387c4f8163bb6aa9437a
# Parent  451df92cec4912aefac57a4cf82e9268192c867b
match: have visitdir() consider includes and excludes

match.visitdir() used to only look at the match's primary pattern roots to
decide if a treemanifest traverser should descend into a particular directory.
This change logically makes visitdir also consider the match's include and
exclude pattern roots (if applicable) to make this decision.

This is especially important for situations like using narrowhg with multiple
treemanifest revlogs.
Matt Mackall - May 21, 2015, 5:55 p.m.
On Wed, 2015-05-20 at 18:20 -0500, Drew Gottlieb wrote:
> # HG changeset patch
> # User Drew Gottlieb <drgott@google.com>
> # Date 1431984560 25200
> #      Mon May 18 14:29:20 2015 -0700
> # Node ID 682db56c7b8687aa2033387c4f8163bb6aa9437a
> # Parent  451df92cec4912aefac57a4cf82e9268192c867b
> match: have visitdir() consider includes and excludes

Queued for default, thanks.

Patch

diff --git a/mercurial/match.py b/mercurial/match.py
--- a/mercurial/match.py
+++ b/mercurial/match.py
@@ -86,17 +86,25 @@ 
         self._always = False
         self._pathrestricted = bool(include or exclude or patterns)
         self._warn = warn
+        self._includeroots = set()
+        self._includedirs = set(['.'])
+        self._excluderoots = set()
 
         matchfns = []
         if include:
             kindpats = self._normalize(include, 'glob', root, cwd, auditor)
             self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)',
                                               listsubrepos)
+            self._includeroots.update(_roots(kindpats))
+            self._includeroots.discard('.')
+            self._includedirs.update(util.dirs(self._includeroots))
             matchfns.append(im)
         if exclude:
             kindpats = self._normalize(exclude, 'glob', root, cwd, auditor)
             self.excludepat, em = _buildmatch(ctx, kindpats, '(?:/|$)',
                                               listsubrepos)
+            self._excluderoots.update(_roots(kindpats))
+            self._excluderoots.discard('.')
             matchfns.append(lambda f: not em(f))
         if exact:
             if isinstance(patterns, list):
@@ -177,10 +185,25 @@ 
         return set(util.dirs(self._fileroots)) | set(['.'])
 
     def visitdir(self, dir):
+        '''Decides whether a directory should be visited based on whether it
+        has potential matches in it or one of its subdirectories. This is
+        based on the match's primary, included, and excluded patterns.
+
+        This function's behavior is undefined if it has returned False for
+        one of the dir's parent directories.
+        '''
+        if dir in self._excluderoots:
+            return False
+        parentdirs = None
+        if (self._includeroots and dir not in self._includeroots and
+                dir not in self._includedirs):
+            parentdirs = util.finddirs(dir)
+            if not any(parent in self._includeroots for parent in parentdirs):
+                return False
         return (not self._fileroots or '.' in self._fileroots or
                 dir in self._fileroots or dir in self._dirs or
                 any(parentdir in self._fileroots
-                    for parentdir in util.finddirs(dir)))
+                    for parentdir in parentdirs or util.finddirs(dir)))
 
     def exact(self, f):
         '''Returns True if f is in .files().'''
diff --git a/tests/test-treemanifest.t b/tests/test-treemanifest.t
--- a/tests/test-treemanifest.t
+++ b/tests/test-treemanifest.t
@@ -280,3 +280,100 @@ 
        0         0     125      0       4 63c9c0557d24 000000000000 000000000000
        1       125     109      0       5 23d12a1f6e0e 000000000000 000000000000
        2       234      55      0       6 3cb2d87b4250 23d12a1f6e0e 000000000000
+
+Create deeper repo with tree manifests.
+
+  $ cd ..
+  $ hg --config experimental.treemanifest=True init deeprepo
+  $ cd deeprepo
+
+  $ mkdir a
+  $ mkdir b
+  $ mkdir b/bar
+  $ mkdir b/bar/orange
+  $ mkdir b/bar/orange/fly
+  $ mkdir b/foo
+  $ mkdir b/foo/apple
+  $ mkdir b/foo/apple/bees
+
+  $ touch a/one.txt
+  $ touch a/two.txt
+  $ touch b/bar/fruits.txt
+  $ touch b/bar/orange/fly/gnat.py
+  $ touch b/bar/orange/fly/housefly.txt
+  $ touch b/foo/apple/bees/flower.py
+  $ touch c.txt
+  $ touch d.py
+
+  $ hg ci -Aqm 'initial'
+
+We'll see that visitdir works by removing some treemanifest revlogs and running
+the files command with various parameters.
+
+Test files from the root.
+
+  $ hg files -r .
+  a/one.txt
+  a/two.txt
+  b/bar/fruits.txt
+  b/bar/orange/fly/gnat.py
+  b/bar/orange/fly/housefly.txt
+  b/foo/apple/bees/flower.py
+  c.txt
+  d.py
+
+Test files for a subdirectory.
+
+  $ mv .hg/store/meta/a oldmf
+  $ hg files -r . b
+  b/bar/fruits.txt
+  b/bar/orange/fly/gnat.py
+  b/bar/orange/fly/housefly.txt
+  b/foo/apple/bees/flower.py
+  $ mv oldmf .hg/store/meta/a
+
+Test files with just includes and excludes.
+
+  $ mv .hg/store/meta/a oldmf
+  $ mv .hg/store/meta/b/bar/orange/fly oldmf2
+  $ mv .hg/store/meta/b/foo/apple/bees oldmf3
+  $ hg files -r . -I b/bar -X b/bar/orange/fly -I b/foo -X b/foo/apple/bees
+  b/bar/fruits.txt
+  $ mv oldmf .hg/store/meta/a
+  $ mv oldmf2 .hg/store/meta/b/bar/orange/fly
+  $ mv oldmf3 .hg/store/meta/b/foo/apple/bees
+
+Test files for a subdirectory, excluding a directory within it.
+
+  $ mv .hg/store/meta/a oldmf
+  $ mv .hg/store/meta/b/foo oldmf2
+  $ hg files -r . -X b/foo b
+  b/bar/fruits.txt
+  b/bar/orange/fly/gnat.py
+  b/bar/orange/fly/housefly.txt
+  $ mv oldmf .hg/store/meta/a
+  $ mv oldmf2 .hg/store/meta/b/foo
+
+Test files for a sub directory, including only a directory within it, and
+including an unrelated directory.
+
+  $ mv .hg/store/meta/a oldmf
+  $ mv .hg/store/meta/b/foo oldmf2
+  $ hg files -r . -I b/bar/orange -I a b
+  b/bar/orange/fly/gnat.py
+  b/bar/orange/fly/housefly.txt
+  $ mv oldmf .hg/store/meta/a
+  $ mv oldmf2 .hg/store/meta/b/foo
+
+Test files for a pattern, including a directory, and excluding a directory
+within that.
+
+  $ mv .hg/store/meta/a oldmf
+  $ mv .hg/store/meta/b/foo oldmf2
+  $ mv .hg/store/meta/b/bar/orange oldmf3
+  $ hg files -r . glob:**.txt -I b/bar -X b/bar/orange
+  b/bar/fruits.txt
+  $ mv oldmf .hg/store/meta/a
+  $ mv oldmf2 .hg/store/meta/b/foo
+  $ mv oldmf3 .hg/store/meta/b/bar/orange
+