182 lines
5.2 KiB
Python
182 lines
5.2 KiB
Python
import logging
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
from argparse import Namespace
|
|
from pathlib import Path
|
|
from tempfile import TemporaryDirectory
|
|
from typing import Dict, List, Optional
|
|
|
|
from .fileutils import chdir, write_json_file
|
|
from .manifest import Repo, load_manifest, update_lock_file
|
|
from .path import LOCK_PATH, MANIFEST_PATH, ROOT
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def load_combined_repos(path: Path) -> Dict[str, Repo]:
|
|
combined_manifest = load_manifest(
|
|
path.joinpath("repos.json"), path.joinpath("repos.json.lock")
|
|
)
|
|
repos = {}
|
|
for repo in combined_manifest.repos:
|
|
repos[repo.name] = repo
|
|
return repos
|
|
|
|
|
|
def repo_source(name: str) -> str:
|
|
cmd = ["nix-build", str(ROOT), "--no-out-link", "-A", f"repo-sources.{name}"]
|
|
out = subprocess.check_output(cmd)
|
|
return out.strip().decode("utf-8")
|
|
|
|
|
|
def repo_changed() -> bool:
|
|
diff_cmd = subprocess.Popen(["git", "diff", "--staged", "--exit-code"])
|
|
return diff_cmd.wait() == 1
|
|
|
|
|
|
def commit_files(files: List[str], message: str) -> None:
|
|
cmd = ["git", "add"]
|
|
cmd.extend(files)
|
|
subprocess.check_call(cmd)
|
|
if repo_changed():
|
|
subprocess.check_call(["git", "commit", "-m", message])
|
|
|
|
|
|
def commit_repo(repo: Repo, message: str, path: Path) -> Repo:
|
|
repo_path = path.joinpath(repo.name).resolve()
|
|
|
|
tmp: Optional[TemporaryDirectory] = TemporaryDirectory(prefix=str(repo_path.parent))
|
|
assert tmp is not None
|
|
|
|
try:
|
|
# dirs_exist_ok=True because our directory definitely already exists
|
|
shutil.copytree(
|
|
repo_source(repo.name), tmp.name, symlinks=True, dirs_exist_ok=True
|
|
)
|
|
if os.path.exists(repo_path):
|
|
shutil.rmtree(repo_path)
|
|
shutil.copytree(tmp.name, repo_path, symlinks=True)
|
|
tmp = None
|
|
finally:
|
|
if tmp is not None:
|
|
tmp.cleanup()
|
|
|
|
with chdir(str(path)):
|
|
commit_files([str(repo_path)], message)
|
|
|
|
return repo
|
|
|
|
|
|
def repo_link(path: Path) -> str:
|
|
commit = subprocess.check_output(["git", "-C", path, "rev-parse", "HEAD"])
|
|
rev = commit.decode("utf-8").strip()[:10]
|
|
return f"https://github.com/nix-community/nur-combined/commit/{rev}"
|
|
|
|
|
|
def update_combined_repo(
|
|
combined_repo: Optional[Repo], repo: Repo, path: Path
|
|
) -> Optional[Repo]:
|
|
if repo.locked_version is None:
|
|
return None
|
|
|
|
new_rev = repo.locked_version.rev
|
|
if combined_repo is None:
|
|
message = f"{repo.name}: init at {new_rev[:10]} ({repo_link(path)})"
|
|
repo = commit_repo(repo, message, path)
|
|
return repo
|
|
|
|
assert combined_repo.locked_version is not None
|
|
old_rev = combined_repo.locked_version.rev
|
|
|
|
if combined_repo.locked_version == repo.locked_version:
|
|
return repo
|
|
|
|
if new_rev != old_rev:
|
|
message = f"{repo.name}: {old_rev[:10]} -> {new_rev[:10]}"
|
|
else:
|
|
message = f"{repo.name}: update"
|
|
|
|
repo = commit_repo(repo, message, path)
|
|
return repo
|
|
|
|
|
|
def remove_repo(repo: Repo, path: Path) -> None:
|
|
repo_path = path.joinpath("repos", repo.name).resolve()
|
|
if repo_path.exists():
|
|
shutil.rmtree(repo_path)
|
|
with chdir(path):
|
|
commit_files([str(repo_path)], f"{repo.name}: remove")
|
|
|
|
|
|
def update_manifest(repos: List[Repo], path: Path) -> None:
|
|
d = {}
|
|
|
|
for repo in repos:
|
|
d[repo.name] = repo.as_json()
|
|
write_json_file(dict(repos=d), path)
|
|
|
|
|
|
def update_combined(path: Path) -> None:
|
|
manifest = load_manifest(MANIFEST_PATH, LOCK_PATH)
|
|
|
|
combined_repos = load_combined_repos(path)
|
|
|
|
repos_path = path.joinpath("repos")
|
|
os.makedirs(repos_path, exist_ok=True)
|
|
|
|
updated_repos = []
|
|
|
|
for repo in manifest.repos:
|
|
combined_repo = None
|
|
if repo.name in combined_repos:
|
|
combined_repo = combined_repos[repo.name]
|
|
del combined_repos[repo.name]
|
|
try:
|
|
new_repo = update_combined_repo(combined_repo, repo, repos_path)
|
|
except Exception:
|
|
logger.exception(f"Failed to updated repository {repo.name}")
|
|
continue
|
|
|
|
if new_repo is not None:
|
|
updated_repos.append(new_repo)
|
|
|
|
for combined_repo in combined_repos.values():
|
|
remove_repo(combined_repo, path)
|
|
|
|
update_manifest(updated_repos, path.joinpath("repos.json"))
|
|
|
|
update_lock_file(updated_repos, path.joinpath("repos.json.lock"))
|
|
|
|
with chdir(path):
|
|
commit_files(["repos.json", "repos.json.lock"], "update repos.json + lock")
|
|
|
|
|
|
def setup_combined() -> None:
|
|
manifest_path = "repos.json"
|
|
|
|
if not Path(".git").exists():
|
|
cmd = ["git", "init", "."]
|
|
subprocess.check_call(cmd)
|
|
|
|
if not os.path.exists(manifest_path):
|
|
write_json_file(dict(repos={}), manifest_path)
|
|
|
|
manifest_lib = "lib"
|
|
if os.path.exists(manifest_lib):
|
|
shutil.rmtree(manifest_lib)
|
|
shutil.copytree(str(ROOT.joinpath("lib")), manifest_lib, symlinks=True)
|
|
default_nix = "default.nix"
|
|
shutil.copy(ROOT.joinpath("default.nix"), default_nix)
|
|
|
|
vcs_files = [manifest_path, manifest_lib, default_nix]
|
|
|
|
commit_files(vcs_files, "update code")
|
|
|
|
|
|
def combine_command(args: Namespace) -> None:
|
|
combined_path = Path(args.directory)
|
|
|
|
with chdir(combined_path):
|
|
setup_combined()
|
|
update_combined(combined_path)
|