1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
from __future__ import annotations
import shutil
from contextlib import suppress
from pathlib import Path
import pygit2
from pygitweb.config import settings
PYGITWEB_DIR = ".pygitweb"
WORKTREE_DIR = "wt"
WORKTREE_NAME = "pytesthtml"
REPORT_FILENAME = "pytest_report.html"
def project_root(project: str) -> Path:
return (Path(settings.PROJECTROOT) / project).resolve()
def pygitweb_dir(project: str) -> Path:
return project_root(project) / PYGITWEB_DIR
def report_path(project: str) -> Path:
return pygitweb_dir(project) / REPORT_FILENAME
def _branch_reference(repo: pygit2.Repository) -> pygit2.Reference:
try:
return repo.lookup_reference(repo.head.name)
except pygit2.GitError:
pass
for ref_name in repo.references:
if ref_name.startswith("refs/heads/"):
return repo.lookup_reference(ref_name)
raise RuntimeError("repository has no branches to check out for pytest")
def _prune_worktree(repo: pygit2.Repository, name: str) -> None:
with suppress(pygit2.GitError):
repo.lookup_worktree(name).prune(True)
def _remove_untracked(wt_root: Path, wt_repo: pygit2.Repository) -> None:
for rel_path, status in wt_repo.status().items():
if status != pygit2.GIT_STATUS_WT_NEW:
continue
target = wt_root / rel_path
if target.is_dir():
shutil.rmtree(target)
elif target.exists():
target.unlink()
def _sync_worktree(wt_repo: pygit2.Repository, wt_root: Path, commit: pygit2.Commit) -> None:
wt_repo.reset(commit.id, pygit2.GIT_RESET_HARD)
_remove_untracked(wt_root, wt_repo)
def _ensure_bare_worktree(repo: pygit2.Repository, wt_path: Path) -> None:
ref = _branch_reference(repo)
commit = ref.peel(pygit2.Commit)
wt_path.parent.mkdir(parents=True, exist_ok=True)
path_str = str(wt_path.resolve())
if WORKTREE_NAME in repo.list_worktrees():
registered = Path(repo.lookup_worktree(WORKTREE_NAME).path)
_sync_worktree(pygit2.Repository(str(registered)), registered, commit)
return
if wt_path.exists():
shutil.rmtree(wt_path)
try:
repo.add_worktree(WORKTREE_NAME, path_str, ref)
except pygit2.GitError:
_prune_worktree(repo, WORKTREE_NAME)
shutil.rmtree(wt_path, ignore_errors=True)
repo.add_worktree(WORKTREE_NAME, path_str, ref)
def prepare_pytest_run(project: str) -> tuple[Path, Path]:
"""Return (pytest cwd, html report path). Bare repos use a worktree under .pygitweb/wt."""
repo_path = project_root(project)
data_dir = pygitweb_dir(project)
report = report_path(project)
data_dir.mkdir(parents=True, exist_ok=True)
repo = pygit2.Repository(str(repo_path))
if not repo.is_bare:
return repo_path, report
wt_path = data_dir / WORKTREE_DIR
_ensure_bare_worktree(repo, wt_path)
return wt_path, report
def cleanup_pytest_run(project: str) -> None:
"""Remove the bare-repo worktree after pytest; leaves .pygitweb/pytest_report.html."""
repo_path = project_root(project)
repo = pygit2.Repository(str(repo_path))
if not repo.is_bare:
return
_prune_worktree(repo, WORKTREE_NAME)
shutil.rmtree(pygitweb_dir(project) / WORKTREE_DIR, ignore_errors=True)
shutil.rmtree(repo_path / "worktrees" / WORKTREE_NAME, ignore_errors=True)