Interactive multi-repo git cleaner. Scans a directory of git repos, shows
which ones drifted off master/main or have uncommitted changes, then lets
you pick the clean ones to reset back to the default branch — fetch, checkout,
fast-forward — all in parallel, with live per-repo progress.
brew install yanptrv/tap/gcleanor with Go:
go install github.com/yanptrv/gclean@latestor grab a binary from releases.
gclean # scan repos in the current directory
gclean ~/work # scan a specific directory
gclean -y # non-interactive: update every clean repoRun it inside a git repo and it skips the picker entirely — if the repo is clean it resets it to the default branch immediately; if it's dirty it tells you and exits.
| on master/main | off master/main | |
|---|---|---|
| clean | selectable (gets a pull) | listed + selectable |
| dirty | listed as FYI | listed, never touched |
Dirty repos are never modified. Updates use merge --ff-only, so a diverged
local default branch fails loudly instead of silently merging.
Repos whose default branch is behind the last-fetched origin ref show a
↓N marker in the listing and picker. Note this reflects your last
fetch/pull/push of that branch, not the remote's live state — a repo
without the marker may still pull new commits.
gclean shells out to your real git, so it works with whatever auth your
repos already use — SSH keys/agent or HTTPS credential helpers. It never
prompts: interactive prompts can't work with 8 parallel fetches, so they're
disabled and surface as ✗ failed instead. New SSH host keys are
auto-accepted (accept-new); changed host keys still fail hard. If a repo
fails with a permission error, set up ssh-add --apple-use-keychain (SSH) or
gh auth setup-git (HTTPS) once and rerun.
| key | action |
|---|---|
↑/↓, k/j |
move cursor (wraps around) |
1–99 |
toggle row by number — multi-digit numbers buffer for 200ms |
space |
toggle row under cursor |
a |
toggle all |
i |
invert selection |
esc |
clear selection |
enter |
confirm and update |
q / ctrl+c |
quit without touching anything |
| code | meaning |
|---|---|
| 0 | everything updated, or nothing to do |
| 1 | a repo failed to update, or the single target repo is dirty |
- repos are scanned concurrently (
git statusis the slow part; scans run on all cores) and sorted by most recent commit - the default branch is resolved from
origin/HEAD, falling back tomaster/main - updates run up to 8 repos in parallel:
fetch origin→checkout <default>→merge --ff-only origin/<default>, each with a live status row - built with bubbletea
make build # build ./gclean
make test # run tests
make lint # go vet
make install # install to ~/.local/binReleases are automated with goreleaser: push a
v* tag and CI builds darwin/linux binaries for amd64/arm64, creates the
GitHub release, and updates the Homebrew formula in
yanptrv/homebrew-tap.
One-time setup: create the homebrew-tap repo and add a HOMEBREW_TAP_TOKEN
repo secret (a fine-grained PAT with write access to the tap repo).
