Skip to content

How it works

gitsweep does one thing: it computes the set of local branches that are safe to remove, shows them to you, and — once you confirm — deletes them. This page explains how each of those decisions is made.

A local branch is offered for deletion when it is either:

  • merged — it is already merged into the default branch, as reported by git branch --merged <default>, or
  • gone — its upstream tracking branch was deleted on the remote, which git records as the [origin/x: gone] marker in git branch -vv.

Everything gitsweep knows comes from parsing those two git commands. It never guesses: a branch with no upstream and no merge into the default branch is left alone.

A branch can qualify under both rules at once — it was merged and its remote was pruned. In that case merged wins: the branch is reported as merged and removed with the safe delete (see below). This is deliberate, so a branch that can be removed safely never gets force-deleted.

The default branch is what “merged” is measured against, and it’s one of the two branches that is never deleted. gitsweep detects it best-effort, in order:

  1. origin/HEAD — the remote’s declared default (refs/remotes/origin/HEAD), if that symbolic ref is set. This is the most reliable signal.
  2. main, then master — the first of these that exists locally.
  3. main — a final fallback if nothing else resolves.

If detection guesses wrong — for example your repo’s trunk is develop — pass --main <branch> to override it:

Terminal window
gitsweep --main develop

These hold on every run, with or without -y:

  • The current branch is never deleted. The checked-out branch (the * in git branch -vv) is always excluded.
  • The default branch is never deleted. Whether detected or set with --main, it is excluded as a candidate.
  • Detached HEAD is handled. If you’re not on a branch, gitsweep reports detached HEAD and still excludes the default branch.
  • --dry-run changes nothing. It prints the exact list that a real run would act on, then exits. It’s the recommended first run.

How a branch is deleted depends on why it’s stale:

Branch stateDelete commandShown as
Merged into the default branchgit branch -d (safe)✓ name (merged)
Upstream gone, not mergedgit branch -D (force)⚠ name (upstream gone) — force delete

The safe delete (-d) refuses to drop a branch that still has unmerged commits, so merged branches can never take work down with them. A gone branch that was never merged genuinely needs the force delete (-D) — and because that can discard commits, gitsweep labels it force delete in the list so you see it before you confirm.

After computing candidates, gitsweep prints a header with the default and current branch, then groups the candidates under Merged into default branch and Upstream gone, flagging any force deletes:

Terminal window
Default branch: main · current: feature-y
Merged into default branch:
✓ feature-x (merged)
Upstream gone:
⚠ old-spike (upstream gone) — force delete
Delete 2 branch(es)? [y/N]

Only y or yes (case-insensitive) proceeds — anything else aborts with Aborted. No branches were deleted. and nothing is touched. To skip the prompt entirely (for scripts or CI), pass -y / --yes. To preview without ever being asked, use --dry-run.

If there’s nothing to sweep, you’ll simply see:

Terminal window
Nothing to sweep. Your branches are tidy. ✨

The part that decides which branches to remove is a set of pure functions (parseBranchVV, parseMerged, selectCandidates) that operate on raw git output strings and return plain data. The only code that touches git is a thin wrapper over child_process. That separation is what the test suite exercises — the selection logic is tested without ever shelling out to a real repository.