Background gradient
Cleaning solution

If you're anything like us, you're not the best at deleting your local git branches after your pull requests have been merged.

After a while, the hundreds of dusty, stale git branches pile up. Running git branch is useless. Any hope of finding that branch that you're looking for but forgot the name to is futile in the poorly labeled haystack.

Cleaning this mess up, even if you muster the willpower, is a daunting task — you fear accidentally deleting a branch with some useful, in-progress, but unmerged code that you were saving for that task on the backlog.

Eventually, the frustration and lack of usability spills over and you declare branch bankruptcy, going through and clearing all of your branches; the code can be re-written.

As we developed the Graphite CLI, we found ourselves running into this situation more and more. This task is particularly important for stacked workflows, where there are more branches and where users frequently merge in the bottom of their stack and need to shift the remainder.

As a result, we found a better, more civil solution: a way to programmatically review your branches and delete any that are definitively merged (the exact contents of their commits have been merged into trunk).

The following snippet successfully does the job:

(skip ahead for the explanation)

function isMerged(branchName: string, trunk: string) {
const tree = execSync(`git rev-parse "${branchName}^{tree}"`).toString();
const mergeBase = execSync(`git merge-base ${trunk} ${branchName}`).toString();
const commitObject = execSync(`git commit-tree ${tree} -p ${mergeBase} -m _`).toString();
const cherryResult = execSync(`git cherry ${trunk} ${commitObject}`).toString();
return cherryResult.startsWith('-');

In English, we:

This sequence reliably finds branches that have been merged into trunks, including stacked branches. That being said, it's not perfect:

  • By comparing exactly the tip of the branch, the script won't flag a branch that had been merged into trunk but then advanced by a commit. We could improve the script by having it consider each commit in the branch one at a time, rather than just the tip.

  • It will also only flag branches whose merge base is behind their merge commit. This means the script would not flag a branch that had been squashed and merged on GitHub, and then later rebased the branch onto trunk's tip.

If you want to clean your old branches right now, run:

brew install screenplaydev/tap/graphite;
gt repo sync;

In addition to deleting your branches with this logic, gt repo sync performs a few other niceties:

  • pulls the latest code in from your trunk

  • detects partial sets of stacked branches that have been merged, deletes them, and rebases the remainder of the stack appropriately

  • removes any now-dead remotes

If you're interested a look behind the curtain, we're open source — take a look at how we do it here!