@@ -13,9 +13,14 @@ whose upstream has been deleted so they can be cleaned up in one step.
1313Requires Go 1.26+ and git on your PATH.
1414
1515``` sh
16- make build # builds to ~/shared/bin/git_pruner (on PATH)
16+ ./install.sh # build + install to a user bin directory
17+ ./install.sh --bindir ~ /bin # ...or pick the directory yourself
1718```
1819
20+ With no flags it installs to the first usable of ` ~/.local/bin ` or ` ~/bin ` , falling back to
21+ ` /usr/local/bin ` (via sudo), and warns if the directory is not on your PATH. See
22+ ` ./install.sh --help ` for the details.
23+
1924Then run it from inside any git repository:
2025
2126``` sh
@@ -38,7 +43,7 @@ git_pruner version # also --version, -v
3843| ` a ` / ` n ` | Select all / clear selection |
3944| ` r ` | Toggle "also delete remote" for the row (needs an upstream) |
4045| ` v ` | View the branch's diff (green additions / red removals) |
41- | ` p ` | Fetch ` --all --prune ` , then select branches whose upstream is gone |
46+ | ` p ` | Fetch ` --all --prune ` , then select gone branches that hold no unique work |
4247| ` s ` | Cycle sort field: committerdate -> name -> ahead/behind |
4348| ` o ` | Reverse sort direction |
4449| ` f ` | Toggle delete mode: safe ` -d ` <-> force ` -D ` |
@@ -67,22 +72,30 @@ In the diff view: `↑`/`↓` scroll, `space`/`ctrl+d` page down, `ctrl+u`/`pgup
6772- ` > ` cursor, ` [x] ` selected, ` R ` remote deletion armed, ` * ` current branch
6873- ahead/behind shown as ` ↑N↓M ` (` = ` when in sync, ` gone ` in red when the upstream was deleted)
6974- a green ` ✓ ` after the track column means the upstream is merged into the remote default
70- branch ( ` origin/HEAD ` , else ` origin/main ` / ` origin/master ` ) — i.e. the remote is safe to delete
75+ branch — i.e. the remote is safe to delete
7176- relative commit date, short hash, and commit subject
7277
7378## Viewing a branch's changes
7479
7580Press ` v ` to see what a branch contains as a colorized patch — ** green** for additions, ** red**
7681for removals, magenta hunk headers. The diff is computed against the repository's default branch
77- (` origin/HEAD ` , falling back to ` main ` , then ` master ` ) using a three-dot diff
82+ (see [ Resolving the default branch ] ( #resolving-the-default-branch ) ) using a three-dot diff
7883(` git diff <base>...<branch> ` ), so it shows only the changes introduced on that branch since it
7984diverged. The view is scrollable for large diffs; the header shows which base it was compared to.
8085
86+ ## Resolving the default branch
87+
88+ The default branch is used as the diff base, as the merge target for the ` ✓ ` indicator, and to
89+ measure what a force delete would discard. It resolves to ` <remote>/HEAD ` if set, else
90+ ` <remote>/main ` , else ` <remote>/master ` , trying each configured remote in turn with ` origin `
91+ first — so repositories whose only remote is named something else (` upstream ` , a fork, …) still
92+ get merge information. If no remote resolves, a local ` main ` /` master ` is used.
93+
8194## Pruning gone branches
8295
8396Press ` p ` to run ` git fetch --all --prune ` in the background (the UI stays responsive). Once it
84- finishes, any local branch whose upstream was deleted is marked ** gone** and automatically
85- selected, and a status line reports how many were found. Press ` d ` to review and delete them.
97+ finishes, any local branch whose upstream was deleted is marked ** gone** , and a status line
98+ reports what was found. Press ` d ` to review and delete them.
8699
87100This is the interactive equivalent of:
88101
@@ -93,10 +106,26 @@ git fetch --all --prune && git branch -vv | awk '/: gone]/{print $1}' | xargs gi
93106Gone branches are always removed with ` git branch -D ` (force), since ` -d ` refuses a branch whose
94107upstream no longer exists — this is why selecting them via ` p ` prunes them even in safe mode.
95108
109+ Because ` -D ` discards unmerged commits and git reports ** no ahead/behind count for a gone
110+ branch** , git_pruner measures each one against the default branch with ` git cherry ` and counts
111+ the commits that have no equivalent patch there:
112+
113+ - gone branches holding ** no** such commits are auto-selected by ` p ` — the one-keystroke workflow
114+ - gone branches that ** do** hold unique commits are left unselected and reported in the status
115+ line, so discarding them takes a deliberate ` space ` ; the confirmation screen then shows
116+ ` ⚠ N commit(s) not in <base> — force delete (-D) will discard them `
117+
118+ ` git cherry ` is used rather than ` git rev-list <base>..<branch> ` so commits that were
119+ cherry-picked, rebased, or squashed individually into the base are correctly recognized as
120+ already integrated. A group of commits squashed together into one still counts as unique, since
121+ no single equivalent patch exists — which is why the warning reads "not in ` <base> ` " rather than
122+ claiming the work is unrecoverable.
123+
96124## Deletion behavior
97125
98126- Local: ` git branch -d ` by default (refuses unmerged branches); ` f ` switches to ` git branch -D ` .
99- Branches whose upstream is ** gone** are always deleted with ` -D ` , regardless of the mode.
127+ Branches whose upstream is ** gone** are always deleted with ` -D ` , regardless of the mode, and
128+ the confirmation screen flags any commits that would be discarded (see above).
100129 When a ` -d ` delete is refused for being unmerged, a follow-up prompt lets you retry those
101130 branches with ` -D ` without leaving the results — no need to back out and re-select.
102131- Remote: when armed with ` r ` , runs ` git push <remote> --delete <branch> ` , where the remote is
@@ -111,7 +140,22 @@ upstream no longer exists — this is why selecting them via `p` prunes them eve
111140## Development
112141
113142``` sh
143+ make build # build straight to $BINDIR (default ~/shared/bin), skipping install.sh
114144make test # go test ./...
115145make vet # go vet ./...
116- make clean # remove the installed binary
146+ make clean # remove the binary from $BINDIR
117147```
148+
149+ CI runs ` gofmt ` , ` go build ` , ` go vet ` , and ` go test -race ` on Linux and macOS for every push to
150+ ` master ` and every pull request (` .github/workflows/ci.yml ` ).
151+
152+ [ ` docs/improvements.md ` ] ( docs/improvements.md ) records the codebase analysis, the reasoning behind
153+ the current safety behavior, and the roadmap of remaining work.
154+
155+ The test suite drives a real ` git ` binary against throwaway repositories created per test, so it
156+ needs ` git ` on ` PATH ` and a committer identity (` user.name ` / ` user.email ` ); the tests set one
157+ inside each temporary repo.
158+
159+ ## License
160+
161+ [ MIT] ( LICENSE )
0 commit comments