dirstate: drop explicit files that shouldn't match (BC) (issue4679)
authorYuya Nishihara <yuya@tcha.org>
Fri, 26 Jan 2018 19:48:39 +0900
changeset 36238 deb851914fd7
parent 36237 2d6e03a28c31
child 36239 46260fac5563
dirstate: drop explicit files that shouldn't match (BC) (issue4679) Before, wctx.walk() could include files excluded by -X pattern, which disagrees with wctx.matches() and ctx.walk()/matches() behavior. This patch fixes the problem by testing stat results against the matcher if the matcher may contain false paths. I have no idea if the fix should be made before the workaround for case- insensitive filesystems, but that shouldn't matter since match.anypats() means 'not match.isexact()'. This patch also makes narrow and sparse extensions to not exclude explicit paths on walk() because they appear to depend on the buggy behavior. More detailed analysis about this issue by Martin von Zweigbergk: "I think it's just an unintended consequence of how the dirstate walk works, but I'm not sure. The exception for explicit files also bothered me when I was working on the matcher code a year or so ago. I actually added the exception to the matcher code because I thought it was always working like that (not just for dirstate) in a83a7d27911e (match: handle excludes using new differencematcher, 2017-05-16). It was only recently that Yuya realized that it used to be inconsistent and that I probably made it consistently bad because I didn't realize it was inconsistent to start with, see 821d8a5ab4ff (match: do not weirdly include explicit files excluded by -X option, 2018-01-16)." .. bc:: Working-directory commands now respect ``-X PATTERN`` no matter if PATTERN matches explicitly-specified FILEs. For example, ``hg add foo -X foo`` no longer add the file ``foo``.
hgext/narrow/narrowdirstate.py
hgext/sparse.py
mercurial/dirstate.py
tests/test-add.t
tests/test-narrow-commit.t
tests/test-sparse.t
tests/test-walk.t
--- a/hgext/narrow/narrowdirstate.py	Sun Jan 21 17:04:42 2018 +0900
+++ b/hgext/narrow/narrowdirstate.py	Fri Jan 26 19:48:39 2018 +0900
@@ -23,8 +23,11 @@
     def walk(orig, self, match, subrepos, unknown, ignored, full=True,
              narrowonly=True):
         if narrowonly:
-            narrowmatch = repo.narrowmatch()
-            match = matchmod.intersectmatchers(match, narrowmatch)
+            # hack to not exclude explicitly-specified paths so that they can
+            # be warned later on e.g. dirstate.add()
+            em = matchmod.exact(match._root, match._cwd, match.files())
+            nm = matchmod.unionmatcher([repo.narrowmatch(), em])
+            match = matchmod.intersectmatchers(match, nm)
         return orig(self, match, subrepos, unknown, ignored, full)
 
     extensions.wrapfunction(dirstate.dirstate, 'walk', walk)
--- a/hgext/sparse.py	Sun Jan 21 17:04:42 2018 +0900
+++ b/hgext/sparse.py	Fri Jan 26 19:48:39 2018 +0900
@@ -194,7 +194,11 @@
     """
 
     def walk(orig, self, match, subrepos, unknown, ignored, full=True):
-        match = matchmod.intersectmatchers(match, self._sparsematcher)
+        # hack to not exclude explicitly-specified paths so that they can
+        # be warned later on e.g. dirstate.add()
+        em = matchmod.exact(match._root, match._cwd, match.files())
+        sm = matchmod.unionmatcher([self._sparsematcher, em])
+        match = matchmod.intersectmatchers(match, sm)
         return orig(self, match, subrepos, unknown, ignored, full)
 
     extensions.wrapfunction(dirstate.dirstate, 'walk', walk)
--- a/mercurial/dirstate.py	Sun Jan 21 17:04:42 2018 +0900
+++ b/mercurial/dirstate.py	Fri Jan 26 19:48:39 2018 +0900
@@ -787,6 +787,17 @@
                     else:
                         badfn(ff, encoding.strtolocal(inst.strerror))
 
+        # match.files() may contain explicitly-specified paths that shouldn't
+        # be taken; drop them from the list of files found. dirsfound/notfound
+        # aren't filtered here because they will be tested later.
+        if match.anypats():
+            for f in list(results):
+                if f == '.hg' or f in subrepos:
+                    # keep sentinel to disable further out-of-repo walks
+                    continue
+                if not match(f):
+                    del results[f]
+
         # Case insensitive filesystems cannot rely on lstat() failing to detect
         # a case-only rename.  Prune the stat object for any file that does not
         # match the case in the filesystem, if there are multiple files that
--- a/tests/test-add.t	Sun Jan 21 17:04:42 2018 +0900
+++ b/tests/test-add.t	Fri Jan 26 19:48:39 2018 +0900
@@ -146,6 +146,13 @@
   M a
   ? a.orig
 
+excluded file shouldn't be added even if it is explicitly specified
+
+  $ hg add a.orig -X '*.orig'
+  $ hg st
+  M a
+  ? a.orig
+
 Forgotten file can be added back (as either clean or modified)
 
   $ hg forget b
--- a/tests/test-narrow-commit.t	Sun Jan 21 17:04:42 2018 +0900
+++ b/tests/test-narrow-commit.t	Fri Jan 26 19:48:39 2018 +0900
@@ -55,6 +55,11 @@
   $ hg add outside/f3
   abort: cannot track 'outside/f3' - it is outside the narrow clone
   [255]
+
+But adding a truly excluded file shouldn't count
+
+  $ hg add outside/f3 -X outside/f3
+
   $ rm -r outside
 
 Can modify dirstate inside
--- a/tests/test-sparse.t	Sun Jan 21 17:04:42 2018 +0900
+++ b/tests/test-sparse.t	Fri Jan 26 19:48:39 2018 +0900
@@ -129,6 +129,10 @@
   (include file with `hg debugsparse --include <pattern>` or use `hg add -s <file>` to include file directory while adding)
   [255]
 
+But adding a truly excluded file shouldn't count
+
+  $ hg add hide3 -X hide3
+
 Verify deleting sparseness while a file has changes fails
 
   $ hg debugsparse --delete 'show*'
--- a/tests/test-walk.t	Sun Jan 21 17:04:42 2018 +0900
+++ b/tests/test-walk.t	Fri Jan 26 19:48:39 2018 +0900
@@ -304,12 +304,10 @@
   f  beans/turtle    beans/turtle
   $ hg debugwalk -Xbeans/black beans/black
   matcher: <differencematcher m1=<patternmatcher patterns='(?:beans\\/black(?:/|$))'>, m2=<includematcher includes='(?:beans\\/black(?:/|$))'>>
-  f  beans/black  beans/black  exact
   $ hg debugwalk -Xbeans/black -Ibeans/black
   matcher: <differencematcher m1=<includematcher includes='(?:beans\\/black(?:/|$))'>, m2=<includematcher includes='(?:beans\\/black(?:/|$))'>>
   $ hg debugwalk -Xbeans beans/black
   matcher: <differencematcher m1=<patternmatcher patterns='(?:beans\\/black(?:/|$))'>, m2=<includematcher includes='(?:beans(?:/|$))'>>
-  f  beans/black  beans/black  exact
   $ hg debugwalk -Xbeans -Ibeans/black
   matcher: <differencematcher m1=<includematcher includes='(?:beans\\/black(?:/|$))'>, m2=<includematcher includes='(?:beans(?:/|$))'>>
   $ hg debugwalk 'glob:mammals/../beans/b*'
@@ -345,17 +343,13 @@
   [255]
 
 Test explicit paths and excludes:
-(BROKEN: nothing should be included, but wctx.walk() does)
 
   $ hg debugwalk fennel -X fennel
   matcher: <differencematcher m1=<patternmatcher patterns='(?:fennel(?:/|$))'>, m2=<includematcher includes='(?:fennel(?:/|$))'>>
-  f  fennel  fennel  exact
   $ hg debugwalk fennel -X 'f*'
   matcher: <differencematcher m1=<patternmatcher patterns='(?:fennel(?:/|$))'>, m2=<includematcher includes='(?:f[^/]*(?:/|$))'>>
-  f  fennel  fennel  exact
   $ hg debugwalk beans/black -X 'path:beans'
   matcher: <differencematcher m1=<patternmatcher patterns='(?:beans\\/black(?:/|$))'>, m2=<includematcher includes='(?:beans(?:/|$))'>>
-  f  beans/black  beans/black  exact
   $ hg debugwalk -I 'path:beans/black' -X 'path:beans'
   matcher: <differencematcher m1=<includematcher includes='(?:beans\\/black(?:/|$))'>, m2=<includematcher includes='(?:beans(?:/|$))'>>