Skip to content

Instantly share code, notes, and snippets.

@ifthenelse
Created April 19, 2026 15:46
Show Gist options
  • Select an option

  • Save ifthenelse/dc55421c0323230568013322cd59f441 to your computer and use it in GitHub Desktop.

Select an option

Save ifthenelse/dc55421c0323230568013322cd59f441 to your computer and use it in GitHub Desktop.
Zsh script to prune locale branches already merged into the default branch
#!/usr/bin/env zsh
# Prune local Git branches that are already merged into the default base branch.
# The script skips the base branch, the current branch, and branches with related stashes,
# then asks for confirmation before deleting each eligible branch.
prune_local_branches() {
local version="1.0.0"
case "${1:-}" in
-h|--help)
cat <<EOF
Usage: git-prune-branches [OPTIONS]
Prune local Git branches that are already merged into the default base branch.
The script skips the base branch, the current branch, and branches with related
stashes, then asks for confirmation before deleting each eligible branch.
Options:
-h, --help Show this help message
-v, --version Show version number
EOF
return 0
;;
-v|--version)
echo "git-prune-branches version $version"
return 0
;;
"")
;;
*)
echo "Error: unknown option '$1'" >&2
return 1
;;
esac
command -v git >/dev/null 2>&1 || { echo "Error: git is not installed."; return 1; }
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || { echo "Error: not a git repository."; return 1; }
local base current branch ans stash_count s
local -a removed skipped
base=$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null)
base=${base#origin/}
if [[ -z "$base" ]]; then
if git show-ref --verify --quiet refs/heads/main; then
base=main
elif git show-ref --verify --quiet refs/heads/master; then
base=master
else
echo "Error: could not determine main branch."
return 1
fi
fi
current=$(git branch --show-current)
while IFS= read -r branch; do
[[ -z "$branch" ]] && continue
if [[ "$branch" == "$base" ]]; then
skipped+=("$branch: protected base branch")
continue
fi
if [[ "$branch" == "$current" ]]; then
skipped+=("$branch: current branch")
continue
fi
stash_count=0
while IFS= read -r s; do
[[ "$s" == "WIP on $branch:"* || "$s" == "On $branch:"* ]] && ((stash_count++))
done < <(git stash list --format='%gs')
if (( stash_count > 0 )); then
skipped+=("$branch: has $stash_count related stash entries")
continue
fi
if ! git merge-base --is-ancestor "$branch" "$base" >/dev/null 2>&1; then
skipped+=("$branch: not merged into $base")
continue
fi
printf "Delete branch '%s'? [y/N] " "$branch"
read -r ans
if [[ "$ans" == [yY] ]]; then
if git branch -d "$branch" >/dev/null 2>&1; then
removed+=("$branch")
else
skipped+=("$branch: deletion failed")
fi
else
skipped+=("$branch: user declined")
fi
done < <(git for-each-ref --format='%(refname:short)' refs/heads)
if (( ${#removed[@]} == 0 )); then
echo "no branch pruned"
fi
if (( ${#removed[@]} )); then
echo "Removed branches:"
printf ' - %s\n' "${removed[@]}"
fi
if (( ${#skipped[@]} )); then
echo "Not removed:"
printf ' - %s\n' "${skipped[@]}"
fi
}
prune_local_branches
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment