You're done with a feature branch and want it gone, or your git branch output
has forty stale names in it and you need git delete local branch cleanup
before it becomes unreadable. Git ships several commands for deleting something called a
"branch," and the differences matter: -d refuses to delete unmerged work,
-D deletes it anyway, and deleting a remote branch is a completely separate
operation from deleting your local copy of it. Here is the fact that should calm you down
before any of this: deleting a branch never deletes commits — it only
removes a pointer. The commits stay in your repository's object database until git
garbage-collects them, usually 30–90 days later, which is exactly why reflog recovery works
later in this guide. If you're also straightening out commit history while you're in here,
the git undo last commit guide covers reset,
revert, and amend for the commits themselves — this guide is about the branch pointers.
Quick Answer: Every Git Delete Branch Command
Pick your scenario. This is how to delete any branch in git — local, remote, stale remote-tracking refs, or a whole batch of merged branches at once. Full explanations follow in the sections linked from each row.
| Situation | Command | What it touches |
|---|---|---|
| Delete a local branch (safe, merged only) | git branch -d branch-name | Local ref only |
| Delete a local branch (force, any merge status) | git branch -D branch-name | Local ref only |
| Delete a remote branch | git push origin --delete branch-name | Remote server ref |
| Delete a remote branch (older refspec form) | git push origin :branch-name | Remote server ref |
| Delete only your local copy of a remote branch | git branch -d -r origin/branch-name | Local remote-tracking ref only |
| Clean up all stale remote-tracking refs | git fetch --prune | Local remote-tracking refs |
| Delete local AND remote in one pass | git branch -d branch-name && git push origin --delete branch-name | Local ref + remote server ref |
| Bulk-delete every merged branch except main | See the bulk cleanup one-liner below | Multiple local refs |
| Recover a branch you just deleted | git reflog + git branch name <SHA> | Recreates local ref |
The most common mistake beginners search for is "git rm branch" — that
command does not exist. git rm removes files from the index, not branches.
The real git delete branch command is git branch -d (or -D) for
local branches and git push --delete for remote ones. There's no unified
git remove branch shortcut that handles both — they're genuinely different
operations against different ref namespaces, which is the whole reason this guide has
separate sections for each.
git branch -d vs -D: The Safe Check and the Override
-d (--delete) is the safe form. It deletes a branch only if it is
fully merged — technically, only if its tip commit is reachable from HEAD or
its upstream. If it isn't, git stops you:
$ git branch -d feature-old
error: The branch 'feature-old' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature-old'. -D is literally shorthand for --delete --force. It deletes
irrespective of merge status — no check, no refusal, no warning beyond the missing commits
themselves. If what you actually want is to destroy git branch state outright, ignoring
whether the work made it anywhere else, that's -D, not -d:
# Safe: refuses if not merged into HEAD or its upstream
git branch -d feature-old
# Force: deletes regardless of merge status — commits can be lost
git branch -D feature-old
Default to -d. It costs you nothing extra when the branch really is merged, and
it stops you cold when it isn't — which is the scenario where a delete local branch command
would otherwise silently throw away work nobody merged yet. Reach for -D only
after you've confirmed, by reading the diff or checking git cherry (covered in
the bulk cleanup section), that the branch's content genuinely isn't needed.
A second common search here is "git rm remote branch" — same mistake, same
answer. git rm is a file-index command; it has never deleted branches, local or
remote. If you want to remove a branch locally, it's git branch -d. If you want
to remove it from the remote, skip ahead to the next section. For the full flag reference,
see the official
git-branch documentation.
How to Delete a Local Branch Step by Step
If you just want to know how to delete a branch without thinking about merge status yet, start here — this is the everyday, five-second version.
# 1. See what you have locally, and which one is current (marked with *)
git branch
# 2. Make sure you are NOT on the branch you want to delete
git switch main
# 3. Delete it — this is the git delete local branch command you'll use 95% of the time
git branch -d feature-old
# 4. Confirm it's gone
git branch
That's it for the common case: git branch -d is the remove branch command,
full stop, for anything already merged — whatever phrasing you searched for, git
remove branch included, this is the command it resolves to. If git refuses because
the branch isn't merged and you're certain you don't need the work, repeat step 3 with
-D instead. If you're not certain,
diff the branch against main before you decide — git log main..feature-old
shows every commit main doesn't have yet, and git diff main...feature-old shows
the actual content.
Searches for "github remove branch locally" or "github delete a local branch" usually mean one of two things: deleting your local copy after a pull request merges (the exact steps above), or removing the branch from GitHub's servers instead (the next section). GitHub's website has no button for the local case — deleting a local branch always happens in your terminal or git client, never on the website, because your local branches aren't something GitHub can see or touch. It's local to your machine only — a purely local remove operation — and it never sends a single byte over the network.
Git Delete Remote Branch: --delete and the Refspec Form
To remove a branch from the remote — or git remove remote branch, same
operation, different phrasing — on GitHub, GitLab, Bitbucket, or any other server, use
git push, not git branch. It's a git push delete
branch pattern: you're pushing an empty source into the ref, and the server
interprets that as a delete request, whether you type the command by hand or a UI button
triggers it for you:
# Modern, explicit form
git push origin --delete branch-name
# Older, still common in scripts and CI configs — same effect
git push origin :branch-name
The refspec form is the one that confuses people the first time they see it, so it's worth
understanding rather than memorizing. A normal push is
git push origin local-branch:remote-branch — take what's at
local-branch and push it into remote-branch. Leave the source side
empty, as in git push origin :branch-name, and you're pushing nothing
into that remote ref. Pushing nothing into a ref is git's way of saying "delete it." Almost
no other guide walks through why that syntax works — it's not a special delete flag, it's
the ordinary push mechanism with an empty source.
# Full picture of the refspec form
git push origin <source>:<destination>
# Normal push: local main becomes the new remote main
git push origin main:main
# Delete push: nothing becomes the new remote branch-name — i.e. it's removed
git push origin :branch-name Note the naming trap: "git branch delete remote branch" isn't a real,
single command — git branch only ever touches local refs, including the
-r flag, which touches your local remote-tracking refs, not the branch
actually sitting on the server. Deleting the branch on the server always requires
git push. If you run git branch -d -r origin/feature-x, you delete
only your local remote-tracking ref — the branch on the server is completely untouched,
and it will simply reappear locally the next time you fetch.
# Deletes ONLY the local remote-tracking ref — the server branch is unaffected
git branch -d -r origin/feature-x
# The server branch is still there, so a fetch brings the ref right back
git fetch origin Deleting a remote branch from the GitHub or GitLab web UI
For "how to delete a branch in github," "github remove remote branch," or "github delete
branch from remote" — all the same question phrased differently — open the repository, go
to the branches page (github.com/owner/repo/branches), and click the trash icon
next to the branch you want gone. That performs the same --delete push
server-side.
For "gitlab remove remote branch": open Repository → Branches in the project sidebar, find the branch, and click the delete icon next to it. Both GitHub and GitLab also let you auto-delete the source branch when a pull/merge request merges — a checkbox in the PR/MR settings that saves you the manual step for the common case.
Every git delete remote branch route — the --delete flag, the
refspec form, or a web UI trash icon — ends up as the same push server-side. For the
underlying push mechanics behind both the CLI and the web UI, see the
official git-push
documentation.
Git Delete Local and Remote Branch Together
Because local branches, local remote-tracking refs, and the branch on the server are three separate things (the next section covers this in detail), a "git delete local and remote branch" cleanup needs two commands, not one:
# Delete the branch on the remote server first
git push origin --delete feature-payments
# Then delete your local branch
git branch -d feature-payments
# Or chain them — remote delete, then local delete, only if the first succeeds
git push origin --delete feature-payments && git branch -d feature-payments
Order rarely matters in practice, but deleting the remote first means if the remote push
fails (permissions, protected branch, network), you still have your local branch intact to
retry from. If you'd rather confirm the remote branch's work truly landed in main before
removing anything, compare the file content directly — the
git diff between two files guide covers
pulling two versions of a file from different branches with git show and diffing
them, which is the fastest way to be sure before you run a delete you can't easily undo on
the remote.
To do a "git remove local and remote branch" cleanup for several branches, run the delete commands per branch, then finish with a prune so your local remote-tracking refs match reality:
for b in feature-a feature-b feature-c; do
git push origin --delete "$b"
git branch -d "$b"
done
git fetch --prune Comparison Table: The Three Things Called a Branch
Most confusion around deleting branches comes from git using the word "branch" for three different objects. Understanding which one a command touches makes every delete command in this guide predictable instead of memorized.
| Thing | Where it lives | Command to delete it | Deleting it affects the others? |
|---|---|---|---|
| Local branch | .git/refs/heads/ on your machine | git branch -d / -D | No — remote and remote-tracking ref untouched |
| Remote-tracking branch | .git/refs/remotes/origin/ on your machine (a local cache of remote state) | git branch -d -r or git fetch --prune | No — the actual server branch untouched; it reappears on next fetch unless the server branch is also gone |
| Remote server branch | refs/heads/ inside the remote repository (GitHub, GitLab, etc.) | git push origin --delete or web UI trash icon | No — your local branch and remote-tracking ref are untouched until you clean those up too |
Deleting one of these three never automatically deletes the other two. That's why "git
delete branch" cleanup so often leaves half-finished residue — you deleted the branch on
GitHub after a merge, but git branch -a on your machine still lists both the
local branch and origin/feature-x as if nothing happened, because from your
machine's point of view, nothing did.
Pruning Stale Remote-Tracking Branches
Once teammates delete branches on the remote — through a merged PR's auto-delete, or
manually — your local remote-tracking refs (the origin/* entries in
git branch -r) don't update themselves. They still point at branches that no
longer exist server-side until you prune them.
# Remove local remote-tracking refs for branches deleted on the remote
git fetch --prune
# Same effect, without also fetching new commits
git remote prune origin
# Preview what would be removed, without removing anything
git fetch --prune --dry-run
git remote prune origin --dry-run git fetch --prune does two things in one step: it fetches new commits and refs
as usual, and it removes any remote-tracking ref whose corresponding branch no longer exists
on the remote. git remote prune origin does only the second part — useful when
you want to clean up stale refs without triggering a full fetch. Both dry-run variants print
exactly what would be deleted so you can eyeball the list first.
This is a genuinely useful place to compare two lists side by side: run
git branch -a before pruning and save the output, then run it again after. The
comparing two lists guide covers pasting both
outputs into a diff tool to confirm exactly which remote-tracking refs disappeared — much
faster than scanning forty branch names by eye when a prune touches a lot of refs at once.
Many teams set fetch.prune to true in their git config so every
ordinary git fetch prunes automatically, removing the need to remember the flag:
git config --global fetch.prune true Bulk Cleanup: Deleting Many Merged Branches at Once
git branch --merged lists every local branch that's fully merged into your
current HEAD. Combined with xargs, it becomes a one-liner that
deletes all of them safely:
git branch --merged main | grep -v '^[*+]' | grep -vE '^\s*(main|master|develop)$' | xargs -r git branch -d Broken into stages:
git branch --merged main— lists branches whose tip is reachable frommaingrep -v '^[*+]'— drops the current branch (marked*) and any branch checked out in another worktree (marked+), since those can't be deleted anywaygrep -vE '^\s*(main|master|develop)$'— excludes your protected long-lived branches by name, so you never accidentally target themxargs -r git branch -d— runsgit branch -donce per remaining name;-rskips the command entirely if the list is empty
Using -d here, not -D, is what keeps the whole pipeline safe: any
branch that --merged got wrong for some reason still has to pass git's own
merge check before it's deleted. To preview the pipeline without deleting anything, drop the
final stage and just look at the list:
# Dry run — see exactly what the cleanup would delete
git branch --merged main | grep -v '^[*+]' | grep -vE '^\s*(main|master|develop)$' The gotcha almost nobody mentions: --merged only catches
branches that were merged with a real merge commit or a fast-forward. Squash-merged and
rebase-merged branches will not show up as merged, because none of the original
branch's commits are ancestors of HEAD — the entire branch landed as one new,
different commit (squash) or as a set of replayed commits with new SHAs (rebase). Run the
cleanup pipeline above after a bunch of squash-merged PRs, and it will silently skip every
one of them, even though the work is safely in main. git branch -d
will also refuse to delete those branches individually for the same reason.
The check that actually works for squash and rebase merges is git cherry:
# Compare the branch's commits against main by patch content, not by SHA
git cherry -v main feature-squashed
# Empty output, or every line prefixed with "-", means the changes
# already exist in main — safe to force-delete with -D
git branch -D feature-squashed git cherry -v main feature-x compares patches by content rather than by commit
SHA, so it correctly identifies work that made it into main through a squash or
rebase merge even though no individual commit matches. A - prefix on every line
(or no output at all) means it's safe to delete; a + prefix means that patch
isn't in main yet, and you should look before deleting.
Why Your Delete Fails: Current Branch, Protected Branches, Worktrees
Three failure modes account for almost every "why won't git delete my branch" question — and in none of them was the git delete local branch syntax you typed actually wrong.
1. You're trying to git delete current branch
$ git branch -d feature-x
error: Cannot delete branch 'feature-x' checked out at '/Users/you/project'
A checked-out branch is what HEAD symbolically points to. Removing it would
leave the repository pointing at nothing — detached HEAD with no branch reference at all.
Git refuses outright rather than let that happen. The fix is always to move off the branch
first:
git switch main
git branch -d feature-x 2. The branch is checked out in a different worktree
Newer git also refuses to delete a branch that's checked out in another worktree, even if it isn't your current branch in this working directory:
$ git branch -d feature-x
error: Cannot delete branch 'feature-x' checked out at '/Users/you/worktrees/feature-x' # Find where it's checked out
git worktree list
# Then either remove that worktree...
git worktree remove /Users/you/worktrees/feature-x
# ...or switch that worktree to a different branch, then delete from here
3. The remote branch is protected
git push origin --delete main can fail even when your local command syntax is
perfect — because GitHub or GitLab branch protection rules block deletion server-side. This
is not a local error; the push leaves your machine fine and gets rejected on arrival:
$ git push origin --delete main
remote: error: GH006: Protected branch update failed for refs/heads/main.
remote: error: Cannot delete a protected branch
To github.com:your-org/your-repo.git
! [remote rejected] main (protected branch hook declined)
error: failed to push some refs to 'github.com:your-org/your-repo.git'
The fix isn't a different git flag — it's changing the protection rule in the repository's
settings (or asking whoever owns them to). Self-hosted, non-GitHub remotes have a git-native
version of the same idea: receive.denyDeleteCurrent, a config option on the
remote repository that blocks deleting whichever branch is currently checked out there.
Almost every guide covers -d vs -D and stops — protected-branch
rejection is a real, common failure mode that deserves equal billing.
If any of these failures happened because you deleted the wrong thing rather than the branch you meant to, note that deleting a branch and undoing a commit are different repairs — the git revert commit guide covers reversing commit content, which a branch delete alone won't do for you.
Recovering a Deleted Branch with git reflog
Restated plainly, because it's the fact that makes recovery possible: deleting a branch only
removes a pointer. The commits it pointed to stay in .git/objects until git's
garbage collector decides nothing references them anymore and prunes them — which by default
takes 30 days for entirely unreachable commits (gc.reflogExpireUnreachable)
and 90 days for commits still reachable through some other ref
(gc.reflogExpire). Both are configurable defaults, not guarantees — a repository
that has had gc.reflogExpireUnreachable lowered, or that someone has run
git gc --prune=now on, gives you a much shorter window.
If you catch it immediately and haven't run any other git commands since, the fastest path
is git reflog show on the branch name — but this only works before the delete
actually lands, since the reflog is tied to the ref itself. In practice, for a branch that's
already deleted, the reliable path is the general HEAD reflog:
# Find the last SHA the deleted branch pointed to
git reflog | grep feature-payments
# Example match:
# b7c4d81 HEAD@{3}: checkout: moving from feature-payments to main
# Recreate the branch at that SHA
git branch feature-payments b7c4d81
# Confirm it's back with the expected history
git log --oneline feature-payments -5 If the reflog entry itself is missing — a longer-lived deletion, or a fresh clone that never had the reflog history — search for dangling commits directly:
# Find commits no longer referenced by any branch or tag
git fsck --full --no-reflogs --unreachable --lost-found
# Inspect a candidate before restoring it
git show <dangling-sha>
# Recreate the branch once you've confirmed it's the right commit
git branch feature-payments <dangling-sha>
Before trusting a recovered SHA, verify it actually is what you think it is. Paste the
recovered commit's diff into a visual comparison rather than eyeballing raw
git show output for a large commit — it's much faster to confirm every expected
file changed the way you remember, especially when the recovery touched more than a couple
of files.
Verifying What You're About to Delete
Before running -D on a branch that isn't confirmed merged, it's worth building
a habit of checking twice — not because git is unreliable, but because the check costs ten
seconds and a mistake costs the recovery workflow above. Diff Checker (a free Chrome
extension, no signup) is a plain visual diff tool, not a git plugin — it has no branch
awareness and no repository integration — but it's a solid second pair of eyes for exactly
this kind of confirmation.
Two concrete uses when you're about to force-delete something:
-
Run
git branch -abefore and after a prune or bulk delete, and paste both outputs into the extension's side-by-side view. Switch on Show Diff Only and you'll see exactly which ref lines disappeared, with nothing else cluttering the comparison. -
Before force-deleting an unmerged branch, run
git show branch-a:path/to/fileandgit show main:path/to/filefor the files you're worried about, then paste both outputs into the two panes. The stats bar reports a percent-match figure and exact added/removed/modified line counts, which is a faster sanity check than reading a fullgit diffoutput line by line when you just need a yes-or-no answer.
Everything runs locally in the browser — the extension opens in a full tab (not a popup),
supports Split and Unified view modes, a Smart Diff / Ignore Whitespace / Classic (LCS)
comparison mode, and syntax highlighting for shell output and 16 other languages. Nothing is
sent anywhere unless you explicitly turn on the optional AI summary. It's a verification
habit, not a replacement for reading git log and git cherry — those
stay the actual source of truth about what's merged.
Frequently Asked Questions
How do I delete a branch I'm currently on?
You can't, directly — git refuses to delete the branch that HEAD currently
points to, because doing so would leave the repository in a detached HEAD state with no
branch to return to. Switch to a different branch first with git switch main
or git checkout main, then delete the old branch with
git branch -d old-branch. If the branch is checked out in a different
worktree rather than your current one, run git worktree list to find that
worktree, then switch it to another branch or remove it with
git worktree remove <path> before deleting the branch.
What is the difference between git branch -d and -D?
git branch -d (lowercase, short for --delete) deletes a branch
only if it is fully merged — technically, only if its tip commit is reachable from
HEAD or its upstream. If it isn't, git refuses and tells you to use
-D. git branch -D is shorthand for --delete --force
and deletes the branch regardless of merge status, discarding any commits that exist only
on that branch. Use -d as your default and reach for -D only
when you've confirmed, by reading the diff or checking git cherry, that you
genuinely want to discard unmerged work.
How do I delete a remote branch in Git?
Run git push origin --delete branch-name to delete a branch on the remote
(GitHub, GitLab, Bitbucket, or any other server). The older equivalent, still common in
scripts, is the refspec form git push origin :branch-name — pushing nothing
into that remote ref tells git to remove it. Either command only removes the branch on
the server; your local branch and your local remote-tracking ref
(origin/branch-name) still exist until you delete them separately or run
git fetch --prune. On GitHub or GitLab you can also delete a remote branch
from the web UI: the branches page has a trash icon next to each branch.
Can I recover a deleted Git branch?
Usually, yes. Deleting a branch only removes the pointer, not the commits — the commit
objects stay in your repository until git garbage-collects them, typically 30 to 90 days
later. Run git reflog and search for the branch name or the last commit
message you remember to find its SHA, then run
git branch branch-name <SHA> to recreate the branch at that exact
commit. If the reflog entry itself is gone,
git fsck --full --no-reflogs --unreachable --lost-found can locate dangling
commits that no longer have any reference pointing to them.
How do I delete multiple Git branches at once?
List branches already merged into your current branch with
git branch --merged main, filter out the current branch and protected
branches, then pipe the result into git branch -d:
git branch --merged main | grep -v "^[*+]" | grep -vE "^\s*(main|master|develop)$" | xargs -r git branch -d.
Using -d instead of -D keeps the operation safe — any branch
git can't confirm as merged is skipped rather than force-deleted. Note that
squash-merged and rebase-merged branches will not show up under --merged,
since none of their original commits are ancestors of main; check those
with git cherry -v main branch-name instead.