///|
let cleanup_dir_option : @admiral.PositionDef[String] = @admiral.position_string(
"dir",
description="Base directory",
required=false,
)
///|
let cleanup_dry_run_option : @admiral.OptionDef[Bool] = @admiral.bool(
"dry-run",
short='n',
description="Print removals without changing files",
)
///|
fn cleanup_command_def(
run : async (@admiral.Context) -> Unit,
) -> @admiral.CommandDef {
@admiral.CommandDef::CommandDef(
name="cleanup",
description="Remove worktrees with merged or no PR",
positionals=[cleanup_dir_option],
options=[cleanup_dry_run_option],
run=Some(run),
)
}
///|
struct CleanupOptions {
dir : String
dry_run : Bool
}
///|
fn cleanup_options(context : @admiral.Context) -> CleanupOptions raise {
let dir = match context.get_string(cleanup_dir_option) {
Some(value) => value
None => "."
}
{ dir, dry_run: context.get_bool(cleanup_dry_run_option) }
}
///|
fn removal_reason(pr : PrInfo, git : String) -> String? {
if git != "pushed" {
return None
}
if pr.state == "merged" {
return Some("merged (pushed)")
}
if pr.state == "closed" {
return Some("closed (pushed)")
}
if pr.state == "none" {
return Some("no PR (pushed)")
}
None
}
///|
async fn run_cleanup(context : @admiral.Context) -> Unit {
let options = cleanup_options(context)
let dir = options.dir
let dry_run = options.dry_run
let base = if dir == "." { @env.current_dir().unwrap_or(".") } else { dir }
let (removed, skipped) = match fetch_repo_worktrees(base) {
Some(result) => {
let status_array = collect_worktree_statuses(
result.entries.filter(entry => !entry.is_main),
entry => fetch_worktree_status(result.slug, entry),
)
let count_array = @async.all(
status_array.map(status => {
() => {
match removal_reason(status.pr, status.git) {
Some(reason) => {
let prefix = if dry_run {
"[dry-run] Would remove"
} else {
"Removing"
}
println(
"\{prefix}: \{status.entry.path} (\{status.branch}, \{reason})",
)
if !dry_run {
run_checked("git", [
"-C",
result.repo_path,
"worktree",
"remove",
status.entry.path,
])
run_checked("git", [
"-C",
result.repo_path,
"branch",
"-d",
status.branch,
])
}
(1, 0)
}
None => (0, 1)
}
}
}),
max_concurrent=1,
)
if !dry_run {
run_checked("git", ["-C", result.repo_path, "worktree", "prune"])
}
count_array
.iter()
.fold(init=(0, 0), fn(total, count) {
(total.0 + count.0, total.1 + count.1)
})
}
None => {
println("No git repositories found.")
return
}
}
let suffix = if dry_run { " (dry-run)" } else { "" }
println("Done: \{removed} removed, \{skipped} skipped\{suffix}")
}