How to Permanently Delete a Git Branch (Locally and Remotely)

A clear, no-nonsense guide to deleting Git branches for good, locally, on GitHub/GitLab, and from every teammate's machine, with the exact commands.

You merged the feature, the PR is closed, and you delete the branch. A week later it's back in your branch list like nothing happened. If that's why you're here, you're not losing your mind. Git branches have a habit of resurrecting themselves, and there's a specific reason for it that almost nobody explains properly.

delete git branch

This guide covers the actual deletion commands, why a branch sometimes refuses to die, and the one step most tutorials skip that lets it come back from the dead on every machine that ever pulled it.

The short version, if you just need the commands

Delete a local branch:

git branch -d branch-name

Delete a remote branch (GitHub, GitLab, Bitbucket, anywhere):

git push origin --delete branch-name

If that's all you needed, you're done. If you want to know why your "deleted" branch keeps reappearing, keep reading.

Deleting a branch locally

Git gives you two ways to remove a local branch, and the difference between them matters more than most people realize.

The safe way: -d

git branch -d branch-name

The lowercase -d flag stands for --delete, and it comes with a built-in safety check. Git will only delete the branch if its commits have already been merged into your current branch. If they haven't, you get a message like this:

error: the branch 'feature-login' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature-login'

That's not a bug, it's Git refusing to let you lose work. If you're staring at this error and your changes really are saved somewhere else, like a different branch or an already-merged commit, the fix is the next command. If they're not saved anywhere, stop and check before you go further.

The forceful way: -D

git branch -D branch-name

Capital -D is shorthand for --delete --force. It skips the merge check entirely and deletes the branch regardless of whether its work exists anywhere else. Use this when you're certain the branch is disposable, like an experiment that went nowhere, a branch you squash-merged (which technically looks "unmerged" to Git even though the work is safely on main), or a duplicate you created by accident.

One detail that trips people up: you can't delete the branch you're currently standing on. Git will block it because deleting your own checkout would leave you in a weird limbo state. Switch to another branch first:

git checkout main
git branch -D branch-name

Deleting a branch on the remote (GitHub, GitLab, Bitbucket)

Here's the part that confuses people the most: deleting a branch on your machine does absolutely nothing to the copy sitting on GitHub or GitLab. They're two separate references, and Git treats them that way on purpose. You have to remove each one.

git push origin --delete branch-name

This tells the remote named origin to remove the branch. There's also a shorthand using a colon, which does the exact same thing and shows up often in older Stack Overflow answers:

git push origin :branch-name

Both commands work identically. The --delete version is just easier to read six months later when you're scrolling through your shell history wondering what that colon command actually did.

If your team enforces protected branches, GitHub or GitLab might block this push with a permissions error. That's expected, protected branches are meant to resist exactly this kind of deletion from the command line. In that case you'll need to remove the protection rule first or delete the branch through the web UI, where an admin can override it.

Why a deleted branch keeps coming back (the part everyone skips)

This is the actual reason you're probably reading this article, so let's get into it properly.

When you clone a repository or fetch from it, Git doesn't just download the branches that exist right now. It creates and stores local bookmarks, called remote-tracking branches, for every branch it sees on the remote at that moment. These live under a separate namespace: refs/remotes/origin/branch-name. You can see them with:

git branch -r

Here's the problem. When someone deletes feature-login from GitHub, your local copy has no idea that happened. Your machine still has a remote-tracking reference for it from the last time you fetched, and as far as your Git is concerned, that branch still exists on the remote. So when you run git branch -a or pull up a list in your Git client, there it is, looking very much alive.

This is the entire mystery behind "I deleted this branch and it's back." It never actually came back. Your local Git just never got the memo that it left.

The fix: pruning

The command that clears out these stale references is:

git fetch --prune

Or the shorter, more commonly typed version:

git fetch -p

This tells Git to check the remote, compare it against your local remote-tracking branches, and delete any tracking references for branches that no longer exist on the other end. Run this and the "zombie" branch finally disappears from your branch list, because Git now knows the truth.

If you'd rather not run this manually every time, you can make pruning automatic on every fetch and pull:

git config --global fetch.prune true

Set that once and you'll stop seeing deleted branches linger in your local list altogether. This is genuinely one of the most useful small settings in Git that almost nobody turns on by default.

Cleaning up after a teammate deletes a branch

This is the same root cause showing up on a different machine. If your coworker deletes feature-login from GitHub, your local repo and theirs don't update automatically. Everyone who ever fetched that branch is carrying their own stale tracking reference until they run the prune command themselves.

If you're on a small team and want to clean every local clone in one motion, you can't push that from your end. Each person needs to run the prune locally. What you can do is build it into a habit, for example by running git fetch --prune as part of your daily pull routine, or set the global config above and tell your team to do the same. That single setting solves this for good.

Deleting multiple branches at once

If you've got a pile of old feature branches cluttering your local list, deleting them one at a time is tedious. A faster approach, once you've confirmed they're all safe to remove:

git branch --merged main | grep -v "main" | xargs git branch -d

This lists every local branch already merged into main, filters out main itself so you don't accidentally delete your own base branch, and deletes the rest. Because it uses -d rather than -D, Git still applies its merge safety check, so nothing with unmerged work gets touched.

For remote cleanup at scale, GitHub and GitLab both have repository settings to automatically delete branches once their pull request or merge request is merged. Turning that setting on saves you from ever needing this cleanup pass in the first place.

Recovering a branch you deleted by mistake

Worth knowing before you panic: a deleted branch isn't gone the instant you delete it. Branches are just pointers to a commit, and the commit itself sticks around in Git's internal storage for a while even after the pointer is removed.

When you delete a local branch, Git tells you the commit hash it pointed to:

Deleted branch feature-login (was a1b2c3d).

Copy that hash before you close the terminal. If you didn't, you can usually find it with:

git reflog

The reflog tracks where your branch tips have pointed recently. Find the commit, then bring the branch back with:

git branch recovered-branch a1b2c3d

This works for a limited window, typically around 30 to 90 days depending on your Git garbage collection settings, after which unreferenced commits get cleaned up for real. If it's been an hour or a day, you're almost certainly fine. If it's been three months, don't count on it.

For a branch deleted on GitHub specifically, there's an easier path: GitHub keeps a record on the closed pull request page with a "Restore branch" button, as long as the PR associated with that branch is still there. That's usually faster than digging through reflogs.

Quick reference

GoalCommand
Delete local branch (safe)git branch -d branch-name
Delete local branch (force)git branch -D branch-name
Delete remote branchgit push origin --delete branch-name
Clear stale remote-tracking refsgit fetch --prune
Auto-prune on every fetchgit config --global fetch.prune true
Recover a deleted branchgit branch name <commit-hash> (find hash via git reflog)

Branch deletion in Git is simple once you separate the two things actually happening: removing the branch itself, and clearing the stale reference your machine kept of it. Miss the second part and you'll spend years quietly confused about branches that won't stay dead. Now you know exactly why, and exactly what to type to fix it.

Post a Comment