feat: authorized cleanup for reviewer scratch worktrees (Closes #534)

Discover branches/review-pr<N>[-submit] worktrees separately from author
cleanup, fail closed on dirty trees / open PRs / active reviewer leases,
and remove only assessed-safe scratch paths via reconcile.
This commit is contained in:
2026-07-08 23:12:57 -04:00
parent 631620d264
commit e4f1a52a87
3 changed files with 473 additions and 11 deletions
+38
View File
@@ -4737,6 +4737,34 @@ def gitea_reconcile_merged_cleanups(
)
delete_capability_allowed = not _profile_operation_gate("gitea.branch.delete")
# #534: discover reviewer scratch trees and active leases for those PRs.
scratch_candidates = merged_cleanup_reconcile.discover_reviewer_scratch_worktrees(
PROJECT_ROOT
)
active_reviewer_leases: dict[int, bool] = {}
pr_states: dict[int, dict] = {}
for scratch in scratch_candidates:
pr_num = int(scratch["pr_number"])
if pr_num in active_reviewer_leases:
continue
try:
comments = api_get_all(f"{base}/issues/{pr_num}/comments", auth)
except Exception:
comments = []
lease = reviewer_pr_lease.find_active_reviewer_lease(
comments, pr_number=pr_num
)
active_reviewer_leases[pr_num] = bool(lease)
try:
pr_live = api_request("GET", f"{base}/pulls/{pr_num}", auth)
except Exception:
pr_live = {}
pr_states[pr_num] = {
"merged": bool((pr_live or {}).get("merged") or (pr_live or {}).get("merged_at")),
"closed": (pr_live or {}).get("state") == "closed",
}
report = merged_cleanup_reconcile.build_reconciliation_report(
project_root=PROJECT_ROOT,
closed_prs=merged_closed,
@@ -4744,6 +4772,8 @@ def gitea_reconcile_merged_cleanups(
remote_branch_exists=remote_branch_exists,
head_on_master=head_on_master,
delete_capability_allowed=delete_capability_allowed,
active_reviewer_leases=active_reviewer_leases,
pr_states=pr_states,
)
if dry_run:
@@ -4787,6 +4817,14 @@ def gitea_reconcile_merged_cleanups(
)
actions.append({"action": "remove_local_worktree", **result})
for scratch in report.get("reviewer_scratch_entries") or []:
if not scratch.get("safe_to_remove_worktree"):
continue
result = merged_cleanup_reconcile.remove_reviewer_scratch_worktree(
PROJECT_ROOT, scratch.get("worktree_path") or ""
)
actions.append({"action": "remove_reviewer_scratch_worktree", **result})
report["dry_run"] = False
report["executed"] = True
report["actions"] = actions