From 3a43fa887eb75efe606b81e2bfe1c762abb71bbe Mon Sep 17 00:00:00 2001 From: Brodie Rao Date: Wed, 30 Mar 2011 20:03:05 -0700 Subject: [PATCH] url: refactor util.drop_scheme() and hg.localpath() into url.localpath() This replaces util.drop_scheme() with url.localpath(), using 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 url.localpath(). This provides more consistent behavior between "hg clone" and other commands. To preserve backwards compatibility, URLs like bundle://../foo still refer to ../foo, not /foo. If a URL contains a scheme, percent-encoded entities are decoded. When there's no scheme, all characters are left untouched. Comparison of old and new behaviors: URL drop_scheme() hg.localpath() url.localpath() === ============= ============== =============== file://foo/foo /foo foo/foo /foo file://localhost:80/foo /foo localhost:80/foo /foo file://localhost:/foo /foo localhost:/foo /foo file://localhost/foo /foo /foo /foo file:///foo /foo /foo /foo file://foo (empty string) foo / file:/foo /foo /foo /foo file:foo foo foo foo file:foo%23bar foo%23bar foo%23bar foo#bar foo%23bar foo%23bar foo%23bar foo%23bar /foo /foo /foo /foo Windows-related paths on Windows: URL drop_scheme() hg.localpath() url.localpath() === ============= ============== =============== file:///C:/foo C:/C:/foo /C:/foo C:/foo file:///D:/foo C:/D:/foo /D:/foo D:/foo file://C:/foo C:/foo C:/foo C:/foo file://D:/foo C:/foo D:/foo D:/foo file:////foo/bar //foo/bar //foo/bar //foo/bar //foo/bar //foo/bar //foo/bar //foo/bar \\foo\bar //foo/bar //foo/bar \\foo\bar Windows-related paths on other platforms: file:///C:/foo C:/C:/foo /C:/foo C:/foo file:///D:/foo C:/D:/foo /D:/foo D:/foo file://C:/foo C:/foo C:/foo C:/foo file://D:/foo C:/foo D:/foo D:/foo file:////foo/bar //foo/bar //foo/bar //foo/bar //foo/bar //foo/bar //foo/bar //foo/bar \\foo\bar //foo/bar //foo/bar \\foo\bar For more information about file:// URL handling, see: http://www-archive.mozilla.org/quality/networking/testing/filetests.html Related issues: - issue1153: File URIs aren't handled correctly in windows This patch should preserve the fix implemented in 5c92d05b064e. However, it goes a step further and "promotes" Windows-style drive letters from being interpreted as host names to being part of the path. - 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. However, when file: is used, percent-encoded entities are decoded, so file:test%23bundle.hg can refer to test#bundle.hg ond isk. --- mercurial/bundlerepo.py | 8 ++++---- mercurial/hg.py | 17 ++++------------- mercurial/localrepo.py | 2 +- mercurial/url.py | 23 +++++++++++++++++++++++ mercurial/util.py | 20 -------------------- tests/test-bundle.t | 16 ++++++++++++++++ tests/test-pull.t | 4 ++++ 7 files changed, 52 insertions(+), 38 deletions(-) diff --git a/mercurial/bundlerepo.py b/mercurial/bundlerepo.py index 307c3150a5..d56a9a8a54 100644 --- 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, @@ -274,9 +274,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) - if path.startswith('bundle:'): - path = util.drop_scheme('bundle', path) + u = url.url(path) + path = u.localpath() + if u.scheme == 'bundle': s = path.split("+", 1) if len(s) == 1: repopath, bundlename = parentpath, s[0] diff --git a/mercurial/hg.py b/mercurial/hg.py index a89062ac74..cf03972fd0 100644 --- 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): @@ -102,15 +102,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''' @@ -230,8 +221,8 @@ def clone(ui, source, dest=None, pull=False, rev=None, update=True, 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): @@ -257,7 +248,7 @@ def clone(ui, source, dest=None, pull=False, rev=None, update=True, 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 index b15b5bc886..e4c0176b8e 100644 --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -1928,7 +1928,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 index 5b20c29412..19969dbbf4 100644 --- a/mercurial/url.py +++ b/mercurial/url.py @@ -70,6 +70,8 @@ class url(object): self.scheme = self.user = self.passwd = self.host = None self.port = self.path = self.query = self.fragment = None self._localpath = True + self._hostport = '' + self._origpath = path # special case for Windows drive letters if has_drive_letter(path): @@ -137,6 +139,7 @@ class url(object): # Don't split on colons in IPv6 addresses without ports if (self.host and ':' in self.host and not (self.host.startswith('[') and self.host.endswith(']'))): + self._hostport = self.host self.host, self.port = self.host.rsplit(':', 1) if not self.host: self.host = None @@ -231,12 +234,32 @@ class url(object): return (s, (None, (str(self), self.host), self.user, self.passwd or '')) + def localpath(self): + if self.scheme == 'file' or self.scheme == 'bundle': + path = self.path or '/' + # For Windows, we need to promote hosts containing drive + # letters to paths with drive letters. + if has_drive_letter(self._hostport): + path = self._hostport + '/' + self.path + elif self.host is not None and self.path: + path = '/' + path + # We also need to handle the case of file:///C:/, which + # should return C:/, not /C:/. + elif has_drive_letter(path): + # Strip leading slash from paths with drive names + return path[1:] + return path + return self._origpath + def has_scheme(path): return bool(url(path).scheme) def has_drive_letter(path): return path[1:2] == ':' and path[0:1].isalpha() +def localpath(path): + return url(path, parse_query=False, parse_fragment=False).localpath() + def hidepassword(u): '''hide user credential in a url string''' u = url(u) diff --git a/mercurial/util.py b/mercurial/util.py index c02c5dba52..bd11f7e346 100644 --- a/mercurial/util.py +++ b/mercurial/util.py @@ -1384,26 +1384,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 index f2e9103fef..cb73a7ed29 100644 --- a/tests/test-bundle.t +++ b/tests/test-bundle.t @@ -470,6 +470,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 index b38b25d6fa..9d76906364 100644 --- a/tests/test-pull.t +++ b/tests/test-pull.t @@ -71,6 +71,10 @@ Test 'file:' uri handling: abort: file:// URLs can only refer to localhost [255] + $ hg pull -q file://../test + abort: file:// URLs can only refer to localhost + [255] + $ hg pull -q file:../test It's tricky to make file:// URLs working on every platform with