feat(prune): `--apply` — safe, worktree-aware branch/worktree removal
还没有人认领这个 Issue。
评估
- 难度
- 5/5
- 预计耗时
- 一周以上
- 新手友好度
- 35/100
- Issue 类型
- 功能
- 描述清晰度
- 描述清楚
- 活跃度
- 冷清
- 技术栈
- git, github, go
调研方向
从你选择的、按依赖关系排序的任务开始,先处理 report/guard.go 和 report/guard_test.go 中的纯 Guard 类型和表格测试。阅读所引用的 package 文件,并首先运行任务中列出的 Go 测试命令。完成意味着在不更改只读默认设置的情况下通过任务的验收标准;完整功能还需要集成 cmd/prune.go、端到端测试以及 README/help 更新。
由索引模型根据 Issue 内容生成。
描述
Summary
skiffs prune today classifies local branches read-only. This adds an opt-in executor,
skiffs prune --apply, that removes integrated/merged branches and the worktrees holding
them — behind a durability guard that never destroys unique work. Read-only stays the default
(a plan / apply split).
Design
Durability guard — block removal if any of:
- Working tree not clean — untracked / modified / staged files.
- Un-PR'd commit — a commit in
default..branchthat is not an ancestor of the default
branch, not patch-equivalent to one already in default (rebase-&-merge, detected via
git cherry), and not covered by a MERGED PR (squash-&-merge). A CLOSED-unmerged
PR does not make a branch safe. - Precious ignored content — gitignored-but-present files that a plain
git worktree removewould silently destroy. Opted in via--anti-gitignore <path>(file of verbatim
.gitignorekeys) or--no-ignore(blanket: every ignored+present file is precious).
Removal routing (per --git-common-dir):
- default → native
git worktree remove+git branch -D - worktrunk-managed repo (
.config/wt.tomlpresent or anyworktrunk.*key in
.git/config) → delegate towt remove(preserves itspre-removehooks) - worktrunk-managed but
wtnot onPATH→ skip + report, never native-fallback
(fail-closed)
Interaction:
- read-only by default (bare
skiffs prune= the report / "plan") --apply→ interactive[y/N/a/q](Enter=skip,a=all,q=quit);-y/--yes= all--applyrefuses when the scan spans more than one--git-common-dir(single-repo
safety boundary; the primary + all its worktrees still count as one repo)- the per-branch verdict is additive to the existing
PruneResultJSON —
safeToDelete/needsReviewunchanged
Tasks
Dependency-ordered. T1 unblocks the parallel block T2–T5. Each task ≤ ~5 files, test-first.
-
T1 — Guard verdict types + pure
Guard()- Acceptance:
report/guard.godefinesVerdict{Branch, Blocks []BlockReason, Route},
BlockReason(DirtyWorktree|UnPushedCommits|PreciousIgnored),Route
(NativeGit|WorktrunkDelegate|SkipWtUnavailable),BranchState,GuardConfig.
Guard(BranchState, GuardConfig) Verdict;Removable == len(Blocks)==0. Additive —
PruneResultslices untouched. - Verify:
go test ./report/— table tests: each block reason alone, combined, clean case. - Files:
report/guard.go,report/guard_test.go.
- Acceptance:
-
T2 —
gh: MERGED-only signal- Acceptance:
internal/ghaddsMergedPRBranches(ctx, dir, limit) (map[string]bool, error)
filteringstate == MERGEDonly. ExistingMergedClosedPRBranchesunchanged. - Verify:
go test ./internal/gh/— fixture asserts CLOSED excluded, MERGED included. - Files:
internal/gh/gh.go,internal/gh/gh_test.go.
- Acceptance:
-
T3 —
git: durability collectors- Acceptance:
WorktreeDirty(dir) (bool, FileStatus)(reuseGetState);
UnDurableCommits(ctx, dir, defaultRef, branch string, mergedPR bool) ([]string, error)
combiningmerge-base --is-ancestor,git cherry defaultRef branch(+= candidate,
-= upstream), and the MERGED-PR cover; ignores empty commits and in-range merges. - Verify:
go test ./internal/git/—t.TempDir()repos for merge-commit, rebase-&-merge,
squash (+MERGED), and CLOSED-unmerged; assert removable vs held per the matrix. - Files:
internal/git/prune.go,internal/git/prune_test.go.
- Acceptance:
-
T4 —
internal/ignore:.anti-gitignore- Acceptance: new package.
Parse(path) ([]string, error)(non-empty, non-#verbatim
keys);PreciousPresent(ctx, dir string, keys []string, blanket bool) ([]string, error)=
ignored+present files (git ls-files --others --ignored --exclude-standard) matched by a
listed key (verbatim, viadoublestar), or all whenblanket. Reports per-key match count. - Verify:
go test ./internal/ignore/— temp repo with a gitignored dir + noise; scoped key
matches only its dir, blanket matches all, a typo'd key → 0 (surfaced, not silent). - Files:
internal/ignore/ignore.go,internal/ignore/ignore_test.go.
- Acceptance: new package.
-
T5 —
internal/worktrunk: detect + route- Acceptance: new package.
IsManaged(ctx, dir) bool(.config/wt.tomlpresent or
git config --get-regexp '^worktrunk\.'non-empty).Available() bool
(exec.LookPath("wt")).Route(managed, available bool) report.Route:
managed+avail→WorktrunkDelegate; managed+!avail→SkipWtUnavailable; else→NativeGit. - Verify:
go test ./internal/worktrunk/— temp repos with/without each signal; assert
fail-closed route whenwtunavailable. - Files:
internal/worktrunk/worktrunk.go,internal/worktrunk/worktrunk_test.go.
- Acceptance: new package.
-
T6 — Executor interface (real + fake)
- Acceptance:
Executorinterface{ RemoveWorktree(dir) error; DeleteBranch(name) error; WtRemove(branch) error }. Real impl shellsgit worktree remove/git branch -D/
wt remove --foreground.fakeExecutorrecords calls without running them. - Verify:
go test ./internal/git/— integration (temp repo) for realRemoveWorktree+
DeleteBranch; fake used by later routing tests. - Files:
internal/git/executor.go,internal/git/executor_test.go.
- Acceptance:
-
T7 —
cmd/prune: per-branch Verdict in the read-only report- Acceptance:
runPrunecollects state (T2–T5), builds aBranchStateper candidate, runs
Guard, renders held reasons in the table + addsverdictto JSON additively. No
--applyyet → still fully read-only. - Verify:
go test ./cmd/— safety-regression: barepruneperforms zero mutations
(refs+worktrees unchanged); JSON still containssafeToDelete/needsReviewplusverdict. - Files:
cmd/prune.go,cmd/prune_test.go.
- Acceptance:
-
T8 —
cmd/prune:--apply(single-repo gate +[y/N/a/q]+ dispatch)- Acceptance: flags
--apply(bool),--no-ignore(bool),--anti-gitignore <path>,
-y/--yes. Single-repo gate: if--applyand discovered branches span >1
--git-common-dir, error before any prompt. Interactive loop matches[y/N/a/q]
(Enter=skip,y,a=all,q=quit);-y= all. OnlyRemovableverdicts offered; held
listed with reason. Each confirmed removal dispatched viaRoute→Executor. - Verify:
go test ./cmd/— (a) multi-common-dir--applyrefused; (b) interaction parity
with scripted stdin; (c)SkipWtUnavailablenever calls native executor (fake asserts);
(d) temp-repo end-to-end: one loose branch + one worktree removed. - Files:
cmd/prune.go,cmd/prune_test.go.
- Acceptance: flags
-
T9 — Docs + help text
- Acceptance: README documents the plan/apply model, the durability guard, and all flags;
cobra long-help forpruneupdated.gofmt -l .clean,go vet ./...clean. - Verify:
go run . prune --helpshows the new flags;go test ./...green. - Files:
README.md,cmd/prune.go.
- Acceptance: README documents the plan/apply model, the durability guard, and all flags;
Parallelization
T1 ─┬─ T2 ─┐
├─ T3 ─┼─ T6 ─┐
├─ T4 ─┤ ├─ T8 ─ T9
└─ T5 ─┘ T7 ─┘
(T7 needs T1–T5; T8 needs T6+T7)
Out of scope
- Touching
NeedsReview,CLOSED-unmerged branches, or the primary/current worktree. - Glob re-interpretation in
.anti-gitignore— keys match verbatim against.gitignore. - Making the bare
skiffs prunecommand destructive (read-only stays the default). - Non-GitHub remotes: branches whose squash-safety can't be proven via a MERGED PR are held,
never auto-removed.
- 主要语言
- Go
- 星标
- 5
- 派生
- 0
- PR 合并指标
- 30 天内没有已合并 PR
环境准备
这个项目没有提供开发容器、Dockerfile 或贡献指南,环境需要你自己搭建:先看它的 README,通用步骤见我们的新手贡献指南。
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
相似的 Issue
-
Mend: dependency security vulnerability
难度 2/5 1-3 小时 新手友好度 72/100
kube-tarian/sigrun#202 ·
-
question raised-by:verifier
难度 2/5 1-3 小时 新手友好度 72/100
medici-finance/assay#4013 ·
维护者通常 1 天内回复
-
bug
难度 2/5 1-3 小时 新手友好度 83/100
tsukumogami/tsuku#2775 ·
维护者通常 1 天内回复
-
automation models
难度 2/5 1-3 小时 新手友好度 72/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 69/100
维护者通常 1 天内回复