commit a3704312cc36f6bf5e9cbf89b37f62973f1d9c06 from: Jelmer Vernooij via: GitHub date: Mon May 16 19:45:20 2022 UTC Merge pull request #965 from dtrifiro/fix/windows-file-urls client: add support for file:// urls on windows commit - aa67e876cf7e27d67a6f9baf186bf2b2a70cad4e commit + a3704312cc36f6bf5e9cbf89b37f62973f1d9c06 blob - 0afceda1a36907e148b0bb4841a8c8d6bc6b4fa8 blob + da39847fd9a87b55175e2b7890d58abed6abe2b3 --- dulwich/client.py +++ dulwich/client.py @@ -57,7 +57,9 @@ from urllib.parse import ( urlunsplit, urlunparse, ) +from urllib.request import url2pathname + import dulwich from dulwich.config import get_xdg_config_home_path from dulwich.errors import ( @@ -2236,6 +2238,33 @@ class Urllib3HttpGitClient(AbstractHttpGitClient): HttpGitClient = Urllib3HttpGitClient +def _win32_url_to_path(parsed) -> str: + """ + Convert a file: URL to a path. + + https://datatracker.ietf.org/doc/html/rfc8089 + """ + assert sys.platform == "win32" or os.name == "nt" + assert parsed.scheme == "file" + + _, netloc, path, _, _, _ = parsed + + if netloc == "localhost" or not netloc: + netloc = "" + elif ( + netloc + and len(netloc) >= 2 + and netloc[0].isalpha() + and netloc[1:2] in (":", ":/") + ): + # file://C:/foo.bar/baz or file://C://foo.bar//baz + netloc = netloc[:2] + else: + raise NotImplementedError("Non-local file URLs are not supported") + + return url2pathname(netloc + path) + + def get_transport_and_path_from_url(url, config=None, **kwargs): """Obtain a git client from a URL. @@ -2261,6 +2290,8 @@ def get_transport_and_path_from_url(url, config=None, parsed.path, ) elif parsed.scheme == "file": + if sys.platform == "win32" or os.name == "nt": + return default_local_git_client_cls(**kwargs), _win32_url_to_path(parsed) return ( default_local_git_client_cls.from_parsedurl(parsed, **kwargs), parsed.path, blob - 9937d034e4255379f79e52cb269ba287f22e5c30 blob + 12f62d15e3ef2f408dc60a906e06e474720ff45a --- dulwich/tests/test_client.py +++ dulwich/tests/test_client.py @@ -30,6 +30,8 @@ from urllib.parse import ( quote as urlquote, urlparse, ) + +from unittest.mock import patch import dulwich from dulwich import ( @@ -682,12 +684,42 @@ class TestGetTransportAndPathFromUrl(TestCase): self.assertIsInstance(c, HttpGitClient) self.assertEqual("/jelmer/dulwich", path) + @patch("os.name", "posix") + @patch("sys.platform", "linux") def test_file(self): c, path = get_transport_and_path_from_url("file:///home/jelmer/foo") self.assertIsInstance(c, LocalGitClient) self.assertEqual("/home/jelmer/foo", path) + + @patch("os.name", "nt") + @patch("sys.platform", "win32") + def test_file_win(self): + # `_win32_url_to_path` uses urllib.request.url2pathname, which is set to + # `ntutl2path.url2pathname` when `os.name==nt` + from nturl2path import url2pathname + + with patch("dulwich.client.url2pathname", url2pathname): + expected = "C:\\foo.bar\\baz" + for file_url in [ + "file:C:/foo.bar/baz", + "file:/C:/foo.bar/baz", + "file://C:/foo.bar/baz", + "file://C://foo.bar//baz", + "file:///C:/foo.bar/baz", + ]: + c, path = get_transport_and_path(file_url) + self.assertIsInstance(c, LocalGitClient) + self.assertEqual(path, expected) + for remote_url in [ + "file://host.example.com/C:/foo.bar/baz" + "file://host.example.com/C:/foo.bar/baz" + "file:////host.example/foo.bar/baz", + ]: + with self.assertRaises(NotImplementedError): + c, path = get_transport_and_path(remote_url) + class TestSSHVendor(object): def __init__(self): self.host = None