mirror of
https://github.com/brunopostle/ifcurl.git
synced 2026-07-12 10:18:14 +00:00
Fix port preservation and HTTP fallback for local Gitea
- url.py: preserve non-standard ports in host field; ifc://host:3000/... now correctly generates https://host:3000/... rather than dropping :3000 - git.py: fall back from https to http on clone/fetch failure, enabling local Gitea instances running on plain HTTP; inject tokens into http:// URLs as well as https://; extract _clone_bare() helper - tests/test_url.py: three new tests for port preservation in host, git_remote_url with non-standard HTTPS port, and SSH with non-standard port
This commit is contained in:
parent
173cf653a8
commit
778f0004e7
3 changed files with 72 additions and 12 deletions
|
|
@ -128,12 +128,35 @@ def _cache_dir_for(remote_url: str) -> Path:
|
|||
|
||||
def _auth_url(remote_url: str, token: str | None) -> str:
|
||||
"""Return *remote_url* with the token injected, or the original URL."""
|
||||
if token and remote_url.startswith("https://"):
|
||||
if token and (remote_url.startswith("https://") or remote_url.startswith("http://")):
|
||||
from ifcurl.auth import inject_token
|
||||
return inject_token(remote_url, token)
|
||||
return remote_url
|
||||
|
||||
|
||||
def _http_fallback(url: str) -> str | None:
|
||||
"""Return the http:// equivalent of an https:// URL, or None."""
|
||||
if url.startswith("https://"):
|
||||
return "http" + url[5:]
|
||||
return None
|
||||
|
||||
|
||||
def _clone_bare(auth_url: str, git_dir: Path, remote_url: str) -> git.Repo:
|
||||
"""Clone *auth_url* as a bare repo to *git_dir*, falling back to http
|
||||
when the server does not speak HTTPS (common for local dev instances).
|
||||
"""
|
||||
try:
|
||||
return git.Repo.clone_from(auth_url, str(git_dir), bare=True)
|
||||
except git.exc.GitCommandError as exc:
|
||||
fallback = _http_fallback(auth_url)
|
||||
if fallback:
|
||||
try:
|
||||
return git.Repo.clone_from(fallback, str(git_dir), bare=True)
|
||||
except git.exc.GitCommandError:
|
||||
pass # report the original error
|
||||
raise ValueError(f"Failed to clone {remote_url!r}: {exc.stderr.strip()}") from exc
|
||||
|
||||
|
||||
def _open_remote(remote_url: str, is_mutable: bool, token: str | None = None) -> git.Repo:
|
||||
"""Return a GitPython Repo for *remote_url*, cloning it if necessary.
|
||||
|
||||
|
|
@ -141,26 +164,35 @@ def _open_remote(remote_url: str, is_mutable: bool, token: str | None = None) ->
|
|||
For mutable refs (branches, HEAD) the remote is fetched on every call.
|
||||
Immutable refs (commit hashes, tags) skip the fetch.
|
||||
|
||||
If *token* is provided it is injected into the HTTPS URL for clone and
|
||||
fetch operations but is never written to the on-disk git config.
|
||||
If *token* is provided it is injected into the URL for clone and fetch
|
||||
operations but is never written to the on-disk git config.
|
||||
|
||||
HTTPS URLs automatically fall back to HTTP when the server does not
|
||||
speak TLS — this handles local Gitea instances running on HTTP.
|
||||
"""
|
||||
cache_dir = _cache_dir_for(remote_url)
|
||||
git_dir = cache_dir / "repo.git"
|
||||
auth = _auth_url(remote_url, token)
|
||||
|
||||
if not git_dir.exists():
|
||||
try:
|
||||
repo = git.Repo.clone_from(auth, str(git_dir), bare=True)
|
||||
except git.exc.GitCommandError as exc:
|
||||
raise ValueError(f"Failed to clone {remote_url!r}: {exc.stderr.strip()}") from exc
|
||||
repo = _clone_bare(auth, git_dir, remote_url)
|
||||
else:
|
||||
repo = git.Repo(str(git_dir))
|
||||
if is_mutable:
|
||||
try:
|
||||
if auth != remote_url:
|
||||
# Fetch from the authenticated URL directly so the token
|
||||
# Fetch directly from the authenticated URL so the token
|
||||
# is never stored in the repo config.
|
||||
repo.git.fetch(auth, "+refs/*:refs/*")
|
||||
fetch_url = auth
|
||||
fallback = _http_fallback(auth)
|
||||
try:
|
||||
repo.git.fetch(fetch_url, "+refs/*:refs/*")
|
||||
except git.exc.GitCommandError:
|
||||
if fallback:
|
||||
try:
|
||||
repo.git.fetch(fallback, "+refs/*:refs/*")
|
||||
except git.exc.GitCommandError:
|
||||
pass
|
||||
else:
|
||||
repo.git.fetch("origin")
|
||||
except git.exc.GitCommandError:
|
||||
|
|
|
|||
|
|
@ -58,14 +58,20 @@ class IfcUrl:
|
|||
raise ValueError(f"Expected ifc:// scheme, got {parsed.scheme!r}")
|
||||
|
||||
# --- Transport and authority ---
|
||||
# parsed.hostname strips the port; we rebuild host as hostname:port
|
||||
# so that git_remote_url() generates correct clone URLs for
|
||||
# non-standard ports (e.g. a local Gitea on :3000 or SSH on :2222).
|
||||
def _host(p) -> str:
|
||||
return f"{p.hostname}:{p.port}" if p.port else (p.hostname or "")
|
||||
|
||||
if parsed.username:
|
||||
transport = "ssh"
|
||||
user: str | None = parsed.username
|
||||
host = parsed.hostname or ""
|
||||
host = _host(parsed)
|
||||
elif parsed.netloc:
|
||||
transport = "https"
|
||||
user = None
|
||||
host = parsed.hostname or ""
|
||||
host = _host(parsed)
|
||||
else:
|
||||
transport = "local"
|
||||
user = None
|
||||
|
|
@ -147,7 +153,14 @@ class IfcUrl:
|
|||
)
|
||||
|
||||
def git_remote_url(self) -> str:
|
||||
"""Return the git remote URL for this IFC URL (SSH or HTTPS form)."""
|
||||
"""Return the git remote URL for this IFC URL.
|
||||
|
||||
The port is preserved when non-standard. SSH uses ssh://, HTTPS
|
||||
uses https://. Whether the server actually speaks HTTPS or plain
|
||||
HTTP is resolved at clone time — see ifcurl.git._open_remote which
|
||||
falls back from https to http when the server refuses the TLS
|
||||
handshake.
|
||||
"""
|
||||
if self.transport == "local":
|
||||
return self.repo_path
|
||||
if self.transport == "ssh":
|
||||
|
|
|
|||
|
|
@ -176,6 +176,21 @@ def test_git_remote_url_local():
|
|||
assert url.git_remote_url() == "/home/alice/project"
|
||||
|
||||
|
||||
def test_git_remote_url_https_nonstandard_port():
|
||||
url = IfcUrl.parse("ifc://localhost:3000/org/repo@heads/main?path=m.ifc")
|
||||
assert url.git_remote_url() == "https://localhost:3000/org/repo"
|
||||
|
||||
|
||||
def test_git_remote_url_ssh_nonstandard_port():
|
||||
url = IfcUrl.parse("ifc://git@localhost:2222/org/repo@heads/main?path=m.ifc")
|
||||
assert url.git_remote_url() == "ssh://git@localhost:2222/org/repo"
|
||||
|
||||
|
||||
def test_host_preserves_port():
|
||||
url = IfcUrl.parse("ifc://example.com:8080/org/repo@HEAD?path=m.ifc")
|
||||
assert url.host == "example.com:8080"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# is_mutable_ref()
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue