Commits

Brodie Rao committed 204d392

More improvements to the url patches

  • Participants
  • Parent commits fd6c044

Comments (0)

Files changed (6)

 url-hg-lookup #+urlsplit
 url-hg-parseurl #+urlsplit
 url-ui #+urlsplit
-url-drop-scheme #+urlsplit
+url-localpath #+urlsplit
 fetch-ret #+fetch-ret
 popen-and-wait #+popen-wait
 pager-popen-wait #+popen-wait
 # HG changeset patch
-# Parent dc5ff71579877c60e22c98e2b7e246204494db3c
+# Parent 9dec22427f10dcfb6e42972cb2425af5fee671c7
 url: provide URL object
 
 This patch adds a URL object that re-implements urlsplit() and
 diff --git a/mercurial/url.py b/mercurial/url.py
 --- a/mercurial/url.py
 +++ b/mercurial/url.py
-@@ -23,6 +23,175 @@ def _urlunparse(scheme, netloc, path, pa
+@@ -23,6 +23,184 @@ def _urlunparse(scheme, netloc, path, pa
          result = scheme + '://' + result[len(scheme + ':'):]
      return result
  
 +        # We slowly chomp away at path until we have only the path left
 +        self.scheme = self.user = self.passwd = self.host = None
 +        self.port = self.path = self.query = self.fragment = None
++        self._localpath = True
 +
 +        if not path.startswith('//') and ':' in path:
-+            self.scheme, path = path.split(':', 1)
-+            if not self.scheme:
-+                raise ValueError('not a URL')
++            parts = path.split(':', 1)
++            if parts[0]:
++                self.scheme, path = parts
++                self._localpath = False
 +
 +        if not path:
 +            path = None
++            if self._localpath:
++                return
 +        else:
 +            if parse_fragment and '#' in path:
 +                path, self.fragment = path.split('#', 1)
 +                if not path:
 +                    path = None
++            if self._localpath:
++                self.path = path
++                return
++
 +            if parse_query and '?' in path:
 +                path, self.query = path.split('?', 1)
 +                if not path:
 +        >>> str(url('path'))
 +        'path'
 +        """
-+        s = ''
-+        if self.scheme:
-+            s += self.scheme + ':'
++        if self._localpath:
++            return self.path or ''
++
++        s = self.scheme + ':'
 +        if (self.user or self.passwd or self.host or
 +            self.scheme and not self.path):
 +            s += '//'
 diff --git a/tests/test-url.py b/tests/test-url.py
 --- a/tests/test-url.py
 +++ b/tests/test-url.py
-@@ -36,3 +36,149 @@ check(_verifycert({'subject': ()},
+@@ -36,3 +36,139 @@ check(_verifycert({'subject': ()},
        'no commonName found in certificate')
  check(_verifycert(None, 'example.com'),
        'no certificate received')
 +    <url scheme: 'http', host: 'host', path: 'a', fragment: 'b#c'>
 +    >>> url('http://host/a#b?c')
 +    <url scheme: 'http', host: 'host', path: 'a', fragment: 'b?c'>
-+    >>> url('?a#b')
-+    <url query: 'a', fragment: 'b'>
-+    >>> url('?a#b', parse_query=False)
-+    <url path: '?a', fragment: 'b'>
-+    >>> url('?a#b', parse_fragment=False)
-+    <url query: 'a#b'>
-+    >>> url('?a#b', parse_query=False, parse_fragment=False)
-+    <url path: '?a#b'>
++    >>> url('http://host/?a#b')
++    <url scheme: 'http', host: 'host', path: '', query: 'a', fragment: 'b'>
++    >>> url('http://host/?a#b', parse_query=False)
++    <url scheme: 'http', host: 'host', path: '?a', fragment: 'b'>
++    >>> url('http://host/?a#b', parse_fragment=False)
++    <url scheme: 'http', host: 'host', path: '', query: 'a#b'>
++    >>> url('http://host/?a#b', parse_query=False, parse_fragment=False)
++    <url scheme: 'http', host: 'host', path: '?a#b'>
 +
 +    IPv6 addresses:
 +
 +    Missing scheme, host, etc.:
 +
 +    >>> url('://192.0.2.16:80/')
-+    Traceback (most recent call last):
-+        ...
-+    ValueError: not a URL
-+    >>> url('//192.0.2.16:80/foo')
-+    <url host: '192.0.2.16', port: '80', path: 'foo'>
-+    >>> url('//:80/foo')
-+    <url port: '80', path: 'foo'>
-+    >>> url('mercurial.selenic.com/wiki/')
-+    <url path: 'mercurial.selenic.com/wiki/'>
-+    >>> url('mercurial.selenic.com:80')
-+    <url scheme: 'mercurial.selenic.com', path: '80'>
++    <url path: '://192.0.2.16:80/'>
 +    >>> url('http://mercurial.selenic.com')
 +    <url scheme: 'http', host: 'mercurial.selenic.com'>
 +    >>> url('/foo')
 +    >>> url('bundle:/foo')
 +    <url scheme: 'bundle', path: '/foo'>
 +    >>> url('a?b#c')
-+    <url path: 'a', query: 'b', fragment: 'c'>
++    <url path: 'a?b', fragment: 'c'>
 +    >>> url('http://x.com?arg=/foo')
 +    <url scheme: 'http', host: 'x.com', query: 'arg=/foo'>
 +    >>> url('http://joe:xxx@/foo')
 +    >>> u = url('')
 +    >>> u
 +    <url >
-+    >>> u.unparse()
++    >>> str(u)
 +    ''
 +
 +    Empty path with query string:
 +
-+    >>> url('http://foo/?bar').unparse()
++    >>> str(url('http://foo/?bar'))
 +    'http://foo/?bar'
 +
 +    Invalid path:
 +
 +    >>> u = url('http://foo/bar')
 +    >>> u.path = 'bar'
-+    >>> u.unparse()
++    >>> str(u)
 +    'http://foo/bar'
 +
 +    >>> u = url('file:///foo/bar/baz')
 +    >>> u
 +    <url scheme: 'file', path: '/foo/bar/baz'>
-+    >>> u.unparse()
++    >>> str(u)
 +    'file:/foo/bar/baz'
 +    """
 +

File url-drop-scheme

-# HG changeset patch
-# Parent 1395e9c19eb01a49ad224fc2ee33ce1391eef2d8
-url: use url.URL in util.drop_scheme() and remove hg.localpath()
-
-This updates drop_scheme() to use url.URL for parsing instead of doing
-it on its own. The function is moved from util to url to avoid an
-import cycle.
-
-hg.localpath() is removed in favor of using drop_scheme(). This
-provides more consistent behavior between "hg clone" and other
-commands.
-
-This change has a side effect of making URLs like bundle://../foo not
-correspond to relative paths with "hg bundle". This is because ".." is
-considered to be the host for the URL. The URL should be bundle:../foo
-to be relative.
-
-Invalid URLs like the one above never worked with push, in, out, etc.,
-so this makes URL handling more consistent.
-
-This also changes the behavior of push, in, out, etc. in that they no
-longer ignore the host part of file:// URLs when converting them to
-local paths. If the host is not "localhost", it isn't converted.
-
-Comparison of old localpath() behavior and new drop_scheme() behavior:
-
-URL                          Old return value      New return value
-===                          ================      ================
-file://foo/foo               foo/foo               file://foo/foo
-file://localhost:80/foo      localhost:80/foo      file://localhost:80/foo
-file://localhost:/foo        localhost:/foo        file://localhost:/foo
-file://localhost/foo         /foo                  /foo
-file:///foo                  /foo                  /foo
-file://foo                   foo                   file://foo
-file:/foo                    /foo                  /foo
-file:foo                     foo                   foo
-/foo                         /foo                  /foo
-file://C:/foo                C:/foo                file://C:/foo
-file:///C:/foo               /C:/foo               /C:/foo
-
-On Windows:
-
-URL                          Old return value      New return value
-===                          ================      ================
-file://localhost:80/foo      localhost:80/foo      file://localhost:80/foo
-file://localhost:/foo        localhost:/foo        file://localhost:/foo
-file://localhost/foo         C:/foo                C:/foo
-file:///foo                  C:/foo                C:/foo
-file://foo                   foo                   file://foo
-file:/foo                    C:/foo                C:/foo
-file:foo                     foo                   foo
-/foo                         /foo                  /foo
-file://C:/foo                C:/foo                file://C:/foo
-file:///C:/foo               /C:/foo               C:/foo
-
-The above behavior now also applies to the bundle scheme.
-
-Related issues:
-
-- issue1153: File URIs aren't handled correctly in windows
-
-  This patch should preserve the fix implemented in 2770d03ae49f. It
-  could, however, go a step further and "promote" Windows-style drive
-  letters from being interpreted as host names to being part of the
-  path. As of now, file://C:/foo has a host of C and a path of /foo.
-
-- issue2154: Cannot escape '#' in Mercurial URLs (#1172 in THG)
-
-  The fragment is still interpreted as a revision or a branch, even in
-  paths to bundles. This patch does not implement percent encoding
-  handling.
-
-  XXX It might make sense to decode percent escapes only when a
-  scheme is specified.
-
-diff --git a/mercurial/bundlerepo.py b/mercurial/bundlerepo.py
---- a/mercurial/bundlerepo.py
-+++ b/mercurial/bundlerepo.py
-@@ -15,7 +15,7 @@ from node import nullid
- from i18n import _
- import os, struct, tempfile, shutil
- import changegroup, util, mdiff, discovery
--import localrepo, changelog, manifest, filelog, revlog, error
-+import localrepo, changelog, manifest, filelog, revlog, error, url
- 
- class bundlerevlog(revlog.revlog):
-     def __init__(self, opener, indexfile, bundle,
-@@ -279,9 +279,9 @@ def instance(ui, path, create):
-             cwd = os.path.join(cwd,'')
-             if parentpath.startswith(cwd):
-                 parentpath = parentpath[len(cwd):]
--    path = util.drop_scheme('file', path)
-+    path = url.localpath(path)
-     if path.startswith('bundle:'):
--        path = util.drop_scheme('bundle', path)
-+        path = url.localpath(path)
-         s = path.split("+", 1)
-         if len(s) == 1:
-             repopath, bundlename = parentpath, s[0]
-diff --git a/mercurial/hg.py b/mercurial/hg.py
---- a/mercurial/hg.py
-+++ b/mercurial/hg.py
-@@ -17,7 +17,7 @@ import verify as verifymod
- import errno, os, shutil
- 
- def _local(path):
--    path = util.expandpath(util.drop_scheme('file', path))
-+    path = util.expandpath(url.localpath(path))
-     return (os.path.isfile(path) and bundlerepo or localrepo)
- 
- def addbranchrevs(lrepo, repo, branches, revs):
-@@ -104,15 +104,6 @@ def defaultdest(source):
-     '''return default destination of clone if none is given'''
-     return os.path.basename(os.path.normpath(source))
- 
--def localpath(path):
--    if path.startswith('file://localhost/'):
--        return path[16:]
--    if path.startswith('file://'):
--        return path[7:]
--    if path.startswith('file:'):
--        return path[5:]
--    return path
--
- def share(ui, source, dest=None, update=True):
-     '''create a shared repository'''
- 
-@@ -232,8 +223,8 @@ def clone(ui, source, dest=None, pull=Fa
-     else:
-         dest = ui.expandpath(dest)
- 
--    dest = localpath(dest)
--    source = localpath(source)
-+    dest = url.localpath(dest)
-+    source = url.localpath(source)
- 
-     if os.path.exists(dest):
-         if not os.path.isdir(dest):
-@@ -259,7 +250,7 @@ def clone(ui, source, dest=None, pull=Fa
-         abspath = origsource
-         copy = False
-         if src_repo.cancopy() and islocal(dest):
--            abspath = os.path.abspath(util.drop_scheme('file', origsource))
-+            abspath = os.path.abspath(url.localpath(origsource))
-             copy = not pull and not rev
- 
-         if copy:
-diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
---- a/mercurial/localrepo.py
-+++ b/mercurial/localrepo.py
-@@ -1898,7 +1898,7 @@ def aftertrans(files):
-     return a
- 
- def instance(ui, path, create):
--    return localrepository(ui, util.drop_scheme('file', path), create)
-+    return localrepository(ui, urlmod.localpath(path), create)
- 
- def islocal(path):
-     return True
-diff --git a/mercurial/url.py b/mercurial/url.py
---- a/mercurial/url.py
-+++ b/mercurial/url.py
-@@ -181,6 +181,23 @@ class url(object):
- def has_scheme(path):
-     return bool(url(path).scheme)
- 
-+def localpath(path):
-+    u = url(path, parse_query=False, parse_fragment=False)
-+    if u.scheme == 'file' or u.scheme == 'bundle':
-+        if u.host and (u.port is not None or u.host != 'localhost'):
-+            return path
-+        elif os.name == 'nt' and u.path and u.path.startswith('/'):
-+            if u.path[1].isalpha() and u.path[2] == ':':
-+                # Strip leading slash from paths with drive names
-+                u.path = u.path[1:]
-+            else:
-+                # On Windows, absolute paths are rooted at the
-+                # current drive root. On POSIX they are rooted at
-+                # the file system root.
-+                droot = os.path.splitdrive(os.getcwd())[0] + '/'
-+                u.path = os.path.join(droot, u.path)
-+    return u.path or ''
-+
- def hidepassword(u):
-     '''hide user credential in a url string'''
-     u = url(u)
-diff --git a/mercurial/util.py b/mercurial/util.py
---- a/mercurial/util.py
-+++ b/mercurial/util.py
-@@ -1301,26 +1301,6 @@ def bytecount(nbytes):
-             return format % (nbytes / float(divisor))
-     return units[-1][2] % nbytes
- 
--def drop_scheme(scheme, path):
--    sc = scheme + ':'
--    if path.startswith(sc):
--        path = path[len(sc):]
--        if path.startswith('//'):
--            if scheme == 'file':
--                i = path.find('/', 2)
--                if i == -1:
--                    return ''
--                # On Windows, absolute paths are rooted at the current drive
--                # root. On POSIX they are rooted at the file system root.
--                if os.name == 'nt':
--                    droot = os.path.splitdrive(os.getcwd())[0] + '/'
--                    path = os.path.join(droot, path[i + 1:])
--                else:
--                    path = path[i:]
--            else:
--                path = path[2:]
--    return path
--
- def uirepr(s):
-     # Avoid double backslash in Windows path repr()
-     return repr(s).replace('\\\\', '\\')
-diff --git a/tests/test-bundle.t b/tests/test-bundle.t
---- a/tests/test-bundle.t
-+++ b/tests/test-bundle.t
-@@ -139,7 +139,7 @@ Log -R full.hg in fresh empty
-   $ rm -r empty
-   $ hg init empty
-   $ cd empty
--  $ hg -R bundle://../full.hg log
-+  $ hg -R bundle:../full.hg log
-   changeset:   8:aa35859c02ea
-   tag:         tip
-   parent:      3:eebf5a27f8ca
-@@ -189,6 +189,12 @@ Log -R full.hg in fresh empty
-   summary:     0.0
-   
- 
-+Pull invalid relative URL
-+
-+  $ hg pull bundle://../full.hg
-+  abort: No such file or directory: bundle://../full.hg
-+  [255]
-+
- Pull ../full.hg into empty (with hook)
- 
-   $ echo '[hooks]' >> .hg/hgrc
-@@ -196,11 +202,11 @@ Pull ../full.hg into empty (with hook)
- 
- doesn't work (yet ?)
- 
--hg -R bundle://../full.hg verify
-+hg -R bundle:../full.hg verify
- 
--  $ hg pull bundle://../full.hg
-+  $ hg pull bundle:../full.hg
-   changegroup hook: HG_NODE=f9ee2f85a263049e9ae6d37a0e67e96194ffb735 HG_SOURCE=pull HG_URL=bundle:../full.hg 
--  pulling from bundle://../full.hg
-+  pulling from bundle:../full.hg
-   requesting all changes
-   adding changesets
-   adding manifests
-@@ -249,7 +255,7 @@ Create partial clones
- 
- Log -R full.hg in partial
- 
--  $ hg -R bundle://../full.hg log
-+  $ hg -R bundle:../full.hg log
-   changeset:   8:aa35859c02ea
-   tag:         tip
-   parent:      3:eebf5a27f8ca
-@@ -301,8 +307,8 @@ Log -R full.hg in partial
- 
- Incoming full.hg in partial
- 
--  $ hg incoming bundle://../full.hg
--  comparing with bundle://../full.hg
-+  $ hg incoming bundle:../full.hg
-+  comparing with bundle:../full.hg
-   searching for changes
-   changeset:   4:095197eb4973
-   parent:      0:f9ee2f85a263
-@@ -335,7 +341,7 @@ Incoming full.hg in partial
- 
- Outgoing -R full.hg vs partial2 in partial
- 
--  $ hg -R bundle://../full.hg outgoing ../partial2
-+  $ hg -R bundle:../full.hg outgoing ../partial2
-   comparing with ../partial2
-   searching for changes
-   changeset:   4:095197eb4973
-@@ -369,7 +375,7 @@ Outgoing -R full.hg vs partial2 in parti
- 
- Outgoing -R does-not-exist.hg vs partial2 in partial
- 
--  $ hg -R bundle://../does-not-exist.hg outgoing ../partial2
-+  $ hg -R bundle:../does-not-exist.hg outgoing ../partial2
-   abort: No such file or directory: ../does-not-exist.hg
-   [255]
-   $ cd ..
-@@ -455,6 +461,22 @@ test for 540d1059c802
-   
-   $ cd ..
- 
-+test bundle with # in the filename (issue2154):
-+
-+  $ cp bundle.hg 'test#bundle.hg'
-+  $ cd orig
-+  $ hg incoming '../test#bundle.hg'
-+  comparing with ../test
-+  abort: unknown revision 'bundle.hg'!
-+  [255]
-+
-+note that percent encoding is not handled:
-+
-+  $ hg incoming ../test%23bundle.hg
-+  abort: repository ../test%23bundle.hg not found!
-+  [255]
-+  $ cd ..
-+
- test for http://mercurial.selenic.com/bts/issue1144
- 
- test that verify bundle does not traceback
-diff --git a/tests/test-pull.t b/tests/test-pull.t
---- a/tests/test-pull.t
-+++ b/tests/test-pull.t
-@@ -68,7 +68,11 @@ Issue622: hg init && hg pull -u URL does
- Test 'file:' uri handling:
- 
-   $ hg pull -q file://../test-doesnt-exist
--  abort: repository /test-doesnt-exist not found!
-+  abort: repository file://../test-doesnt-exist not found!
-+  [255]
-+
-+  $ hg pull -q file://../test
-+  abort: repository file://../test not found!
-   [255]
- 
-   $ hg pull -q file:../test
-@@ -78,4 +82,6 @@ regular shell commands.
- 
-   $ URL=`python -c "import os; print 'file://foobar' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"`
-   $ hg pull -q "$URL"
-+  abort: repository file://foobar/*/test-pull.t/empty/../test not found! (glob)
-+  [255]
- 

File url-localpath

+# HG changeset patch
+# Parent 425fd474322bbaae04ae0f7ab6e618b4ad2420c6
+url: use url.URL in util.drop_scheme() and remove hg.localpath()
+
+This updates drop_scheme() to use url.URL for parsing instead of doing
+it on its own. The function is moved from util to url to avoid an
+import cycle.
+
+hg.localpath() is removed in favor of using drop_scheme(). This
+provides more consistent behavior between "hg clone" and other
+commands.
+
+This change has a side effect of making URLs like bundle://../foo not
+correspond to relative paths with "hg bundle". This is because ".." is
+considered to be the host for the URL. The URL should be bundle:../foo
+to be relative.
+
+Invalid URLs like the one above never worked with push, in, out, etc.,
+so this makes URL handling more consistent.
+
+This also changes the behavior of push, in, out, etc. in that they no
+longer ignore the host part of file:// URLs when converting them to
+local paths. If the host is not "localhost", it isn't converted.
+
+Comparison of old localpath() behavior and new drop_scheme() behavior:
+
+URL                          Old return value      New return value
+===                          ================      ================
+file://foo/foo               foo/foo               file://foo/foo
+file://localhost:80/foo      localhost:80/foo      file://localhost:80/foo
+file://localhost:/foo        localhost:/foo        file://localhost:/foo
+file://localhost/foo         /foo                  /foo
+file:///foo                  /foo                  /foo
+file://foo                   foo                   file://foo
+file:/foo                    /foo                  /foo
+file:foo                     foo                   foo
+/foo                         /foo                  /foo
+file://C:/foo                C:/foo                file://C:/foo
+file:///C:/foo               /C:/foo               /C:/foo
+
+On Windows:
+
+URL                          Old return value      New return value
+===                          ================      ================
+file://localhost:80/foo      localhost:80/foo      file://localhost:80/foo
+file://localhost:/foo        localhost:/foo        file://localhost:/foo
+file://localhost/foo         C:/foo                C:/foo
+file:///foo                  C:/foo                C:/foo
+file://foo                   foo                   file://foo
+file:/foo                    C:/foo                C:/foo
+file:foo                     foo                   foo
+/foo                         /foo                  /foo
+file://C:/foo                C:/foo                file://C:/foo
+file:///C:/foo               /C:/foo               C:/foo
+
+The above behavior now also applies to the bundle scheme.
+
+Related issues:
+
+- issue1153: File URIs aren't handled correctly in windows
+
+  This patch should preserve the fix implemented in 2770d03ae49f. It
+  could, however, go a step further and "promote" Windows-style drive
+  letters from being interpreted as host names to being part of the
+  path. As of now, file://C:/foo has a host of C and a path of /foo.
+
+- issue2154: Cannot escape '#' in Mercurial URLs (#1172 in THG)
+
+  The fragment is still interpreted as a revision or a branch, even in
+  paths to bundles. This patch does not implement percent encoding
+  handling.
+
+  XXX It might make sense to decode percent escapes only when a
+  scheme is specified.
+
+diff --git a/mercurial/bundlerepo.py b/mercurial/bundlerepo.py
+--- a/mercurial/bundlerepo.py
++++ b/mercurial/bundlerepo.py
+@@ -15,7 +15,7 @@ from node import nullid
+ from i18n import _
+ import os, struct, tempfile, shutil
+ import changegroup, util, mdiff, discovery
+-import localrepo, changelog, manifest, filelog, revlog, error
++import localrepo, changelog, manifest, filelog, revlog, error, url
+ 
+ class bundlerevlog(revlog.revlog):
+     def __init__(self, opener, indexfile, bundle,
+@@ -279,9 +279,9 @@ def instance(ui, path, create):
+             cwd = os.path.join(cwd,'')
+             if parentpath.startswith(cwd):
+                 parentpath = parentpath[len(cwd):]
+-    path = util.drop_scheme('file', path)
++    path = url.localpath(path)
+     if path.startswith('bundle:'):
+-        path = util.drop_scheme('bundle', path)
++        path = url.localpath(path)
+         s = path.split("+", 1)
+         if len(s) == 1:
+             repopath, bundlename = parentpath, s[0]
+diff --git a/mercurial/hg.py b/mercurial/hg.py
+--- a/mercurial/hg.py
++++ b/mercurial/hg.py
+@@ -17,7 +17,7 @@ import verify as verifymod
+ import errno, os, shutil
+ 
+ def _local(path):
+-    path = util.expandpath(util.drop_scheme('file', path))
++    path = util.expandpath(url.localpath(path))
+     return (os.path.isfile(path) and bundlerepo or localrepo)
+ 
+ def addbranchrevs(lrepo, repo, branches, revs):
+@@ -104,15 +104,6 @@ def defaultdest(source):
+     '''return default destination of clone if none is given'''
+     return os.path.basename(os.path.normpath(source))
+ 
+-def localpath(path):
+-    if path.startswith('file://localhost/'):
+-        return path[16:]
+-    if path.startswith('file://'):
+-        return path[7:]
+-    if path.startswith('file:'):
+-        return path[5:]
+-    return path
+-
+ def share(ui, source, dest=None, update=True):
+     '''create a shared repository'''
+ 
+@@ -232,8 +223,8 @@ def clone(ui, source, dest=None, pull=Fa
+     else:
+         dest = ui.expandpath(dest)
+ 
+-    dest = localpath(dest)
+-    source = localpath(source)
++    dest = url.localpath(dest)
++    source = url.localpath(source)
+ 
+     if os.path.exists(dest):
+         if not os.path.isdir(dest):
+@@ -259,7 +250,7 @@ def clone(ui, source, dest=None, pull=Fa
+         abspath = origsource
+         copy = False
+         if src_repo.cancopy() and islocal(dest):
+-            abspath = os.path.abspath(util.drop_scheme('file', origsource))
++            abspath = os.path.abspath(url.localpath(origsource))
+             copy = not pull and not rev
+ 
+         if copy:
+diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
+--- a/mercurial/localrepo.py
++++ b/mercurial/localrepo.py
+@@ -1898,7 +1898,7 @@ def aftertrans(files):
+     return a
+ 
+ def instance(ui, path, create):
+-    return localrepository(ui, util.drop_scheme('file', path), create)
++    return localrepository(ui, urlmod.localpath(path), create)
+ 
+ def islocal(path):
+     return True
+diff --git a/mercurial/url.py b/mercurial/url.py
+--- a/mercurial/url.py
++++ b/mercurial/url.py
+@@ -190,6 +190,23 @@ class url(object):
+ def has_scheme(path):
+     return bool(url(path).scheme)
+ 
++def localpath(path):
++    u = url(path, parse_query=False, parse_fragment=False)
++    if u.scheme == 'file' or u.scheme == 'bundle':
++        if u.host and (u.port is not None or u.host != 'localhost'):
++            return path
++        elif os.name == 'nt' and u.path and u.path.startswith('/'):
++            if u.path[1].isalpha() and u.path[2] == ':':
++                # Strip leading slash from paths with drive names
++                u.path = u.path[1:]
++            else:
++                # On Windows, absolute paths are rooted at the
++                # current drive root. On POSIX they are rooted at
++                # the file system root.
++                droot = os.path.splitdrive(os.getcwd())[0] + '/'
++                u.path = os.path.join(droot, u.path)
++    return u.path or ''
++
+ def hidepassword(u):
+     '''hide user credential in a url string'''
+     u = url(u)
+diff --git a/mercurial/util.py b/mercurial/util.py
+--- a/mercurial/util.py
++++ b/mercurial/util.py
+@@ -1301,26 +1301,6 @@ def bytecount(nbytes):
+             return format % (nbytes / float(divisor))
+     return units[-1][2] % nbytes
+ 
+-def drop_scheme(scheme, path):
+-    sc = scheme + ':'
+-    if path.startswith(sc):
+-        path = path[len(sc):]
+-        if path.startswith('//'):
+-            if scheme == 'file':
+-                i = path.find('/', 2)
+-                if i == -1:
+-                    return ''
+-                # On Windows, absolute paths are rooted at the current drive
+-                # root. On POSIX they are rooted at the file system root.
+-                if os.name == 'nt':
+-                    droot = os.path.splitdrive(os.getcwd())[0] + '/'
+-                    path = os.path.join(droot, path[i + 1:])
+-                else:
+-                    path = path[i:]
+-            else:
+-                path = path[2:]
+-    return path
+-
+ def uirepr(s):
+     # Avoid double backslash in Windows path repr()
+     return repr(s).replace('\\\\', '\\')
+diff --git a/tests/test-bundle.t b/tests/test-bundle.t
+--- a/tests/test-bundle.t
++++ b/tests/test-bundle.t
+@@ -139,7 +139,7 @@ Log -R full.hg in fresh empty
+   $ rm -r empty
+   $ hg init empty
+   $ cd empty
+-  $ hg -R bundle://../full.hg log
++  $ hg -R bundle:../full.hg log
+   changeset:   8:aa35859c02ea
+   tag:         tip
+   parent:      3:eebf5a27f8ca
+@@ -189,6 +189,12 @@ Log -R full.hg in fresh empty
+   summary:     0.0
+   
+ 
++Pull invalid relative URL
++
++  $ hg pull bundle://../full.hg
++  abort: No such file or directory: bundle://../full.hg
++  [255]
++
+ Pull ../full.hg into empty (with hook)
+ 
+   $ echo '[hooks]' >> .hg/hgrc
+@@ -196,11 +202,11 @@ Pull ../full.hg into empty (with hook)
+ 
+ doesn't work (yet ?)
+ 
+-hg -R bundle://../full.hg verify
++hg -R bundle:../full.hg verify
+ 
+-  $ hg pull bundle://../full.hg
++  $ hg pull bundle:../full.hg
+   changegroup hook: HG_NODE=f9ee2f85a263049e9ae6d37a0e67e96194ffb735 HG_SOURCE=pull HG_URL=bundle:../full.hg 
+-  pulling from bundle://../full.hg
++  pulling from bundle:../full.hg
+   requesting all changes
+   adding changesets
+   adding manifests
+@@ -249,7 +255,7 @@ Create partial clones
+ 
+ Log -R full.hg in partial
+ 
+-  $ hg -R bundle://../full.hg log
++  $ hg -R bundle:../full.hg log
+   changeset:   8:aa35859c02ea
+   tag:         tip
+   parent:      3:eebf5a27f8ca
+@@ -301,8 +307,8 @@ Log -R full.hg in partial
+ 
+ Incoming full.hg in partial
+ 
+-  $ hg incoming bundle://../full.hg
+-  comparing with bundle://../full.hg
++  $ hg incoming bundle:../full.hg
++  comparing with bundle:../full.hg
+   searching for changes
+   changeset:   4:095197eb4973
+   parent:      0:f9ee2f85a263
+@@ -335,7 +341,7 @@ Incoming full.hg in partial
+ 
+ Outgoing -R full.hg vs partial2 in partial
+ 
+-  $ hg -R bundle://../full.hg outgoing ../partial2
++  $ hg -R bundle:../full.hg outgoing ../partial2
+   comparing with ../partial2
+   searching for changes
+   changeset:   4:095197eb4973
+@@ -369,7 +375,7 @@ Outgoing -R full.hg vs partial2 in parti
+ 
+ Outgoing -R does-not-exist.hg vs partial2 in partial
+ 
+-  $ hg -R bundle://../does-not-exist.hg outgoing ../partial2
++  $ hg -R bundle:../does-not-exist.hg outgoing ../partial2
+   abort: No such file or directory: ../does-not-exist.hg
+   [255]
+   $ cd ..
+@@ -455,6 +461,22 @@ test for 540d1059c802
+   
+   $ cd ..
+ 
++test bundle with # in the filename (issue2154):
++
++  $ cp bundle.hg 'test#bundle.hg'
++  $ cd orig
++  $ hg incoming '../test#bundle.hg'
++  comparing with ../test
++  abort: unknown revision 'bundle.hg'!
++  [255]
++
++note that percent encoding is not handled:
++
++  $ hg incoming ../test%23bundle.hg
++  abort: repository ../test%23bundle.hg not found!
++  [255]
++  $ cd ..
++
+ test for http://mercurial.selenic.com/bts/issue1144
+ 
+ test that verify bundle does not traceback
+diff --git a/tests/test-pull.t b/tests/test-pull.t
+--- a/tests/test-pull.t
++++ b/tests/test-pull.t
+@@ -68,7 +68,11 @@ Issue622: hg init && hg pull -u URL does
+ Test 'file:' uri handling:
+ 
+   $ hg pull -q file://../test-doesnt-exist
+-  abort: repository /test-doesnt-exist not found!
++  abort: repository file://../test-doesnt-exist not found!
++  [255]
++
++  $ hg pull -q file://../test
++  abort: repository file://../test not found!
+   [255]
+ 
+   $ hg pull -q file:../test
+@@ -78,4 +82,6 @@ regular shell commands.
+ 
+   $ URL=`python -c "import os; print 'file://foobar' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"`
+   $ hg pull -q "$URL"
++  abort: repository file://foobar/*/test-pull.t/empty/../test not found! (glob)
++  [255]
+ 
 # HG changeset patch
-# Parent 4f45b616d4edf996e8f50e988b3ba5c053e54e1d
+# Parent d50834e8c57d673dcd8e3af6f6eeb106ae1f0636
 url: use url.URL in proxyhandler
 
 diff --git a/mercurial/url.py b/mercurial/url.py
 --- a/mercurial/url.py
 +++ b/mercurial/url.py
+@@ -7,7 +7,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 urllib, urllib2, urlparse, httplib, os, socket, cStringIO
++import urllib, urllib2, httplib, os, socket, cStringIO
+ import __builtin__
+ from i18n import _
+ import keepalive, util
 @@ -349,14 +349,10 @@ class proxyhandler(urllib2.ProxyHandler)
              if not (proxyurl.startswith('http:') or
                      proxyurl.startswith('https:')):
 # HG changeset patch
-# Parent 9ca153ab3c4b3c3c0a4d8e1f11f4f616326fe996
+# Parent ee0a05bd9667374afef41bbd800dc2adc313570c
 httprepo/sshrepo: use url.URL
 
 Like the previous patch to getauthinfo(), this also makes
  
          # urllib cannot handle URLs with embedded user or passwd
 -        self._url, authinfo = url.getauthinfo(path)
-+        self._url, authinfo = str(u), u.getauthinfo()
++        self._url, authinfo = str(u), u.authinfo()
  
          self.ui = ui
          self.ui.debug('using %s\n' % self._url)
  
          sshcmd = self.ui.config("ui", "ssh", "ssh")
          remotecmd = self.ui.config("ui", "remotecmd", "hg")
+diff --git a/mercurial/statichttprepo.py b/mercurial/statichttprepo.py
+--- a/mercurial/statichttprepo.py
++++ b/mercurial/statichttprepo.py
+@@ -86,7 +86,8 @@ class statichttprepository(localrepo.loc
+         self.ui = ui
+ 
+         self.root = path
+-        self.path, authinfo = url.getauthinfo(path.rstrip('/') + "/.hg")
++        u = url.url(path.rstrip('/') + "/.hg")
++        self.path, authinfo = str(u), u.authinfo()
+ 
+         opener = build_opener(ui, authinfo)
+         self.opener = opener(self.path)
+diff --git a/mercurial/url.py b/mercurial/url.py
+--- a/mercurial/url.py
++++ b/mercurial/url.py
+@@ -795,31 +795,6 @@ class httpbasicauthhandler(urllib2.HTTPB
+         return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
+                         self, auth_header, host, req, headers)
+ 
+-def getauthinfo(path):
+-    scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
+-    if not urlpath:
+-        urlpath = '/'
+-    if scheme != 'file':
+-        # XXX: why are we quoting the path again with some smart
+-        # heuristic here? Anyway, it cannot be done with file://
+-        # urls since path encoding is os/fs dependent (see
+-        # urllib.pathname2url() for details).
+-        urlpath = quotepath(urlpath)
+-    host, port, user, passwd = netlocsplit(netloc)
+-
+-    # urllib cannot handle URLs with embedded user or passwd
+-    url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
+-                              urlpath, query, frag))
+-    if user:
+-        netloc = host
+-        if port:
+-            netloc += ':' + port
+-        # Python < 2.4.3 uses only the netloc to search for a password
+-        authinfo = (None, (url, netloc), user, passwd or '')
+-    else:
+-        authinfo = None
+-    return url, authinfo
+-
+ handlerfuncs = []
+ 
+ def opener(ui, authinfo=None):