DEV Community

Cover image for branchreap: safely reap merged & gone-upstream git branches (zero-dep, npx or pip)
benjamin
benjamin

Posted on

branchreap: safely reap merged & gone-upstream git branches (zero-dep, npx or pip)

I ran git branch on a repo I'd been on for a year and counted forty-seven local branches. Maybe six were alive. The rest were feature/* that shipped months ago, fix/* whose PRs merged and whose remotes were deleted the same minute, and a couple of experiments I'd genuinely forgotten writing.

The usual advice is:

git branch --merged | grep -v '\*' | xargs git branch -d
Enter fullscreen mode Exit fullscreen mode

I've pasted that snippet into a dozen shells. It has three problems: it misses branches whose upstream is gone (deleted remote, but the local sticks around forever), it cheerfully includes main if you forget the grep, and it makes you the safety check.

So I built branchreap — it finds the branches that are actually safe to reap, and only those.

npx branchreap          # scan, read-only
# or
pip install branchreap
Enter fullscreen mode Exit fullscreen mode

What it reaps — and what it refuses to

Reaps:

  • merged branches (fully merged into your default branch)
  • gone branches (upstream deleted), as long as they carry no unmerged commits

Never touches:

  • the current branch
  • the default branch (main / master / whatever origin/HEAD says)
  • anything unmerged / unpushed, unless you explicitly pass --include-unmerged (and even then it still won't delete current or default)

That safety floor is the whole design. Run it without reading a word of docs and the worst case is it deletes a branch git itself already considers merged.

What it looks like

$ branchreap

default branch: main

  ✗ feature/old-login          merged         merged into main
  ✗ fix/typo                    gone           upstream gone · merged into main
  ────────────────────────────────────────────────────────
  2 reapable  (1 merged · 1 gone)  ·  4 kept

  1 gone branch(es) held back (unmerged commits). Use --include-unmerged to reap.

$ branchreap clean            # asks first
$ branchreap clean --yes      # for a shell alias
Enter fullscreen mode Exit fullscreen mode

--dry-run previews, --json pipes, --fetch runs a git fetch --prune first so the gone status is fresh (git only marks upstreams gone after a prune).

Why another branch cleaner?

There are a few good ones (git-sweep, git-trim), but most are single-purpose — merged-only or remote-only — and several are unmaintained. branchreap does merged and gone in one pass, leads with safety, and is genuinely zero-dependency: it just shells out to git and parses the output. Same logic ships as a Node package and a Python package (byte-for-byte aligned), so npx branchreap and pipx run branchreap both work with nothing to install.

Try it

npx branchreap          # Node
pipx run branchreap     # Python
Enter fullscreen mode Exit fullscreen mode

How many local branches are in your busiest repo right now? Run git branch | wc -l and tell me — I bet it's higher than you'd guess. 👇

Top comments (0)