nisse-review: read code from a git clone kept between runs
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m3s
CI / e2e (pull_request) Successful in 4m5s

Archives were downloaded and unpacked afresh for every run, which doesn't
scale to very large repositories. Each repository is now cloned once, with
its whole history, under scratch/repos/<owner>/<name>/ (head/ the clone,
base/ a worktree when read). A run fetches every branch and the PR's
refs/pull/N/head, checks out the head exactly, and fails if that head is
gone. Only the latest repository is kept for now.

Git runs walled off: none of the host's git configuration, links checked
out as plain files, no hooks, submodules or LFS, only the Gitea URL's
protocol, and the token as an http.extraHeader for that URL in git's
environment only. A stop ends a git command; a cut-short first clone is
removed. The reader never shows .git. GiteaClient.archive is gone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
This commit is contained in:
dongarandClaude Opus 5.5 committed 2026-10-17 20:37:12 +00:00
1 parent a85e855fc2
commit 3813ad55d6
11 files changed
+642 -139

No files matched your search

+7 -17
View File
@@ -24,12 +24,10 @@ to ready, with nothing drafted posted and no failure counted.
"""
import re
import tempfile
import threading
import time
from collections.abc import Callable
from dataclasses import replace
from pathlib import Path
from nisse_core.gitea import GiteaClient, GiteaError, PullRequest
from nisse_core.models.interruptible import Stopped
@@ -37,7 +35,7 @@ from nisse_core.oplog import OpLog
from nisse_core.state.cancel import Cancel
from nisse_core.state.store import WorkItem
from nisse_review.access import check, reviewed
from nisse_review.reader import Repository
from nisse_review.reader import Repository, Sources
from nisse_review.review import Draft, Posted, Reviewer, Said, answered, said_on
from nisse_review.state import (
CAPPED,
@@ -92,7 +90,7 @@ class Scheduler:
reviewer: Reviewer,
*,
oplog: OpLog,
scratch: Path,
sources: Sources,
poll_s: float = 60.0,
attempts: int = 2,
patterns: frozenset[str] | None = None,
@@ -107,7 +105,7 @@ class Scheduler:
self._gitea = gitea
self._reviewer = reviewer
self._oplog = oplog
self._scratch = scratch
self._sources = sources # the repositories' code, as clones
self._poll_s = poll_s
self._attempts = attempts
# The repositories reviewed, as patterns; None: every one it was given.
@@ -388,19 +386,11 @@ class Scheduler:
if kind == REPLIES and not known.fresh:
self._settle(item, known, pr, "done", Draft(), posted=None)
return
scratch = Path(tempfile.mkdtemp(dir=self._scratch, prefix="review-"))
repo = Repository(
self._gitea,
known.repo,
{"head": pr.head_sha, "base": pr.base},
scratch,
trees = self._sources.open(known.repo, pr.number, pr.head_sha, pr.base)
repo = Repository(self._gitea, known.repo, trees)
outcome, draft = self._reviewer.run(
item.id, pr, repo, kind=kind, known=known, said=said
)
try:
outcome, draft = self._reviewer.run(
item.id, pr, repo, kind=kind, known=known, said=said
)
finally:
repo.close()
if outcome.kind == "capped" and draft.comments:
# Out of turns with points drafted: they're worth more posted
# than lost (#156), as a review that says it's partial.