Patchwork D7649: match: make sure `root` argument is always an absolute path

login
register
mail settings
Submitter phabricator
Date Dec. 13, 2019, 7:51 p.m.
Message ID <differential-rev-PHID-DREV-p4no2snw7tdc72ontedo-req@mercurial-scm.org>
Download mbox | patch
Permalink /patch/43805/
State Superseded
Headers show

Comments

phabricator - Dec. 13, 2019, 7:51 p.m.
martinvonz created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  The `root` argument should already be an absolute path, but we had
  tests that passed a relative path. This patch fixes up the tests and
  adds an assertion.
  
  This assumes that `os.path.isabs('/repo')` will be `True` on all
  platforms we care to run tests on. Augie tested for me that it does
  work on Windows, so that's good enough for me.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D7649

AFFECTED FILES
  mercurial/match.py
  tests/test-manifest.py
  tests/test-match.py

CHANGE DETAILS




To: martinvonz, #hg-reviewers
Cc: mercurial-devel
phabricator - Dec. 17, 2019, 2 a.m.
mharbison72 added a comment.
mharbison72 accepted this revision.


  Windows has some very strange rules for paths, and I remember fixing a subrepo bug with a path in the form `C:foo`.  I tried these, and can't think of anything more we'd need to worry about.
  
    >>> import os
    >>> os.path.isabs("/")
    True
    >>> os.path.isabs("C:/")
    True
    >>> os.path.isabs("C:tests")
    False
  
  (The last one being at the root of the hg repo.)

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D7649/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D7649

To: martinvonz, #hg-reviewers, mharbison72
Cc: mharbison72, mercurial-devel
phabricator - Dec. 17, 2019, 3:24 p.m.
martinvonz added a comment.


  In D7649#112837 <https://phab.mercurial-scm.org/D7649#112837>, @mharbison72 wrote:
  
  > Windows has some very strange rules for paths, and I remember fixing a subrepo bug with a path in the form `C:foo`.  I tried these, and can't think of anything more we'd need to worry about.
  >
  >   >>> import os
  >   >>> os.path.isabs("/")
  >   True
  >   >>> os.path.isabs("C:/")
  >   True
  >   >>> os.path.isabs("C:tests")
  >   False
  >
  > (The last one being at the root of the hg repo.)
  
  Thanks for checking. I think that means we're okay with this series now. I won't queue it because I wrote some of it.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D7649/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D7649

To: martinvonz, #hg-reviewers, mharbison72
Cc: mharbison72, mercurial-devel
phabricator - Dec. 20, 2019, 12:16 p.m.
pulkit added a comment.


  I am bit confused with `/repo` everywhere that whether that's the only valid or other forms can be valid also. I will prefer making existing root passed as absolute instead of replacing them with `/repo` (provided I understood correctly). Also I am not sure, but does this counts as API change?

INLINE COMMENTS

> match.py:186
>      Usually a patternmatcher is returned:
> -    >>> match(b'foo', b'.', [b're:.*\.c$', b'path:foo/a', b'*.py'])
> +    >>> match(b'/repo', b'.', [b're:.*\.c$', b'path:foo/a', b'*.py'])
>      <patternmatcher patterns='.*\\.c$|foo/a(?:/|$)|[^/]*\\.py$'>

Hm, does this has to be always `/repo` or `/foo` is also a valid parameter here?

> test-match.py:69
>      def testVisitdirPrefix(self):
> -        m = matchmod.match(b'x', b'', patterns=[b'path:dir/subdir'])
> +        m = matchmod.match(b'/repo', b'', patterns=[b'path:dir/subdir'])
>          assert isinstance(m, matchmod.patternmatcher)

This and below ones can be `/x` too right?

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D7649/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D7649

To: martinvonz, #hg-reviewers, mharbison72
Cc: pulkit, mharbison72, mercurial-devel
phabricator - Dec. 20, 2019, 2:29 p.m.
martinvonz added a comment.


  In D7649#113440 <https://phab.mercurial-scm.org/D7649#113440>, @pulkit wrote:
  
  > I am bit confused with `/repo` everywhere that whether that's the only valid or other forms can be valid also. I will prefer making existing root passed as absolute instead of replacing them with `/repo` (provided I understood correctly).
  
  I switched to `/repo` only to try to clarify that the path is the path to the repo (not some subdirectory). Want me to switch them back?
  
  > Also I am not sure, but does this counts as API change?
  
  I'm not sure if any extensions might have passed a relative path without noticing. Sure, we can add the (API) tag. Can you do that in flight? Or I can do it later if you want me to change some of the paths anyway.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D7649/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D7649

To: martinvonz, #hg-reviewers, mharbison72
Cc: pulkit, mharbison72, mercurial-devel
phabricator - Dec. 20, 2019, 2:47 p.m.
pulkit added a comment.


  In D7649#113454 <https://phab.mercurial-scm.org/D7649#113454>, @martinvonz wrote:
  
  > In D7649#113440 <https://phab.mercurial-scm.org/D7649#113440>, @pulkit wrote:
  >
  >> I am bit confused with `/repo` everywhere that whether that's the only valid or other forms can be valid also. I will prefer making existing root passed as absolute instead of replacing them with `/repo` (provided I understood correctly).
  >
  > I switched to `/repo` only to try to clarify that the path is the path to the repo (not some subdirectory). Want me to switch them back?
  
  Yes, that will be much appreciated.

INLINE COMMENTS

> match.py:155
>      arguments:
>      root - the canonical root of the tree you're matching against
>      cwd - the current working directory, if relevant

We can mention here that it must be an absolute path.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D7649/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D7649

To: martinvonz, #hg-reviewers, mharbison72
Cc: pulkit, mharbison72, mercurial-devel
phabricator - Dec. 20, 2019, 2:53 p.m.
martinvonz added inline comments.
martinvonz marked an inline comment as done.

INLINE COMMENTS

> pulkit wrote in match.py:186
> Hm, does this has to be always `/repo` or `/foo` is also a valid parameter here?

I've changed this one back.

> pulkit wrote in test-match.py:69
> This and below ones can be `/x` too right?

I've left this one as `/repo` because I think it's clearer that it's a repo path that way.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D7649/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D7649

To: martinvonz, #hg-reviewers, mharbison72
Cc: pulkit, mharbison72, mercurial-devel
phabricator - Dec. 20, 2019, 2:55 p.m.
martinvonz added inline comments.
martinvonz marked an inline comment as done.

INLINE COMMENTS

> pulkit wrote in match.py:155
> We can mention here that it must be an absolute path.

I think "canonical" already implied "absolute".

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D7649/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D7649

To: martinvonz, #hg-reviewers, mharbison72
Cc: pulkit, mharbison72, mercurial-devel

Patch

diff --git a/tests/test-match.py b/tests/test-match.py
--- a/tests/test-match.py
+++ b/tests/test-match.py
@@ -10,6 +10,9 @@ 
 )
 
 
+noop_auditor = lambda name: None
+
+
 class BaseMatcherTests(unittest.TestCase):
     def testVisitdir(self):
         m = matchmod.basematcher()
@@ -63,7 +66,7 @@ 
 
 class PatternMatcherTests(unittest.TestCase):
     def testVisitdirPrefix(self):
-        m = matchmod.match(b'x', b'', patterns=[b'path:dir/subdir'])
+        m = matchmod.match(b'/repo', b'', patterns=[b'path:dir/subdir'])
         assert isinstance(m, matchmod.patternmatcher)
         self.assertTrue(m.visitdir(b''))
         self.assertTrue(m.visitdir(b'dir'))
@@ -73,7 +76,7 @@ 
         self.assertFalse(m.visitdir(b'folder'))
 
     def testVisitchildrensetPrefix(self):
-        m = matchmod.match(b'x', b'', patterns=[b'path:dir/subdir'])
+        m = matchmod.match(b'/repo', b'', patterns=[b'path:dir/subdir'])
         assert isinstance(m, matchmod.patternmatcher)
         self.assertEqual(m.visitchildrenset(b''), b'this')
         self.assertEqual(m.visitchildrenset(b'dir'), b'this')
@@ -83,7 +86,7 @@ 
         self.assertEqual(m.visitchildrenset(b'folder'), set())
 
     def testVisitdirRootfilesin(self):
-        m = matchmod.match(b'x', b'', patterns=[b'rootfilesin:dir/subdir'])
+        m = matchmod.match(b'/repo', b'', patterns=[b'rootfilesin:dir/subdir'])
         assert isinstance(m, matchmod.patternmatcher)
         self.assertFalse(m.visitdir(b'dir/subdir/x'))
         self.assertFalse(m.visitdir(b'folder'))
@@ -93,7 +96,7 @@ 
         self.assertFalse(m.visitdir(b'dir/subdir'))
 
     def testVisitchildrensetRootfilesin(self):
-        m = matchmod.match(b'x', b'', patterns=[b'rootfilesin:dir/subdir'])
+        m = matchmod.match(b'/repo', b'', patterns=[b'rootfilesin:dir/subdir'])
         assert isinstance(m, matchmod.patternmatcher)
         self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), set())
         self.assertEqual(m.visitchildrenset(b'folder'), set())
@@ -104,7 +107,7 @@ 
         self.assertEqual(m.visitchildrenset(b'dir/subdir'), set())
 
     def testVisitdirGlob(self):
-        m = matchmod.match(b'x', b'', patterns=[b'glob:dir/z*'])
+        m = matchmod.match(b'/repo', b'', patterns=[b'glob:dir/z*'])
         assert isinstance(m, matchmod.patternmatcher)
         self.assertTrue(m.visitdir(b''))
         self.assertTrue(m.visitdir(b'dir'))
@@ -114,7 +117,7 @@ 
         self.assertTrue(m.visitdir(b'dir/subdir/x'))
 
     def testVisitchildrensetGlob(self):
-        m = matchmod.match(b'x', b'', patterns=[b'glob:dir/z*'])
+        m = matchmod.match(b'/repo', b'', patterns=[b'glob:dir/z*'])
         assert isinstance(m, matchmod.patternmatcher)
         self.assertEqual(m.visitchildrenset(b''), b'this')
         self.assertEqual(m.visitchildrenset(b'folder'), set())
@@ -126,7 +129,7 @@ 
 
 class IncludeMatcherTests(unittest.TestCase):
     def testVisitdirPrefix(self):
-        m = matchmod.match(b'x', b'', include=[b'path:dir/subdir'])
+        m = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
         assert isinstance(m, matchmod.includematcher)
         self.assertTrue(m.visitdir(b''))
         self.assertTrue(m.visitdir(b'dir'))
@@ -136,7 +139,7 @@ 
         self.assertFalse(m.visitdir(b'folder'))
 
     def testVisitchildrensetPrefix(self):
-        m = matchmod.match(b'x', b'', include=[b'path:dir/subdir'])
+        m = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
         assert isinstance(m, matchmod.includematcher)
         self.assertEqual(m.visitchildrenset(b''), {b'dir'})
         self.assertEqual(m.visitchildrenset(b'dir'), {b'subdir'})
@@ -146,7 +149,7 @@ 
         self.assertEqual(m.visitchildrenset(b'folder'), set())
 
     def testVisitdirRootfilesin(self):
-        m = matchmod.match(b'x', b'', include=[b'rootfilesin:dir/subdir'])
+        m = matchmod.match(b'/repo', b'', include=[b'rootfilesin:dir/subdir'])
         assert isinstance(m, matchmod.includematcher)
         self.assertTrue(m.visitdir(b''))
         self.assertTrue(m.visitdir(b'dir'))
@@ -155,7 +158,7 @@ 
         self.assertFalse(m.visitdir(b'folder'))
 
     def testVisitchildrensetRootfilesin(self):
-        m = matchmod.match(b'x', b'', include=[b'rootfilesin:dir/subdir'])
+        m = matchmod.match(b'/repo', b'', include=[b'rootfilesin:dir/subdir'])
         assert isinstance(m, matchmod.includematcher)
         self.assertEqual(m.visitchildrenset(b''), {b'dir'})
         self.assertEqual(m.visitchildrenset(b'dir'), {b'subdir'})
@@ -164,7 +167,7 @@ 
         self.assertEqual(m.visitchildrenset(b'folder'), set())
 
     def testVisitdirGlob(self):
-        m = matchmod.match(b'x', b'', include=[b'glob:dir/z*'])
+        m = matchmod.match(b'/repo', b'', include=[b'glob:dir/z*'])
         assert isinstance(m, matchmod.includematcher)
         self.assertTrue(m.visitdir(b''))
         self.assertTrue(m.visitdir(b'dir'))
@@ -174,7 +177,7 @@ 
         self.assertTrue(m.visitdir(b'dir/subdir/x'))
 
     def testVisitchildrensetGlob(self):
-        m = matchmod.match(b'x', b'', include=[b'glob:dir/z*'])
+        m = matchmod.match(b'/repo', b'', include=[b'glob:dir/z*'])
         assert isinstance(m, matchmod.includematcher)
         self.assertEqual(m.visitchildrenset(b''), {b'dir'})
         self.assertEqual(m.visitchildrenset(b'folder'), set())
@@ -286,7 +289,7 @@ 
 
     def testVisitdirM2SubdirPrefix(self):
         m1 = matchmod.alwaysmatcher()
-        m2 = matchmod.match(b'', b'', patterns=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'/repo', b'', patterns=[b'path:dir/subdir'])
         dm = matchmod.differencematcher(m1, m2)
         self.assertEqual(dm.visitdir(b''), True)
         self.assertEqual(dm.visitdir(b'dir'), True)
@@ -301,7 +304,7 @@ 
 
     def testVisitchildrensetM2SubdirPrefix(self):
         m1 = matchmod.alwaysmatcher()
-        m2 = matchmod.match(b'', b'', patterns=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'/repo', b'', patterns=[b'path:dir/subdir'])
         dm = matchmod.differencematcher(m1, m2)
         self.assertEqual(dm.visitchildrenset(b''), b'this')
         self.assertEqual(dm.visitchildrenset(b'dir'), b'this')
@@ -317,8 +320,8 @@ 
     # We're using includematcher instead of patterns because it behaves slightly
     # better (giving narrower results) than patternmatcher.
     def testVisitdirIncludeInclude(self):
-        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
-        m2 = matchmod.match(b'', b'', include=[b'rootfilesin:dir'])
+        m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'/repo', b'', include=[b'rootfilesin:dir'])
         dm = matchmod.differencematcher(m1, m2)
         self.assertEqual(dm.visitdir(b''), True)
         self.assertEqual(dm.visitdir(b'dir'), True)
@@ -332,8 +335,8 @@ 
         self.assertEqual(dm.visitdir(b'dir/subdir/x'), True)
 
     def testVisitchildrensetIncludeInclude(self):
-        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
-        m2 = matchmod.match(b'', b'', include=[b'rootfilesin:dir'])
+        m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'/repo', b'', include=[b'rootfilesin:dir'])
         dm = matchmod.differencematcher(m1, m2)
         self.assertEqual(dm.visitchildrenset(b''), {b'dir'})
         self.assertEqual(dm.visitchildrenset(b'dir'), {b'subdir'})
@@ -402,7 +405,7 @@ 
 
     def testVisitdirM2SubdirPrefix(self):
         m1 = matchmod.alwaysmatcher()
-        m2 = matchmod.match(b'', b'', patterns=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'/repo', b'', patterns=[b'path:dir/subdir'])
         im = matchmod.intersectmatchers(m1, m2)
         self.assertEqual(im.visitdir(b''), True)
         self.assertEqual(im.visitdir(b'dir'), True)
@@ -417,7 +420,7 @@ 
 
     def testVisitchildrensetM2SubdirPrefix(self):
         m1 = matchmod.alwaysmatcher()
-        m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
         im = matchmod.intersectmatchers(m1, m2)
         self.assertEqual(im.visitchildrenset(b''), {b'dir'})
         self.assertEqual(im.visitchildrenset(b'dir'), {b'subdir'})
@@ -431,8 +434,8 @@ 
     # We're using includematcher instead of patterns because it behaves slightly
     # better (giving narrower results) than patternmatcher.
     def testVisitdirIncludeInclude(self):
-        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
-        m2 = matchmod.match(b'', b'', include=[b'rootfilesin:dir'])
+        m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'/repo', b'', include=[b'rootfilesin:dir'])
         im = matchmod.intersectmatchers(m1, m2)
         self.assertEqual(im.visitdir(b''), True)
         self.assertEqual(im.visitdir(b'dir'), True)
@@ -443,8 +446,8 @@ 
         self.assertFalse(im.visitdir(b'dir/subdir/x'))
 
     def testVisitchildrensetIncludeInclude(self):
-        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
-        m2 = matchmod.match(b'', b'', include=[b'rootfilesin:dir'])
+        m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'/repo', b'', include=[b'rootfilesin:dir'])
         im = matchmod.intersectmatchers(m1, m2)
         self.assertEqual(im.visitchildrenset(b''), {b'dir'})
         self.assertEqual(im.visitchildrenset(b'dir'), b'this')
@@ -457,8 +460,8 @@ 
     # We're using includematcher instead of patterns because it behaves slightly
     # better (giving narrower results) than patternmatcher.
     def testVisitdirIncludeInclude2(self):
-        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
-        m2 = matchmod.match(b'', b'', include=[b'path:folder'])
+        m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'/repo', b'', include=[b'path:folder'])
         im = matchmod.intersectmatchers(m1, m2)
         # FIXME: is True correct here?
         self.assertEqual(im.visitdir(b''), True)
@@ -470,8 +473,8 @@ 
         self.assertFalse(im.visitdir(b'dir/subdir/x'))
 
     def testVisitchildrensetIncludeInclude2(self):
-        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
-        m2 = matchmod.match(b'', b'', include=[b'path:folder'])
+        m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'/repo', b'', include=[b'path:folder'])
         im = matchmod.intersectmatchers(m1, m2)
         # FIXME: is set() correct here?
         self.assertEqual(im.visitchildrenset(b''), set())
@@ -485,8 +488,8 @@ 
     # We're using includematcher instead of patterns because it behaves slightly
     # better (giving narrower results) than patternmatcher.
     def testVisitdirIncludeInclude3(self):
-        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir/x'])
-        m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir/x'])
+        m2 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
         im = matchmod.intersectmatchers(m1, m2)
         self.assertEqual(im.visitdir(b''), True)
         self.assertEqual(im.visitdir(b'dir'), True)
@@ -498,8 +501,8 @@ 
         self.assertEqual(im.visitdir(b'dir/subdir/x'), True)
 
     def testVisitchildrensetIncludeInclude3(self):
-        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir/x'])
-        m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir/x'])
+        m2 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
         im = matchmod.intersectmatchers(m1, m2)
         self.assertEqual(im.visitchildrenset(b''), {b'dir'})
         self.assertEqual(im.visitchildrenset(b'dir'), {b'subdir'})
@@ -513,8 +516,8 @@ 
     # We're using includematcher instead of patterns because it behaves slightly
     # better (giving narrower results) than patternmatcher.
     def testVisitdirIncludeInclude4(self):
-        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir/x'])
-        m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir/z'])
+        m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir/x'])
+        m2 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir/z'])
         im = matchmod.intersectmatchers(m1, m2)
         # OPT: these next three could probably be False as well.
         self.assertEqual(im.visitdir(b''), True)
@@ -526,8 +529,8 @@ 
         self.assertFalse(im.visitdir(b'dir/subdir/x'))
 
     def testVisitchildrensetIncludeInclude4(self):
-        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir/x'])
-        m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir/z'])
+        m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir/x'])
+        m2 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir/z'])
         im = matchmod.intersectmatchers(m1, m2)
         # OPT: these next two could probably be set() as well.
         self.assertEqual(im.visitchildrenset(b''), {b'dir'})
@@ -620,7 +623,7 @@ 
 
     def testVisitdirM2SubdirPrefix(self):
         m1 = matchmod.alwaysmatcher()
-        m2 = matchmod.match(b'', b'', patterns=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'/repo', b'', patterns=[b'path:dir/subdir'])
         um = matchmod.unionmatcher([m1, m2])
         self.assertEqual(um.visitdir(b''), b'all')
         self.assertEqual(um.visitdir(b'dir'), b'all')
@@ -632,7 +635,7 @@ 
 
     def testVisitchildrensetM2SubdirPrefix(self):
         m1 = matchmod.alwaysmatcher()
-        m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
         um = matchmod.unionmatcher([m1, m2])
         self.assertEqual(um.visitchildrenset(b''), b'all')
         self.assertEqual(um.visitchildrenset(b'dir'), b'all')
@@ -645,8 +648,8 @@ 
     # We're using includematcher instead of patterns because it behaves slightly
     # better (giving narrower results) than patternmatcher.
     def testVisitdirIncludeInclude(self):
-        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
-        m2 = matchmod.match(b'', b'', include=[b'rootfilesin:dir'])
+        m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'/repo', b'', include=[b'rootfilesin:dir'])
         um = matchmod.unionmatcher([m1, m2])
         self.assertEqual(um.visitdir(b''), True)
         self.assertEqual(um.visitdir(b'dir'), True)
@@ -658,8 +661,8 @@ 
         self.assertEqual(um.visitdir(b'dir/subdir/x'), True)
 
     def testVisitchildrensetIncludeInclude(self):
-        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
-        m2 = matchmod.match(b'', b'', include=[b'rootfilesin:dir'])
+        m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'/repo', b'', include=[b'rootfilesin:dir'])
         um = matchmod.unionmatcher([m1, m2])
         self.assertEqual(um.visitchildrenset(b''), {b'dir'})
         self.assertEqual(um.visitchildrenset(b'dir'), b'this')
@@ -673,8 +676,8 @@ 
     # We're using includematcher instead of patterns because it behaves slightly
     # better (giving narrower results) than patternmatcher.
     def testVisitdirIncludeInclude2(self):
-        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
-        m2 = matchmod.match(b'', b'', include=[b'path:folder'])
+        m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'/repo', b'', include=[b'path:folder'])
         um = matchmod.unionmatcher([m1, m2])
         self.assertEqual(um.visitdir(b''), True)
         self.assertEqual(um.visitdir(b'dir'), True)
@@ -686,8 +689,8 @@ 
         self.assertEqual(um.visitdir(b'dir/subdir/x'), True)
 
     def testVisitchildrensetIncludeInclude2(self):
-        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
-        m2 = matchmod.match(b'', b'', include=[b'path:folder'])
+        m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'/repo', b'', include=[b'path:folder'])
         um = matchmod.unionmatcher([m1, m2])
         self.assertEqual(um.visitchildrenset(b''), {b'folder', b'dir'})
         self.assertEqual(um.visitchildrenset(b'dir'), {b'subdir'})
@@ -701,8 +704,8 @@ 
     # We're using includematcher instead of patterns because it behaves slightly
     # better (giving narrower results) than patternmatcher.
     def testVisitdirIncludeInclude3(self):
-        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir/x'])
-        m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir/x'])
+        m2 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
         um = matchmod.unionmatcher([m1, m2])
         self.assertEqual(um.visitdir(b''), True)
         self.assertEqual(um.visitdir(b'dir'), True)
@@ -714,8 +717,8 @@ 
         self.assertEqual(um.visitdir(b'dir/subdir/z'), True)
 
     def testVisitchildrensetIncludeInclude3(self):
-        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir/x'])
-        m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir/x'])
+        m2 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
         um = matchmod.unionmatcher([m1, m2])
         self.assertEqual(um.visitchildrenset(b''), {b'dir'})
         self.assertEqual(um.visitchildrenset(b'dir'), {b'subdir'})
@@ -729,8 +732,8 @@ 
     # We're using includematcher instead of patterns because it behaves slightly
     # better (giving narrower results) than patternmatcher.
     def testVisitdirIncludeInclude4(self):
-        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir/x'])
-        m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir/z'])
+        m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir/x'])
+        m2 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir/z'])
         um = matchmod.unionmatcher([m1, m2])
         # OPT: these next three could probably be False as well.
         self.assertEqual(um.visitdir(b''), True)
@@ -742,8 +745,8 @@ 
         self.assertEqual(um.visitdir(b'dir/subdir/x'), b'all')
 
     def testVisitchildrensetIncludeInclude4(self):
-        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir/x'])
-        m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir/z'])
+        m1 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir/x'])
+        m2 = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir/z'])
         um = matchmod.unionmatcher([m1, m2])
         self.assertEqual(um.visitchildrenset(b''), {b'dir'})
         self.assertEqual(um.visitchildrenset(b'dir'), {b'subdir'})
@@ -756,7 +759,7 @@ 
 
 class SubdirMatcherTests(unittest.TestCase):
     def testVisitdir(self):
-        m = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        m = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
         sm = matchmod.subdirmatcher(b'dir', m)
 
         self.assertEqual(sm.visitdir(b''), True)
@@ -767,7 +770,7 @@ 
         self.assertFalse(sm.visitdir(b'foo'))
 
     def testVisitchildrenset(self):
-        m = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        m = matchmod.match(b'/repo', b'', include=[b'path:dir/subdir'])
         sm = matchmod.subdirmatcher(b'dir', m)
 
         self.assertEqual(sm.visitchildrenset(b''), {b'subdir'})
@@ -781,7 +784,10 @@ 
 class PrefixdirMatcherTests(unittest.TestCase):
     def testVisitdir(self):
         m = matchmod.match(
-            util.localpath(b'root/d'), b'e/f', [b'../a.txt', b'b.txt']
+            util.localpath(b'/root/d'),
+            b'e/f',
+            [b'../a.txt', b'b.txt'],
+            auditor=noop_auditor,
         )
         pm = matchmod.prefixdirmatcher(b'd', m)
 
@@ -814,7 +820,10 @@ 
 
     def testVisitchildrenset(self):
         m = matchmod.match(
-            util.localpath(b'root/d'), b'e/f', [b'../a.txt', b'b.txt']
+            util.localpath(b'/root/d'),
+            b'e/f',
+            [b'../a.txt', b'b.txt'],
+            auditor=noop_auditor,
         )
         pm = matchmod.prefixdirmatcher(b'd', m)
 
diff --git a/tests/test-manifest.py b/tests/test-manifest.py
--- a/tests/test-manifest.py
+++ b/tests/test-manifest.py
@@ -169,7 +169,7 @@ 
         m[b'foo'] = want + b'+'
         self.assertEqual(want, m[b'foo'])
         # make sure the suffix survives a copy
-        match = matchmod.match(b'', b'', [b're:foo'])
+        match = matchmod.match(b'/repo', b'', [b're:foo'])
         m2 = m.matches(match)
         self.assertEqual(want, m2[b'foo'])
         self.assertEqual(1, len(m2))
@@ -186,7 +186,7 @@ 
 
     def testMatchException(self):
         m = self.parsemanifest(A_SHORT_MANIFEST)
-        match = matchmod.match(b'', b'', [b're:.*'])
+        match = matchmod.match(b'/repo', b'', [b're:.*'])
 
         def filt(path):
             if path == b'foo':
@@ -328,7 +328,7 @@ 
         actually exist.'''
         m = self.parsemanifest(A_DEEPER_MANIFEST)
 
-        match = matchmod.match(b'/', b'', [b'a/f'], default=b'relpath')
+        match = matchmod.match(b'/repo', b'', [b'a/f'], default=b'relpath')
         m2 = m.matches(match)
 
         self.assertEqual([], m2.keys())
@@ -348,7 +348,7 @@ 
         '''Tests matches() for what should be a full match.'''
         m = self.parsemanifest(A_DEEPER_MANIFEST)
 
-        match = matchmod.match(b'/', b'', [b''])
+        match = matchmod.match(b'/repo', b'', [b''])
         m2 = m.matches(match)
 
         self.assertEqual(m.keys(), m2.keys())
@@ -358,7 +358,7 @@ 
         match against all files within said directory.'''
         m = self.parsemanifest(A_DEEPER_MANIFEST)
 
-        match = matchmod.match(b'/', b'', [b'a/b'], default=b'relpath')
+        match = matchmod.match(b'/repo', b'', [b'a/b'], default=b'relpath')
         m2 = m.matches(match)
 
         self.assertEqual(
@@ -392,7 +392,7 @@ 
         when not in the root directory.'''
         m = self.parsemanifest(A_DEEPER_MANIFEST)
 
-        match = matchmod.match(b'/', b'a/b', [b'.'], default=b'relpath')
+        match = matchmod.match(b'/repo', b'a/b', [b'.'], default=b'relpath')
         m2 = m.matches(match)
 
         self.assertEqual(
@@ -415,7 +415,7 @@ 
         deeper than the specified directory.'''
         m = self.parsemanifest(A_DEEPER_MANIFEST)
 
-        match = matchmod.match(b'/', b'', [b'a/b/*/*.txt'])
+        match = matchmod.match(b'/repo', b'', [b'a/b/*/*.txt'])
         m2 = m.matches(match)
 
         self.assertEqual(
@@ -467,7 +467,7 @@ 
             sorted(dirs),
         )
 
-        match = matchmod.match(b'/', b'', [b'path:a/b/'])
+        match = matchmod.match(b'/repo', b'', [b'path:a/b/'])
         dirs = [s._dir for s in m.walksubtrees(matcher=match)]
         self.assertEqual(sorted([b'a/b/', b'a/b/c/', b'a/b/d/']), sorted(dirs))
 
diff --git a/mercurial/match.py b/mercurial/match.py
--- a/mercurial/match.py
+++ b/mercurial/match.py
@@ -183,34 +183,34 @@ 
     '<something>' - a pattern of the specified default type
 
     Usually a patternmatcher is returned:
-    >>> match(b'foo', b'.', [b're:.*\.c$', b'path:foo/a', b'*.py'])
+    >>> match(b'/repo', b'.', [b're:.*\.c$', b'path:foo/a', b'*.py'])
     <patternmatcher patterns='.*\\.c$|foo/a(?:/|$)|[^/]*\\.py$'>
 
     Combining 'patterns' with 'include' (resp. 'exclude') gives an
     intersectionmatcher (resp. a differencematcher):
-    >>> type(match(b'foo', b'.', [b're:.*\.c$'], include=[b'path:lib']))
+    >>> type(match(b'/repo', b'.', [b're:.*\.c$'], include=[b'path:lib']))
     <class 'mercurial.match.intersectionmatcher'>
-    >>> type(match(b'foo', b'.', [b're:.*\.c$'], exclude=[b'path:build']))
+    >>> type(match(b'/repo', b'.', [b're:.*\.c$'], exclude=[b'path:build']))
     <class 'mercurial.match.differencematcher'>
 
     Notice that, if 'patterns' is empty, an alwaysmatcher is returned:
-    >>> match(b'foo', b'.', [])
+    >>> match(b'/repo', b'.', [])
     <alwaysmatcher>
 
     The 'default' argument determines which kind of pattern is assumed if a
     pattern has no prefix:
-    >>> match(b'foo', b'.', [b'.*\.c$'], default=b're')
+    >>> match(b'/repo', b'.', [b'.*\.c$'], default=b're')
     <patternmatcher patterns='.*\\.c$'>
-    >>> match(b'foo', b'.', [b'main.py'], default=b'relpath')
+    >>> match(b'/repo', b'.', [b'main.py'], default=b'relpath')
     <patternmatcher patterns='main\\.py(?:/|$)'>
-    >>> match(b'foo', b'.', [b'main.py'], default=b're')
+    >>> match(b'/repo', b'.', [b'main.py'], default=b're')
     <patternmatcher patterns='main.py'>
 
     The primary use of matchers is to check whether a value (usually a file
     name) matches againset one of the patterns given at initialization. There
     are two ways of doing this check.
 
-    >>> m = match(b'foo', b'', [b're:.*\.c$', b'relpath:a'])
+    >>> m = match(b'/repo', b'', [b're:.*\.c$', b'relpath:a'])
 
     1. Calling the matcher with a file name returns True if any pattern
     matches that file name:
@@ -228,6 +228,7 @@ 
     >>> m.exact(b'main.c')
     False
     """
+    assert os.path.isabs(root)
     normalize = _donormalize
     if icasefs:
         dirstate = ctx.repo().dirstate
@@ -940,7 +941,7 @@ 
     The paths are remapped to remove/insert the path as needed:
 
     >>> from . import pycompat
-    >>> m1 = match(b'root', b'', [b'a.txt', b'sub/b.txt'])
+    >>> m1 = match(b'/repo', b'', [b'a.txt', b'sub/b.txt'])
     >>> m2 = subdirmatcher(b'sub', m1)
     >>> m2(b'a.txt')
     False
@@ -1024,7 +1025,7 @@ 
     The prefix path should usually be the relative path from the root of
     this matcher to the root of the wrapped matcher.
 
-    >>> m1 = match(util.localpath(b'root/d/e'), b'f', [b'../a.txt', b'b.txt'])
+    >>> m1 = match(util.localpath(b'/root/d/e'), b'f', [b'../a.txt', b'b.txt'], auditor=lambda name: None)
     >>> m2 = prefixdirmatcher(b'd/e', m1)
     >>> m2(b'a.txt')
     False