Skip to content

Instantly share code, notes, and snippets.

@nabilfreeman
Created September 11, 2025 17:50
Show Gist options
  • Select an option

  • Save nabilfreeman/ff489d0876d2e2463d244b10633b1187 to your computer and use it in GitHub Desktop.

Select an option

Save nabilfreeman/ff489d0876d2e2463d244b10633b1187 to your computer and use it in GitHub Desktop.
Bash function to clean up all locally merged branches and worktrees. Add to your .zshrc or .bashrc and run `gitcleanup` in terminal to make the magic happen
# gitcleanup: remove merged branches and their worktrees, after ensuring everything is up‑to‑date
# Usage: gitcleanup [target-branch]
#
# 1. Ensure the target branch exists, is clean, and up‑to‑date with origin.
# 2. For every local branch already merged into the target:
# • If the branch has a worktree and that worktree is clean, fast‑forward it with `git pull --ff-only`.
# • If the worktree remains clean, remove the worktree and delete the branch.
# • If the branch has no worktree, delete the branch directly.
# 3. Stale worktree records are pruned first.
gitcleanup() {
local target=${1:-main} # merge base (default: main)
# 1. Sync refs
echo "Fetching latest refs from all remotes…"
git fetch --prune --all || return 1
# Remember where we were, so we can come back later
local current_branch
current_branch=$(git symbolic-ref --quiet --short HEAD || true)
# 2. Make sure $target is checked out in the primary worktree
if [[ "$current_branch" != "$target" ]]; then
echo "Checking out $target…"
git switch --quiet "$target" || git checkout "$target" || {
echo "❌ Unable to switch to branch '$target'"; return 1; }
fi
# 3. Bail out if $target worktree is dirty
if ! git diff-index --quiet HEAD --; then
echo "❌ Target branch '$target' has uncommitted changes – aborting cleanup."
[[ -n "$current_branch" && "$current_branch" != "$target" ]] && git switch --quiet "$current_branch"
return 1
fi
# 4. Fast‑forward $target
echo "Pulling latest commits into $target…"
git pull --ff-only --quiet origin "$target" || {
echo "❌ Pull failed – aborting cleanup."; return 1; }
# 5. Get rid of stale worktree refs first
git worktree prune
# 6. Iterate over every local branch already merged to $target (except $target itself)
git for-each-ref --format='%(refname:short)' --merged "$target" refs/heads |
grep -v "^${target}$" |
while read -r branch; do
# Does this branch live in an active worktree?
local wt_path
wt_path=$(
git worktree list --porcelain |
awk -v b="refs/heads/$branch" '
$1=="worktree" { w=$2 }
$1=="branch" && $2==b { print w }
'
)
if [[ -n "$wt_path" ]]; then
# Worktree exists – is it clean?
if git -C "$wt_path" diff-index --quiet HEAD --; then
echo "Fast‑forwarding branch $branch in worktree $wt_path…"
if git -C "$wt_path" pull --ff-only --quiet; then
echo "Removing clean worktree $wt_path (branch $branch)…"
if git worktree remove "$wt_path"; then
git branch -d "$branch"
else
echo "⚠️ Couldn’t remove worktree $wt_path – skipping branch deletion"
fi
else
echo "⚠️ Couldn’t pull branch $branch – skipping"
fi
else
echo "⚠️ Skipping $wt_path (branch $branch) – uncommitted changes present"
fi
else
# No worktree. Just delete the local branch (already merged).
git branch -d "$branch"
fi
done
# 7. Return to original branch if needed
if [[ -n "$current_branch" && "$current_branch" != "$target" ]]; then
git switch --quiet "$current_branch"
fi
echo "✅ Cleanup complete."
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment