mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
feat: git hygiene (branch/preview reaping + cleanup sweep) and panel charts; work sessions under Git (#548)
* feat(panel): session-start, 7d overview spend, and 30d business spend charts * feat(panel): surface work sessions as a Git page tab (route was orphaned) * feat(git): reap spent task branches and render previews at lifecycle chokepoints; guarded stale-branch sweep * feat(panel): stale-branch cleanup button on the Git page * fix(git,panel): cursor-resumable sweep, force-delete spent refs, local filter state * docs(map,rag): branch/preview reaping, cleanup sweep, git-tab work sessions, wave-2 charts --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
@@ -485,3 +485,89 @@ async def test_self_heal_recovers_clone_root_left_on_task_branch(clone: Path) ->
|
||||
assert (wt / ".git").is_file()
|
||||
assert _git(wt, "rev-parse", "--abbrev-ref", "HEAD").strip() == branch
|
||||
assert _git(clone, "rev-parse", "--abbrev-ref", "HEAD").strip() == "main"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# delete_local_branch — cleans up the branch ref remove_worktree leaves behind
|
||||
# (worktree removal never deletes refs/heads/{branch}; every claimed task
|
||||
# leaked a permanent local branch ref until this).
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
async def test_delete_local_branch_skips_default_branches(clone: Path) -> None:
|
||||
svc = _service()
|
||||
for branch in ("main", "master", "develop", ""):
|
||||
await svc.delete_local_branch(clone, branch, force=True)
|
||||
# "main" is the clone's actual current branch — still checked out, untouched.
|
||||
assert _git(clone, "rev-parse", "--abbrev-ref", "HEAD").strip() == "main"
|
||||
|
||||
|
||||
async def test_delete_local_branch_removes_merged_branch(clone: Path) -> None:
|
||||
svc = _service()
|
||||
_git(clone, "branch", "feature/merged") # no new commits -> already merged
|
||||
assert _ref_exists(clone, "refs/heads/feature/merged")
|
||||
|
||||
await svc.delete_local_branch(clone, "feature/merged", force=False)
|
||||
|
||||
assert not _ref_exists(clone, "refs/heads/feature/merged")
|
||||
|
||||
|
||||
async def test_delete_local_branch_not_merged_skips_without_force(clone: Path) -> None:
|
||||
svc = _service()
|
||||
_git(clone, "checkout", "-b", "feature/unmerged")
|
||||
(clone / "work.txt").write_text("x")
|
||||
_git(clone, "add", "work.txt")
|
||||
_git(clone, "commit", "-m", "unmerged work")
|
||||
_git(clone, "checkout", "main")
|
||||
assert _ref_exists(clone, "refs/heads/feature/unmerged")
|
||||
|
||||
await svc.delete_local_branch(clone, "feature/unmerged", force=False)
|
||||
|
||||
# `-d` refuses an unmerged branch — a clean skip, not an error, ref stays.
|
||||
assert _ref_exists(clone, "refs/heads/feature/unmerged")
|
||||
|
||||
|
||||
async def test_delete_local_branch_force_deletes_unmerged(clone: Path) -> None:
|
||||
svc = _service()
|
||||
_git(clone, "checkout", "-b", "feature/unmerged")
|
||||
(clone / "work.txt").write_text("x")
|
||||
_git(clone, "add", "work.txt")
|
||||
_git(clone, "commit", "-m", "unmerged work")
|
||||
_git(clone, "checkout", "main")
|
||||
assert _ref_exists(clone, "refs/heads/feature/unmerged")
|
||||
|
||||
await svc.delete_local_branch(clone, "feature/unmerged", force=True)
|
||||
|
||||
assert not _ref_exists(clone, "refs/heads/feature/unmerged")
|
||||
|
||||
|
||||
async def test_delete_local_branch_squash_merged_needs_force(clone: Path) -> None:
|
||||
# RoboCo's default merge method is SQUASH: the branch's commits are never
|
||||
# ancestors of the base afterwards, so `-d` refuses even though the work
|
||||
# fully landed. This is why every terminal-path caller passes force=True —
|
||||
# with -d the completed-task ref would leak forever.
|
||||
svc = _service()
|
||||
_git(clone, "checkout", "-b", "feature/squashed")
|
||||
(clone / "work.txt").write_text("x")
|
||||
_git(clone, "add", "work.txt")
|
||||
_git(clone, "commit", "-m", "feature work")
|
||||
_git(clone, "checkout", "main")
|
||||
_git(clone, "merge", "--squash", "feature/squashed")
|
||||
_git(clone, "commit", "-m", "squash-merge feature")
|
||||
assert (clone / "work.txt").exists() # the work landed on main
|
||||
|
||||
await svc.delete_local_branch(clone, "feature/squashed", force=False)
|
||||
assert _ref_exists(clone, "refs/heads/feature/squashed") # -d refused
|
||||
|
||||
await svc.delete_local_branch(clone, "feature/squashed", force=True)
|
||||
assert not _ref_exists(clone, "refs/heads/feature/squashed")
|
||||
|
||||
|
||||
async def test_delete_local_branch_missing_branch_is_clean_skip(clone: Path) -> None:
|
||||
svc = _service()
|
||||
await svc.delete_local_branch(
|
||||
clone, "feature/never-existed", force=False
|
||||
) # no error
|
||||
await svc.delete_local_branch(
|
||||
clone, "feature/never-existed", force=True
|
||||
) # no error
|
||||
|
||||
Reference in New Issue
Block a user