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.

Same word, three different refs Local Branch .git/refs/heads/ on your machine Remote-Tracking .git/refs/remotes/origin/ local cache of remote Remote Server Branch refs/heads/ on GitHub/GitLab the actual server copy git branch -d / -D deletes only this ref git branch -d -r or git fetch --prune git push origin --delete branch-name Deleting one of these never touches the other two.
A branch's local ref, remote-tracking ref, and remote server ref are three separate objects — deleting one never touches the other two.

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.

-d checks merge status. -D does not. git branch -d (safe) git branch -D (force) feature-old tip reachable from HEAD feature-x tip NOT reachable from HEAD Deleted — fully merged nothing lost Deleted anyway — no check unmerged commits orphaned If -d refuses, that refusal is the safety check working as intended.
-d walks the commit graph before deleting; -D skips straight to deletion regardless of what the graph shows.

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.

Two ways to push nothing — same delete result Normal push — content flows into the ref local-branch your commits git push origin src:dest origin/main commits land here Delete push — nothing flows in, so git removes the ref (nothing) --delete flag (nothing) :branch-name form origin/branch-name ✕ removed from remote git push origin --delete branch-name git push origin :branch-name
A normal push writes a source into a destination ref. Both --delete and the :branch-name refspec push an empty source — git reads that as "remove this ref."

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.

Deleting the server branch leaves local copies behind Local Branch .git/refs/heads/ unaffected Remote-Tracking Ref origin/feature-x stale — still here Remote Server Branch refs/heads/ on GitHub ✕ deleted git fetch --prune removes the stale ref locally Local branch and remote-tracking ref don't know the server branch is gone. Only fetch --prune (or -d -r) removes the stale local copy.
The server branch, your local branch, and your remote-tracking ref are three separate objects — deleting the server copy leaves the other two stale until you prune.
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 from main
  • grep -v '^[*+]' — drops the current branch (marked *) and any branch checked out in another worktree (marked +), since those can't be deleted anyway
  • grep -vE '^\s*(main|master|develop)$' — excludes your protected long-lived branches by name, so you never accidentally target them
  • xargs -r git branch -d — runs git branch -d once per remaining name; -r skips 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.

Three reasons your delete gets blocked 1. Current Branch 2. Another Worktree 3. Protected on Server $ git branch -d feature-x error: checked out right here (HEAD) $ git branch -d feature-x error: checked out at another worktree path $ git push --delete main remote: protected branch update failed Fix switch off it, then -d Fix worktree remove <path> Fix change the protection rule Each block protects something — your HEAD, another worktree, or the server's rules.
Three different guardrails, three different fixes. None of them is a git bug — each is refusing to leave something in a broken state.

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.

Recovering a deleted branch before gc prunes it 1 Branch deleted git branch -d / -D commit object stays in .git/objects 2 Find the SHA git reflog search HEAD@{n} for the branch name 3 Branch recreated git branch name SHA pointer restored history intact deadline: gc prunes unreachable commits after ~30–90 days
Deleting a branch only removes the pointer — the commit stays reachable via reflog until git's garbage collector prunes it, typically 30 to 90 days later.

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 -a before 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/file and git show main:path/to/file for 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 full git diff output 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.