Create Relay Bot MVP
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
import hashlib
|
||||
import logging
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote, urlsplit, urlunsplit
|
||||
|
||||
from app.repository.security import validate_repository_url
|
||||
from app.repository.tools import RepositoryTools
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RepositoryService:
|
||||
def __init__(self, url: str, branch: str, path: Path, token: str | None, max_file_bytes: int):
|
||||
self.url, self.branch, self.path, self.token, self.max_file_bytes = (
|
||||
url,
|
||||
branch,
|
||||
path,
|
||||
token,
|
||||
max_file_bytes,
|
||||
)
|
||||
|
||||
def _authenticated_url(self) -> str:
|
||||
if not self.token:
|
||||
return self.url
|
||||
parsed = urlsplit(self.url)
|
||||
if parsed.scheme != "https":
|
||||
raise ValueError("Private repository URL must use HTTPS")
|
||||
return urlunsplit(
|
||||
(
|
||||
parsed.scheme,
|
||||
f"oauth2:{quote(self.token, safe='')}@{parsed.netloc}",
|
||||
parsed.path,
|
||||
parsed.query,
|
||||
"",
|
||||
)
|
||||
)
|
||||
|
||||
def sync(self) -> str:
|
||||
if not self.url:
|
||||
raise RuntimeError("REPOSITORY_URL is not configured")
|
||||
self.path.parent.mkdir(parents=True, exist_ok=True)
|
||||
if not (self.path / ".git").exists():
|
||||
clone_args = [
|
||||
"git",
|
||||
"clone",
|
||||
"--filter=blob:none",
|
||||
"--no-checkout",
|
||||
"--depth",
|
||||
"50",
|
||||
]
|
||||
if self.branch != "HEAD":
|
||||
clone_args.extend(["--branch", self.branch])
|
||||
clone_args.extend([self._authenticated_url(), str(self.path)])
|
||||
subprocess.run(
|
||||
clone_args,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
else:
|
||||
fetch_args = ["git", "fetch", "origin", "--depth", "50"]
|
||||
if self.branch != "HEAD":
|
||||
fetch_args.append(self.branch)
|
||||
subprocess.run(
|
||||
fetch_args,
|
||||
cwd=self.path,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
target = "origin/HEAD" if self.branch == "HEAD" else f"origin/{self.branch}"
|
||||
subprocess.run(
|
||||
["git", "reset", "--soft", target],
|
||||
cwd=self.path,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
commit = RepositoryTools(self.path, self.max_file_bytes).current_commit()
|
||||
logger.info("repository_synced branch=%s commit=%s", self.branch, commit)
|
||||
return commit
|
||||
|
||||
def tools(self) -> RepositoryTools:
|
||||
if not (self.path / ".git").exists():
|
||||
raise RuntimeError("Repository is not synced")
|
||||
return RepositoryTools(self.path, self.max_file_bytes)
|
||||
|
||||
|
||||
class RepositoryManager:
|
||||
def __init__(self, cache_root: Path, token: str | None, max_file_bytes: int):
|
||||
self.cache_root = cache_root.resolve()
|
||||
self.token, self.max_file_bytes = token, max_file_bytes
|
||||
|
||||
def service_for(
|
||||
self, url: str, branch: str = "HEAD", cache_path: str | None = None
|
||||
) -> RepositoryService:
|
||||
normalized = validate_repository_url(url)
|
||||
cache = (
|
||||
Path(cache_path)
|
||||
if cache_path
|
||||
else self.cache_root / hashlib.sha256(normalized.encode()).hexdigest()
|
||||
)
|
||||
return RepositoryService(normalized, branch, cache, self.token, self.max_file_bytes)
|
||||
Reference in New Issue
Block a user