97 lines
3.3 KiB
Python
97 lines
3.3 KiB
Python
import json
|
|
import os
|
|
import re
|
|
import subprocess
|
|
from pathlib import Path
|
|
from typing import Optional, Tuple
|
|
from urllib.parse import ParseResult
|
|
|
|
from .error import NurError
|
|
from .manifest import LockedVersion, Repo, RepoType
|
|
|
|
Url = ParseResult
|
|
|
|
|
|
def nix_prefetch_zip(url: str) -> Tuple[str, Path]:
|
|
data = subprocess.check_output(
|
|
["nix-prefetch-url", "--name", "source", "--unpack", "--print-path", url]
|
|
)
|
|
sha256, path = data.decode().strip().split("\n")
|
|
return sha256, Path(path)
|
|
|
|
|
|
class GitPrefetcher:
|
|
def __init__(self, repo: Repo) -> None:
|
|
self.repo = repo
|
|
|
|
def latest_commit(self) -> str:
|
|
data = subprocess.check_output(
|
|
["git", "ls-remote", self.repo.url.geturl(), self.repo.branch or "HEAD"],
|
|
env={**os.environ, "GIT_ASKPASS": "", "GIT_TERMINAL_PROMPT": "0"},
|
|
)
|
|
return data.decode().split(maxsplit=1)[0]
|
|
|
|
def prefetch(self, ref: str) -> Tuple[str, Path]:
|
|
cmd = ["nix-prefetch-git"]
|
|
if self.repo.submodules:
|
|
cmd += ["--fetch-submodules"]
|
|
if self.repo.branch:
|
|
cmd += ["--rev", f"refs/heads/{self.repo.branch}"]
|
|
cmd += [self.repo.url.geturl()]
|
|
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
try:
|
|
stdout, stderr = proc.communicate(timeout=30)
|
|
except subprocess.TimeoutExpired:
|
|
proc.kill()
|
|
raise NurError(
|
|
f"Timeout expired while prefetching git repository {self. repo.url.geturl()}"
|
|
)
|
|
|
|
if proc.returncode != 0:
|
|
raise NurError(
|
|
f"Failed to prefetch git repository {self.repo.url.geturl()}: {stderr.decode('utf-8')}"
|
|
)
|
|
|
|
metadata = json.loads(stdout)
|
|
lines = stderr.decode("utf-8").split("\n")
|
|
repo_path = re.search("path is (.+)", lines[-5])
|
|
assert repo_path is not None
|
|
path = Path(repo_path.group(1))
|
|
sha256 = metadata["sha256"]
|
|
return sha256, path
|
|
|
|
|
|
class GithubPrefetcher(GitPrefetcher):
|
|
def prefetch(self, ref: str) -> Tuple[str, Path]:
|
|
return nix_prefetch_zip(f"{self.repo.url.geturl()}/archive/{ref}.tar.gz")
|
|
|
|
|
|
class GitlabPrefetcher(GitPrefetcher):
|
|
def prefetch(self, ref: str) -> Tuple[str, Path]:
|
|
hostname = self.repo.url.hostname
|
|
assert (
|
|
hostname is not None
|
|
), f"Expect a hostname for Gitlab repo: {self.repo.name}"
|
|
path = Path(self.repo.url.path)
|
|
escaped_path = "%2F".join(path.parts[1:])
|
|
url = f"https://{hostname}/api/v4/projects/{escaped_path}/repository/archive.tar.gz?sha={ref}"
|
|
return nix_prefetch_zip(url)
|
|
|
|
|
|
def prefetch(repo: Repo) -> Tuple[Repo, LockedVersion, Optional[Path]]:
|
|
prefetcher: GitPrefetcher
|
|
if repo.type == RepoType.GITHUB:
|
|
prefetcher = GithubPrefetcher(repo)
|
|
elif repo.type == RepoType.GITLAB:
|
|
prefetcher = GitlabPrefetcher(repo)
|
|
else:
|
|
prefetcher = GitPrefetcher(repo)
|
|
|
|
commit = prefetcher.latest_commit()
|
|
locked_version = repo.locked_version
|
|
if locked_version is not None:
|
|
if locked_version.rev == commit:
|
|
return repo, locked_version, None
|
|
|
|
sha256, path = prefetcher.prefetch(commit)
|
|
return repo, LockedVersion(repo.url, commit, sha256, repo.submodules), path
|