git-delete-merged-branches: a Python CLI for clearing merged branches, and the cases it refuses to handle
:fire: Command-line tool to delete merged Git branches
At a glance
- What is it?
- hartwork/git-delete-merged-branches is a GPL-3.0 Python tool that deletes local and remote branches it considers merged, with interactive confirmation and a dry-run mode. The interesting part is not the deletion itself but how it decides what counts as merged, and where that decision stops.
- Who is it for?
- Adopt it if you maintain repositories with several long-lived release branches and a steady flow of squash-merged or rebased pull requests, because plain git branch --merged will not see those as merged. Skip it if your team merges with ordinary merge commits only, or if you cannot accept a tool that runs git pull --ff-only against your checked-out branch during cleanup.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 9 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The branch hygiene problem it actually targets
Most Git cleanup advice reduces to git branch --merged, which answers a narrow question: which branches are ancestors of the current commit. That works when every pull request lands as a merge commit. It fails when a maintainer rebases a branch onto master, when a hosting platform squash-merges a pull request into a single commit, or when a fix is carried across as a cherry-pick. In all three cases the branch content is in the mainline, but the branch tip is not an ancestor, so git branch --merged stays silent and the branch survives. The README lists exactly these cases under Features: rebase merges, squash merges that need --effort=3, and single or range cherry-picks, with the note that the tool leverages git cherry. The audience is therefore narrower than "anyone with a messy repository". It is aimed at maintainers who run a rebase or squash workflow and who have accumulated branches that no longer correspond to any work in progress.
How merge detection works, and why --effort exists
The tool does not reimplement patch comparison. According to the README it leverages git cherry, the plumbing command that compares commits between two branches by patch identity rather than by commit ancestry. That is what allows it to recognise a branch whose changes exist on the mainline under different commit hashes. The --effort=3 flag is tied specifically to squash merges in the README's feature list, which suggests the default effort level does not attempt that class of detection. The practical consequence is that effort is a dial between speed and aggressiveness: higher settings cause more comparison work and can classify more branches as merged. The README does not document the full range of accepted values or the cost of each level, so treat the default as the conservative position and --effort=3 as an explicit opt-in. For teams with multiple release branches, the README states the tool can be configured to delete only branches merged into all of master, dev and staging, which is the opposite default from a single-target check and is the reason the project exists for release-train workflows.
Installation paths and the git-extras command collision
The primary install is pip install git-delete-merged-branches. The README also lists distribution packages: apt install git-delete-merged-branches on Debian bookworm and later and on Ubuntu lunar and later, brew install git-delete-merged-branches, port install py-git-delete-merged-branches on MacPorts, emerge -av dev-vcs/git-delete-merged-branches on Gentoo, xbps-install -S git-delete-merged-branches on Void, yay -S git-delete-merged-branches on the AUR, and nix-shell -p git-delete-merged-branches. One packaging detail deserves attention before you install: the utility collection git-extras ships a shell script with the same name, and the README documents the resulting command collision. It offers four workarounds: invoke python3 -m git_delete_merged_branches, use the short alias git-dmb, adjust $PATH resolution order, or call the binary by absolute path after checking it with which -a git-delete-merged-branches. If you already have git-extras installed, the collision is not hypothetical, and the module invocation is the least ambiguous of the four.
What a run looks like, step by step
The README's example shows the sequence. The tool first asks whether to run git remote update --prune for each remote, listing them; then whether to run git pull --ff-only for each branch, listing them; then presents the local branches it intends to delete and waits; then, after deletion, presents the remote branches and waits again. Every prompt defaults to no, and the README states plainly that pressing Enter does not delete. The default is interactive, so a first run on a real repository is a series of decisions rather than a single command. The --yes flag exists to skip confirmation, and --dry-run exists to see the planned changes without making them. --verbose prints the Git commands being run. That combination means you can rehearse the whole flow, including the remote update and pull steps, before anything is removed.
The pull step is the sharpest edge in the workflow
Because the tool offers to run git pull --ff-only on branches such as master, it can move your working tree as part of a cleanup operation. The --ff-only restriction means it will not create a merge commit or resolve conflicts on your behalf, which limits the damage, but it still means a branch deletion tool is also a branch update tool. If your checked-out branch has diverged from its remote, the pull fails rather than doing something surprising, and you are left to sort that out yourself. That is a reasonable design, but it is a design that couples two operations people usually keep separate. The second constraint is the remote side. The README states that git push is used with --force-with-lease, so if the server's view of a branch differs from yours, the deletion is refused. That protects against deleting remote work you have not seen, and it also means a stale local remote-tracking ref will block a deletion until you run the remote update step. The two behaviours interact: the update exists to make the lease check meaningful.
When this is the wrong tool, and what to use instead
If your team merges exclusively with merge commits and never squashes or rebases, the tool's distinguishing feature is unused. Plain git branch --merged master, followed by git branch -d for each result, does the same job with no installation, and git branch -d refuses to delete a branch that is not merged, which is its own safeguard. The difference in approach is that git branch -d relies purely on commit ancestry and will always refuse a squash-merged branch, while this tool uses patch comparison through git cherry to accept it. That makes git branch -d the safer choice on a repository where you are not confident about the merge history, and this tool the more useful one on a repository where you are. A second case to avoid: repositories where branch names carry meaning beyond their commits, such as long-lived environment branches or branches kept as documentation of abandoned work. No merge-detection heuristic distinguishes those from stale feature branches, and the confirmation prompt is the only thing standing between them and deletion.
Maintenance, licensing, and what to check before adopting
The project is written in Python and licensed GPL-3.0, which matters if you intend to vendor the code into a proprietary internal tool rather than install it as a dependency. Installing it as a separate command-line program through pip or a distribution package is the normal path and does not raise the same question. Release activity visible in the supplied material runs from 7.5.1 in January 2026 through 7.6.0 in May 2026 to 7.6.1 in July 2026, with the repository not archived. The README does not describe a configuration file format, so settings appear to be command-line flags rather than persisted state; that means any per-repository preferences, such as which release branches must all be matched, have to be repeated or wrapped in a script of your own. That wrapper script is the real maintenance cost for a team, not the tool itself. The README also notes that GitHub can be configured to delete branches of merged pull requests automatically, which reduces the remote-side work this tool has to do. Before adopting, confirm two things on your own repository: that --effort=3 finds the squash-merged branches you expect, and that the git pull --ff-only step does not conflict with how your team keeps its checked-out branch in sync.
Editorial conclusion
Adopt it if you maintain repositories with several long-lived release branches and a steady flow of squash-merged or rebased pull requests, because plain git branch --merged will not see those as merged. Skip it if your team merges with ordinary merge commits only, or if you cannot accept a tool that runs git pull --ff-only against your checked-out branch during cleanup. Before the first real run, execute git-delete-merged-branches --dry-run --verbose on one repository and read the Git commands it prints; that output tells you whether its definition of merged matches yours.
Community notes