Переглянути джерело

Fix LFS URL validation to support file:// URLs

Jelmer Vernooij 3 місяців тому
батько
коміт
2e1baf58df
3 змінених файлів з 75 додано та 7 видалено
  1. 2 1
      NEWS
  2. 23 4
      dulwich/lfs.py
  3. 50 2
      tests/test_lfs.py

+ 2 - 1
NEWS

@@ -12,7 +12,8 @@
    vendor. Regression from 0.24.2.  (Jelmer Vernooij, #1945)
 
  * Fix LFS URL validation to prevent DNS resolution errors when ``lfs.url`` is
-   configured with an invalid value. (Jelmer Vernooij, #1951)
+   configured with an invalid value. Add support for ``file://`` URLs to match
+   git-lfs behavior. (Jelmer Vernooij, #1951)
 
 0.24.6	2025-10-19
 

+ 23 - 4
dulwich/lfs.py

@@ -351,16 +351,35 @@ def _get_lfs_user_agent(config: Optional["Config"]) -> str:
 
 
 def _is_valid_lfs_url(url: str) -> bool:
-    """Check if a URL is valid for LFS (has scheme and netloc).
+    """Check if a URL is valid for LFS.
+
+    Git LFS supports http://, https://, and file:// URLs.
 
     Args:
         url: URL to validate
 
     Returns:
-        True if URL has both scheme and netloc, False otherwise
+        True if URL is a valid LFS URL, False otherwise
     """
     parsed = urlparse(url)
-    return bool(parsed.scheme and parsed.netloc)
+
+    # Must have a scheme
+    if not parsed.scheme:
+        return False
+
+    # Only support http, https, and file schemes
+    if parsed.scheme not in ("http", "https", "file"):
+        return False
+
+    # http/https require a hostname
+    if parsed.scheme in ("http", "https"):
+        return bool(parsed.netloc)
+
+    # file:// URLs must have a path (netloc is typically empty)
+    if parsed.scheme == "file":
+        return bool(parsed.path)
+
+    return False
 
 
 class LFSClient:
@@ -390,7 +409,7 @@ class LFSClient:
             if not _is_valid_lfs_url(url):
                 raise ValueError(
                     f"Invalid lfs.url in config: {url!r}. "
-                    "URL must include scheme (http/https) and hostname."
+                    "URL must be an absolute URL with scheme http://, https://, or file://."
                 )
             return cls(url, config)
 

+ 50 - 2
tests/test_lfs.py

@@ -1136,15 +1136,63 @@ class LFSClientTests(TestCase):
             LFSClient.from_config(config)
         self.assertIn("Invalid lfs.url", str(cm.exception))
 
-        # Test with valid URL - should succeed
+        # Test with relative path - should be rejected (not supported by git-lfs)
+        config.set((b"lfs",), b"url", b"../lfs")
+        with self.assertRaises(ValueError) as cm:
+            LFSClient.from_config(config)
+        self.assertIn("Invalid lfs.url", str(cm.exception))
+
+        # Test with relative path starting with ./
+        config.set((b"lfs",), b"url", b"./lfs")
+        with self.assertRaises(ValueError) as cm:
+            LFSClient.from_config(config)
+        self.assertIn("Invalid lfs.url", str(cm.exception))
+
+        # Test with unsupported scheme - git://
+        config.set((b"lfs",), b"url", b"git://example.com/repo.git")
+        with self.assertRaises(ValueError) as cm:
+            LFSClient.from_config(config)
+        self.assertIn("Invalid lfs.url", str(cm.exception))
+
+        # Test with unsupported scheme - ssh://
+        config.set((b"lfs",), b"url", b"ssh://git@example.com/repo.git")
+        with self.assertRaises(ValueError) as cm:
+            LFSClient.from_config(config)
+        self.assertIn("Invalid lfs.url", str(cm.exception))
+
+        # Test with http:// but no hostname
+        config.set((b"lfs",), b"url", b"http://")
+        with self.assertRaises(ValueError) as cm:
+            LFSClient.from_config(config)
+        self.assertIn("Invalid lfs.url", str(cm.exception))
+
+        # Test with valid https URL - should succeed
         config.set((b"lfs",), b"url", b"https://example.com/repo.git/info/lfs")
         client = LFSClient.from_config(config)
         self.assertIsNotNone(client)
+        assert client is not None  # for mypy
         self.assertEqual(client.url, "https://example.com/repo.git/info/lfs")
 
+        # Test with valid http URL - should succeed
+        config.set((b"lfs",), b"url", b"http://localhost:8080/lfs")
+        client = LFSClient.from_config(config)
+        self.assertIsNotNone(client)
+        assert client is not None  # for mypy
+        self.assertEqual(client.url, "http://localhost:8080/lfs")
+
+        # Test with valid file:// URL - should succeed
+        config.set((b"lfs",), b"url", b"file:///path/to/lfs")
+        client = LFSClient.from_config(config)
+        self.assertIsNotNone(client)
+        assert client is not None  # for mypy
+        self.assertEqual(client.url, "file:///path/to/lfs")
+
         # Test with no lfs.url but valid remote - should derive URL
         config2 = ConfigFile()
-        config2.set((b"remote", b"origin"), b"url", b"https://example.com/user/repo.git")
+        config2.set(
+            (b"remote", b"origin"), b"url", b"https://example.com/user/repo.git"
+        )
         client2 = LFSClient.from_config(config2)
         self.assertIsNotNone(client2)
+        assert client2 is not None  # for mypy
         self.assertEqual(client2.url, "https://example.com/user/repo.git/info/lfs")