feat(workspace): add cleanup lifecycle (clean, prune, protect, unprotect) - #24
Merged
Conversation
Extend WorkspaceConfig with a [workspace.clean] table (threshold_days, auto) and a [workspace] protected_branches list. Add config::store so the protect/unprotect commands can persist the protected-branch list back to the global gx config. All fields default via serde so existing config files keep loading unchanged.
Add the git helpers the cleanup lifecycle needs: - list_local_branches and branches_without_worktree for orphan detection - has_upstream/unpushed_count/has_unpushed for safety checks - orphan_branches annotating each branch with upstream/unpushed state - remote_gone_branches (parses for-each-ref upstream:track == [gone]) - workspace_age_days using the smaller of branch-tip age and the .git pointer mtime, so a fresh workspace off an old branch reads as fresh - prune_metadata wrapping git worktree prune from the main root Pure helpers (age_days_from, branches_without_worktree, parse_gone_branches) are extracted and unit-tested.
Wire the cleanup-lifecycle CLI surface under 'gx workspace': - clean [--auto --dry-run --use-threshold --force] - prune [--dry-run --no-branches] - protect [branch] - unprotect [branch] Each subcommand dispatches to the new workspace_clean handler.
Add the interactive cleaner TUI with three sections: workspaces, local branches without workspaces, and orphan branches whose upstream is gone. Protected, main, current, and locked rows are shown but disabled with a tag explaining why, so a skipped row never looks like a scanner miss. Reuses the background summary lookup and stderr terminal setup. Row building, sectioning, safety tagging, and selection-to-action resolution are pure and unit-tested.
Add the workspace_clean command module: - clean: interactive multi-section picker, or --auto to remove every workspace passing the safety checks (final confirmation still required). --use-threshold limits --auto to workspaces older than threshold_days; --force bypasses dirty/untracked/unpushed but never the main, current, locked, or protected guards. - prune: git worktree prune, then delete safe orphan branches after confirmation; --no-branches stops after metadata cleanup; --dry-run reports without deleting. - protect/unprotect: manage the configured list, defaulting to the current branch and erroring on detached HEAD; advise when a branch stays implicitly protected. Reuse ui::confirm and keep all human output on stderr; expose main_worktree_root, delete_local_branch, and print_go_path from the existing workspace module. delete_local_branch now treats an already-deleted branch as success during multi-step cleanup. Safety and prune-eligibility logic is pure and unit-tested.
…lifecycle # Conflicts: # src/args.rs # src/git/worktree.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds the Workspace Cleanup Lifecycle so worktree-heavy development stays maintainable over time. Workspaces accumulate quickly across reviews, fixes, experiments, and agent tasks, and removing them by hand requires inspecting each one. This lets
gxanswer which workspaces are old, which are unsafe (local changes), which branches no longer have a worktree, which must never be removed, and shows exactly what will be deleted before anything destructive happens.New
gx workspacesubcommands:clean— interactive multi-section picker (workspaces, local branches without workspaces, orphan branches whose remote tracking branch is gone). Protected, main, current, and locked rows are shown but disabled with a tag explaining why, so a skipped row never looks like a scanner miss.clean --auto— removes every workspace that passes the safety checks, with a single final confirmation.--use-thresholdlimits it to workspaces older thanthreshold_days;--dry-runreports without deleting;--forcebypasses the dirty/untracked/unpushed checks but never the main, current, locked, or protected guards.prune [--dry-run --no-branches]— runsgit worktree prune, then deletes safe orphan branches after confirmation.protect [branch]/unprotect [branch]— manage the configured protected-branch list, defaulting to the current branch and erroring on detached HEAD.Safety model
A workspace is safe to remove only when it is not the main worktree, not the current worktree, not locked, not on a protected branch, and has no uncommitted, untracked, or unpushed work. Branches that are always protected regardless of config: the configured default branch,
main,master, the current branch, and any branch checked out in an active worktree. Orphan branches with no upstream are treated as unsafe because their commits exist only locally.Age uses a conservative calculation: the smaller of days since the branch's last commit and days since the workspace
.gitpointer was last modified, so a fresh workspace created off an old branch is treated as fresh.Config
All fields default via serde, so existing config files keep loading unchanged.
Implementation
src/args.rs— new subcommands and flags.src/git/worktree.rs— branch listing, unpushed/upstream checks, conservative age calculation, orphan and gone-branch detection, metadata prune.src/commands/workspace_clean.rs— command handlers; pure safety and prune-eligibility logic.src/ui/clean_picker.rs— multi-section picker reusing the background summary lookup and stderr terminal setup.src/config.rs—[workspace.clean]table,protected_branches, andconfig::store.Reuses
ui::confirm, the existing summary lookup, terminal helpers, and the config loader. All human-readable output goes to stderr; stdout stays reserved for shell navigation (the clean handler emits a navigation target only when the current workspace is removed).Testing
cargo buildandcargo testboth pass (162 tests). New unit tests cover: main worktree never removed (even with--force), current worktree never removed, dirty/untracked/unpushed skipped without--force, locked skipped, protected branch skipped, orphan branch deletion gated on safety + confirmation, orphan with no upstream treated as unsafe, fresh workspace off an old branch not stale, gone-branch parsing, and the picker's row/section/selection logic.