logbook: statistics run on a clean detached snapshot of main ([logbook] snapshot), not the integrator's live checkout, which is often mid-batch; a failed run retries on the next tick instead of waiting for main to move

This commit is contained in:
KeelrLeek committed 2026-10-07 10:35:55 -05:00
1 parent d7464aaa40
commit a3fc48802c
1 file changed
+19 -2
+19 -2
View File
@@ -109,6 +109,21 @@ def rebuild_stats(project, repo, stats_repo, head):
return {"classified": stats.get("classified_percent"), "weighted": stats.get("weighted_percent")} return {"classified": stats.get("classified_percent"), "weighted": stats.get("weighted_percent")}
def snapshot_at(config, repo, head):
"""A clean checkout of `head` for the statistics: [logbook] snapshot, a detached worktree of the decomp repository
that only this refresher uses. The live checkout is the integrator's and is often mid-batch with uncommitted
files, which the statistics tools rightly refuse. Without a snapshot setting, the live checkout is used."""
snapshot = config.get("snapshot")
if not snapshot:
return repo
path = Path(snapshot)
if not (path / ".git").exists():
_git(repo, "worktree", "add", "--detach", str(path), head)
else:
_git(str(path), "checkout", "--detach", "--force", head)
return str(path)
def refresh(project, force=False): def refresh(project, force=False):
"""Bring the Logbook up to main. Returns what changed.""" """Bring the Logbook up to main. Returns what changed."""
config = getattr(project, "logbook", None) or {} config = getattr(project, "logbook", None) or {}
@@ -127,10 +142,12 @@ def refresh(project, force=False):
out["log_error"] = str(error) out["log_error"] = str(error)
if stats_repo: if stats_repo:
try: try:
out["stats"] = rebuild_stats(project, repo, stats_repo, head) out["stats"] = rebuild_stats(project, snapshot_at(config, repo, head), stats_repo, head)
except (RuntimeError, OSError, ValueError, subprocess.TimeoutExpired) as error: except (RuntimeError, OSError, ValueError, subprocess.TimeoutExpired) as error:
out["stats_error"] = str(error) out["stats_error"] = str(error)
_save_state(project, {**state, "last_head": head, "last_refresh": logbook.now(), # a failed statistics run is retried next time, instead of waiting for main to move again
done_head = state.get("last_head") if out.get("stats_error") else head
_save_state(project, {**state, "last_head": done_head, "last_refresh": logbook.now(),
**({"last_error": out.get("stats_error") or out.get("log_error")} **({"last_error": out.get("stats_error") or out.get("log_error")}
if out.get("stats_error") or out.get("log_error") else {"last_error": None})}) if out.get("stats_error") or out.get("log_error") else {"last_error": None})})
return out return out