فهرست منبع

Add IPv6 support for git:// protocol URLs

The TCPGitClient class now properly formats IPv6 addresses in git:// URLs
by wrapping them in square brackets as required by RFC 3986. The underlying
socket connection already supported IPv6 through AF_UNSPEC.

Fixes #1796
Jelmer Vernooij 4 ماه پیش
والد
کامیت
41b67fda54
3فایلهای تغییر یافته به همراه49 افزوده شده و 1 حذف شده
  1. 3 0
      NEWS
  2. 6 1
      dulwich/client.py
  3. 40 0
      tests/test_client.py

+ 3 - 0
NEWS

@@ -37,6 +37,9 @@
  * Add support for ``patiencediff`` algorithm in diff.
    (Jelmer Vernooij, #1795)
 
+ * Add IPv6 support for git:// protocol URLs.
+   (Jelmer Vernooij, #1796)
+
 0.24.1	2025-08-01
 
  * Require ``typing_extensions`` on Python 3.10.

+ 6 - 1
dulwich/client.py

@@ -1708,7 +1708,12 @@ class TCPGitClient(TraditionalGitClient):
         Returns:
           ``git://`` URL for the path
         """
-        netloc = self._host
+        # IPv6 addresses contain colons and need to be wrapped in brackets
+        if ":" in self._host:
+            netloc = f"[{self._host}]"
+        else:
+            netloc = self._host
+
         if self._port is not None and self._port != TCP_GIT_PORT:
             netloc += f":{self._port}"
         return urlunsplit(("git", netloc, path, "", ""))

+ 40 - 0
tests/test_client.py

@@ -424,6 +424,20 @@ class TestGetTransportAndPath(TestCase):
         self.assertEqual(1234, c._port)
         self.assertEqual("/bar/baz", path)
 
+    def test_tcp_ipv6(self) -> None:
+        c, path = get_transport_and_path("git://[::1]/bar/baz")
+        self.assertIsInstance(c, TCPGitClient)
+        self.assertEqual("::1", c._host)
+        self.assertEqual(TCP_GIT_PORT, c._port)
+        self.assertEqual("/bar/baz", path)
+
+    def test_tcp_ipv6_port(self) -> None:
+        c, path = get_transport_and_path("git://[2001:db8::1]:1234/bar/baz")
+        self.assertIsInstance(c, TCPGitClient)
+        self.assertEqual("2001:db8::1", c._host)
+        self.assertEqual(1234, c._port)
+        self.assertEqual("/bar/baz", path)
+
     def test_git_ssh_explicit(self) -> None:
         c, path = get_transport_and_path("git+ssh://foo.com/bar/baz")
         self.assertIsInstance(c, SSHGitClient)
@@ -1642,6 +1656,32 @@ class TCPGitClientTests(TestCase):
         url = c.get_url(path)
         self.assertEqual("git://github.com:9090/jelmer/dulwich", url)
 
+    def test_get_url_with_ipv6(self) -> None:
+        host = "::1"
+        path = "/jelmer/dulwich"
+        c = TCPGitClient(host)
+
+        url = c.get_url(path)
+        self.assertEqual("git://[::1]/jelmer/dulwich", url)
+
+    def test_get_url_with_ipv6_and_port(self) -> None:
+        host = "2001:db8::1"
+        path = "/jelmer/dulwich"
+        port = 9090
+        c = TCPGitClient(host, port=port)
+
+        url = c.get_url(path)
+        self.assertEqual("git://[2001:db8::1]:9090/jelmer/dulwich", url)
+
+    def test_get_url_with_ipv6_default_port(self) -> None:
+        host = "2001:db8::1"
+        path = "/jelmer/dulwich"
+        port = TCP_GIT_PORT  # Default port should not be included in URL
+        c = TCPGitClient(host, port=port)
+
+        url = c.get_url(path)
+        self.assertEqual("git://[2001:db8::1]/jelmer/dulwich", url)
+
 
 class DefaultUrllib3ManagerTest(TestCase):
     def test_no_config(self) -> None: