Overview
Git blocks git branch -d <name> if the branch’s commits are not reachable from your current branch (often main or develop). This protects unmerged work from accidental loss. You can:
- Integrate the work (merge, rebase, or cherry-pick), then delete.
- Decide to discard it and force-delete (
-D) after a quick safety check.
This guide shows practical, DevOps-friendly steps to resolve the message safely.
Quickstart (most common fixes)
- See what’s unmerged:
git branch --no-mergedgit log --oneline --left-right --cherry-pick --graph main...feature
- Merge the branch into your target:
git checkout main && git merge --no-ff feature
- Or pick specific commits:
git checkout main && git cherry-pick <commit1> <commit2>
- If discarding on purpose, make a backup tag and force-delete:
git tag backup/feature feature && git branch -D feature
- Delete remote branch (if needed):
git push origin --delete feature && git fetch -p
Minimal working example
This demonstrates the error and three ways to fix it.
# 1) Setup demo repo
mkdir demo && cd demo
git init -b main
printf "one\n" > file.txt
git add file.txt && git commit -m "init"
# 2) Create a feature branch with unique commits
git checkout -b feature
echo "feature A" >> file.txt
git commit -am "feat: add A"
echo "feature B" >> file.txt
git commit -am "feat: add B"
# 3) Switch back and try to delete (will fail)
git checkout main
git branch -d feature
# -> The branch 'feature' is not fully merged ...
# Fix Option A: Merge the work
git merge --no-ff feature
git branch -d feature # now succeeds
# Reset to before merge to try other options (demo only)
git reset --hard HEAD~1
# Fix Option B: Cherry-pick specific commit(s)
git log --oneline main..feature # list unique commits
git cherry-pick <hash-of-desired-commit>
git branch -d feature
# Fix Option C: Force-delete after making a backup tag
git tag backup/feature feature
git branch -D feature
Step-by-step resolution
- Confirm target branch
- Ensure you’re on the branch that should contain the work (e.g.,
main). git rev-parse --abbrev-ref HEAD
- Identify unmerged work
- List branches not fully merged into your current branch:
git branch --no-merged
- Compare differences (symmetric diff):
git log --oneline --left-right --cherry-pick --graph main...feature
- Show commits unique to
feature:git log --oneline main..feature
- Diff actual changes:
git diff main...feature
- Decide how to handle the changes
- Merge (keeps history):
git checkout main && git merge --no-ff feature - Squash merge (single commit):
git checkout main && git merge --squash feature && git commit - Rebase feature onto main, then fast-forward merge:
git checkout feature && git rebase main && git checkout main && git merge feature
- Cherry-pick selected commits onto main:
git checkout main && git cherry-pick <commit1> [<commit2> ...]
- Delete the local branch
- Safe delete (only if fully merged):
git branch -d feature - Force delete (discarding remaining work):
- Optional backup:
git tag backup/feature feature - Force delete:
git branch -D feature
- Optional backup:
- Delete the remote branch (if applicable)
git push origin --delete feature- Clean up local stale tracking refs:
git fetch -p
- Verify cleanup
git branch --all --list '*feature*'
When to use each option
- Merge or squash merge: You want to keep the branch’s work and history (or a condensed form) in the target branch.
- Cherry-pick: Only some commits are desired.
- Force delete: You intentionally discard remaining work (back it up first with a tag or note the commit hashes).
Common pitfalls
- Deleting the checked-out branch: Switch away first (
git checkout main). - Confusing local vs remote:
git branch -daffects local branches only. Remote deletion usesgit push origin --delete <name>. - Protected branches: Remotes (GitHub/GitLab) may block deletion; adjust protections or use an admin workflow.
- Orphaned work after force-delete: Without a tag or note of commit IDs, finding the commits later is harder.
- Assuming PR merge equals Git merge: Squash/rebase merges may create new commit IDs; confirm the target branch truly contains the changes:
git log --cherry-pick --oneline main...feature.
Recovery tips (if you deleted too soon)
- Find the last commit of the deleted branch in the reflog:
git reflog | grep feature(or inspect recent entries)
- Recreate the branch from the commit:
git branch feature <commit>
- If remote was deleted but others still have it, fetch from a peer or the remote’s refs if available.
DevOps considerations
- CI/CD pipelines: Delete branches after merging to reduce clutter, but only after green pipelines and artifact publication.
- Branch protection policies: Require reviews/status checks and allow branch deletion only after merge.
- Automation: In cleanup scripts, check merged status first:
#!/usr/bin/env bash
set -euo pipefail
base=${1:-main}
for b in $(git for-each-ref --format='%(refname:short)' refs/heads/); do
[[ "$b" == "$base" ]] && continue
if git merge-base --is-ancestor "$b" "$base"; then
git branch -d "$b"
fi
done
Performance notes
- Large repos: Prefer commit-range operations over full diffs when deciding merge status:
git merge-base --is-ancestor feature mainis fast and reliable.
- Fetch pruning:
git fetch -pkeeps refs lean and speeds up branch listings. - Commit-graph: Enable for faster history queries:
git config --global core.commitGraph true && git commit-graph write --reachable
FAQ
- What does “not fully merged” mean?
- The target branch (usually your current branch) doesn’t contain all commits from the branch you’re deleting.
- Is
-Dsafe?- It force-deletes even if unmerged. Use only after verifying diffs or tagging a backup.
- Do I need to merge before deleting a remote branch?
- No. Remotes typically allow deletion regardless of merge state, unless protections apply.
- How do I check if a branch is merged into a specific target?
git merge-base --is-ancestor feature main(exit code 0 means merged).