Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

feat(prune): `--apply` — safe, worktree-aware branch/worktree removal

未关闭
#3 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 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:

  1. Working tree not clean — untracked / modified / staged files.
  2. Un-PR'd commit — a commit in default..branch that 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.
  3. Precious ignored content — gitignored-but-present files that a plain git worktree remove would silently destroy. Opted in via --anti-gitignore <path> (file of verbatim
    .gitignore keys) 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.toml present or any worktrunk.* key in
    .git/config) → delegate to wt remove (preserves its pre-remove hooks)
  • worktrunk-managed but wt not on PATH → 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
  • --apply refuses 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 PruneResult JSON —
    safeToDelete/needsReview unchanged

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.go defines Verdict{Branch, Blocks []BlockReason, Route},
      BlockReason (DirtyWorktree|UnPushedCommits|PreciousIgnored), Route
      (NativeGit|WorktrunkDelegate|SkipWtUnavailable), BranchState, GuardConfig.
      Guard(BranchState, GuardConfig) Verdict; Removable == len(Blocks)==0. Additive —
      PruneResult slices untouched.
    • Verify: go test ./report/ — table tests: each block reason alone, combined, clean case.
    • Files: report/guard.go, report/guard_test.go.
  • T2 — gh: MERGED-only signal

    • Acceptance: internal/gh adds MergedPRBranches(ctx, dir, limit) (map[string]bool, error)
      filtering state == MERGED only. Existing MergedClosedPRBranches unchanged.
    • Verify: go test ./internal/gh/ — fixture asserts CLOSED excluded, MERGED included.
    • Files: internal/gh/gh.go, internal/gh/gh_test.go.
  • T3 — git: durability collectors

    • Acceptance: WorktreeDirty(dir) (bool, FileStatus) (reuse GetState);
      UnDurableCommits(ctx, dir, defaultRef, branch string, mergedPR bool) ([]string, error)
      combining merge-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.
  • 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, via doublestar), or all when blanket. 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.
  • T5 — internal/worktrunk: detect + route

    • Acceptance: new package. IsManaged(ctx, dir) bool (.config/wt.toml present 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 when wt unavailable.
    • Files: internal/worktrunk/worktrunk.go, internal/worktrunk/worktrunk_test.go.
  • T6 — Executor interface (real + fake)

    • Acceptance: Executor interface { RemoveWorktree(dir) error; DeleteBranch(name) error; WtRemove(branch) error }. Real impl shells git worktree remove / git branch -D /
      wt remove --foreground. fakeExecutor records calls without running them.
    • Verify: go test ./internal/git/ — integration (temp repo) for real RemoveWorktree +
      DeleteBranch; fake used by later routing tests.
    • Files: internal/git/executor.go, internal/git/executor_test.go.
  • T7 — cmd/prune: per-branch Verdict in the read-only report

    • Acceptance: runPrune collects state (T2–T5), builds a BranchState per candidate, runs
      Guard, renders held reasons in the table + adds verdict to JSON additively. No
      --apply yet → still fully read-only.
    • Verify: go test ./cmd/ — safety-regression: bare prune performs zero mutations
      (refs+worktrees unchanged); JSON still contains safeToDelete/needsReview plus verdict.
    • Files: cmd/prune.go, cmd/prune_test.go.
  • 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 --apply and 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. Only Removable verdicts offered; held
      listed with reason. Each confirmed removal dispatched via Route→Executor.
    • Verify: go test ./cmd/ — (a) multi-common-dir --apply refused; (b) interaction parity
      with scripted stdin; (c) SkipWtUnavailable never 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.
  • T9 — Docs + help text

    • Acceptance: README documents the plan/apply model, the durability guard, and all flags;
      cobra long-help for prune updated. gofmt -l . clean, go vet ./... clean.
    • Verify: go run . prune --help shows the new flags; go test ./... green.
    • Files: README.md, cmd/prune.go.

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 prune command 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,通用步骤见我们的新手贡献指南。

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

相似的 Issue

更多 Go Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。