Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,52 @@ pub enum WorkspaceCommands {
/// Copy setup files (e.g. .env) from the main worktree into this workspace
Setup,

/// Clean up workspaces: interactive multi-section picker, or --auto to
/// remove every workspace that passes the safety checks
Clean {
/// Remove all safe workspaces without per-workspace prompts (still asks
/// for one final confirmation). Also enabled by `[workspace.clean] auto`.
#[arg(long)]
auto: bool,

/// Show what would be removed without deleting anything
#[arg(long)]
dry_run: bool,

/// In --auto mode, only consider workspaces older than the configured
/// threshold ([workspace.clean] threshold_days, default 7)
#[arg(long)]
use_threshold: bool,

/// Bypass the dirty/untracked/unpushed safety checks. Still protects the
/// main and current worktrees, locked worktrees, and protected branches.
#[arg(long)]
force: bool,
},

/// Prune stale worktree metadata and delete safe orphan branches
Prune {
/// Show what would be deleted without deleting anything
#[arg(long)]
dry_run: bool,

/// Only prune worktree metadata; do not touch branches
#[arg(long)]
no_branches: bool,
},

/// Protect a branch from cleanup (defaults to the current branch)
Protect {
/// Branch to protect (defaults to the current branch)
branch: Option<String>,
},

/// Remove a branch from the protected list (defaults to the current branch)
Unprotect {
/// Branch to unprotect (defaults to the current branch)
branch: Option<String>,
},

/// Copy files/directories between two workspaces (manual copy tool).
/// Defaults: target = current workspace, source = main worktree,
/// paths = configured setup copy files.
Expand Down Expand Up @@ -450,6 +496,22 @@ impl Commands {
delete_branch,
}) => commands::workspace::run_remove(query, force, delete_branch),
Some(WorkspaceCommands::Setup) => commands::workspace::run_setup(),
Some(WorkspaceCommands::Clean {
auto,
dry_run,
use_threshold,
force,
}) => commands::workspace_clean::run_clean(auto, dry_run, use_threshold, force),
Some(WorkspaceCommands::Prune {
dry_run,
no_branches,
}) => commands::workspace_clean::run_prune(dry_run, no_branches),
Some(WorkspaceCommands::Protect { branch }) => {
commands::workspace_clean::run_protect(branch)
}
Some(WorkspaceCommands::Unprotect { branch }) => {
commands::workspace_clean::run_unprotect(branch)
}
Some(WorkspaceCommands::Sync {
target,
paths,
Expand Down
1 change: 1 addition & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ pub mod setup;
pub mod stash;
pub mod status;
pub mod workspace;
pub mod workspace_clean;
10 changes: 8 additions & 2 deletions src/commands/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1441,9 +1441,15 @@ fn confirm_force_remove(worktree: &Worktree) -> Result<bool> {
Ok(confirmed)
}

fn delete_local_branch(main_root: &Path, branch: &str) -> Result<()> {
pub(crate) fn delete_local_branch(main_root: &Path, branch: &str) -> Result<()> {
match git::worktree::delete_branch(main_root, branch, false) {
Ok(()) => Ok(()),
// The branch is already gone (e.g. removed earlier in the same cleanup
// run): treat that as success rather than aborting the whole operation.
Err(GitError::CommandFailed(msg)) if msg.contains("not found") => {
eprintln!("Branch '{}' was already deleted", branch);
Ok(())
}
Err(GitError::CommandFailed(msg)) if msg.contains("not fully merged") => {
let confirmed = ui::confirm::run_on_stderr(&format!(
"Branch '{}' is not fully merged. Force delete it?",
Expand Down Expand Up @@ -1535,7 +1541,7 @@ pub(crate) fn print_go_path(path: &Path) {
}
}

fn main_worktree_root(worktrees: &[Worktree]) -> Result<PathBuf, WorkspaceError> {
pub(crate) fn main_worktree_root(worktrees: &[Worktree]) -> Result<PathBuf, WorkspaceError> {
worktrees
.iter()
.find(|w| w.is_main)
Expand Down
Loading
Loading