comparison mercurial/subrepo.py @ 35777:0c0689a7565e

subrepo: handle 'C:' style paths on the command line (issue5770) If you think 'C:' and 'C:\' are equivalent paths, see the inline comment before proceeding. The problem here was that several commands that take a URL argument (incoming, outgoing, pull, and push) will use that value to set 'repo._subtoppath' on the repository object after command specific manipulation of it, but before converting it to an absolute path. When an operation is performed on a relative subrepo, subrepo._abssource() will posixpath.join() this value with the relative subrepo path. That adds a '/' after the drive letter, changing how it is evaluated by abspath()/realpath() in vfsmod.vfs(..., realpath=True) as the subrepo is instantiated. I initially tried sanitizing the path in url.localpath(), because url.isabs() only checks that it starts with a drive letter. By the sample behavior, this is clearly not an absolute path. (Though the comment in isabs() is weasely- this style path can't be joined either.) But not everything funnels through there, and it required explicitly calling localpath() in hg.parseurl() and assigning to url.path to fix. But then tests failed with urls like 'a#0'. Next up was sanitizing the path in the url constructor. That caused doctest failures, because there are drive letter tests, so those got expanded in system specific ways. Yuya correctly pointed out that util.url is a parser, and shouldn't be substituting the path too. Rather than fixing every command call site, just convert it in the common subrepo location. I don't see any sanitizing on the path config options, so I fixed those too. Note that while the behavior is fixed here, there are still places where 'comparing with C:' gets printed out, and that's not great for debugging purposes. (Specifically I saw it in `hg incoming -B C:`, without subrepos.) While clone will write out an absolute default path, I wonder what would happen if a user edited that path to be 'C:'. (I don't think supporting relative paths in .hgrc is a sane thing to do, but while we're poking holes in things...) Since this is such an oddball case, it still leaks through in places, and there seems to be a lot of duplicate url parsing, maybe the url parsing should be moved to dispatch, and provide the command with a url object? Then we could convert this to an absolute path once, and not have to worry about it in the rest of the code. I also checked '--cwd C:' on the command line, and it was previously working because os.chdir() will DTRT. Finally, one other note from the url.localpath() experimenting. I don't see any cases where 'self._hostport' can hold a drive letter. So I'm wondering if that is wrong/old code.
author Matt Harbison <matt_harbison@yahoo.com>
date Sun, 21 Jan 2018 13:54:05 -0500
parents 9c575c22dcf4
children eed02e192770 c8e2d6ed1f9e
comparison
equal deleted inserted replaced
35776:75bae69747f0 35777:0c0689a7565e
396 parent = util.url(util.pconvert(parent)) 396 parent = util.url(util.pconvert(parent))
397 parent.path = posixpath.join(parent.path or '', source.path) 397 parent.path = posixpath.join(parent.path or '', source.path)
398 parent.path = posixpath.normpath(parent.path) 398 parent.path = posixpath.normpath(parent.path)
399 return bytes(parent) 399 return bytes(parent)
400 else: # recursion reached top repo 400 else: # recursion reached top repo
401 path = None
401 if util.safehasattr(repo, '_subtoppath'): 402 if util.safehasattr(repo, '_subtoppath'):
402 return repo._subtoppath 403 path = repo._subtoppath
403 if push and repo.ui.config('paths', 'default-push'): 404 elif push and repo.ui.config('paths', 'default-push'):
404 return repo.ui.config('paths', 'default-push') 405 path = repo.ui.config('paths', 'default-push')
405 if repo.ui.config('paths', 'default'): 406 elif repo.ui.config('paths', 'default'):
406 return repo.ui.config('paths', 'default') 407 path = repo.ui.config('paths', 'default')
407 if repo.shared(): 408 elif repo.shared():
408 # chop off the .hg component to get the default path form 409 # chop off the .hg component to get the default path form. This has
410 # already run through vfsmod.vfs(..., realpath=True), so it doesn't
411 # have problems with 'C:'
409 return os.path.dirname(repo.sharedpath) 412 return os.path.dirname(repo.sharedpath)
413 if path:
414 # issue5770: 'C:\' and 'C:' are not equivalent paths. The former is
415 # as expected: an absolute path to the root of the C: drive. The
416 # latter is a relative path, and works like so:
417 #
418 # C:\>cd C:\some\path
419 # C:\>D:
420 # D:\>python -c "import os; print os.path.abspath('C:')"
421 # C:\some\path
422 #
423 # D:\>python -c "import os; print os.path.abspath('C:relative')"
424 # C:\some\path\relative
425 if util.hasdriveletter(path):
426 if len(path) == 2 or path[2:3] not in br'\/':
427 path = os.path.abspath(path)
428 return path
429
410 if abort: 430 if abort:
411 raise error.Abort(_("default path for subrepository not found")) 431 raise error.Abort(_("default path for subrepository not found"))
412 432
413 def _sanitize(ui, vfs, ignore): 433 def _sanitize(ui, vfs, ignore):
414 for dirname, dirs, names in vfs.walk(): 434 for dirname, dirs, names in vfs.walk():