Homeβ€Ί DevOpsβ€Ί Git Delete Local and Remote Branch: The Complete Guide

Git Delete Local and Remote Branch: The Complete Guide

Where developers are forged. Β· Structured learning Β· Free forever.
πŸ“ Part of: Git β†’ Topic 13 of 19
Learn exactly how to delete local and remote Git branches with git branch -d, git branch -D, and git push origin --delete.
πŸ§‘β€πŸ’» Beginner-friendly β€” no prior DevOps experience needed
In this tutorial, you'll learn:
  • git branch -d is safe β€” it checks for unmerged commits. git branch -D is force delete β€” use it after squash-merges or to abandon work, never blindly.
  • git push origin --delete removes the remote branch. git fetch --prune cleans up your local tracking refs. You need both for a clean slate.
  • Set git config --global fetch.prune true once and never think about stale remote-tracking refs again.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
⚑ Quick Answer
A Git branch is like a sticky note on a specific commit β€” deleting it just removes the sticky note, not the commits underneath. Git gives you two delete commands for good reason: the safe one checks you've merged everything first, and the nuclear one doesn't. Knowing which to reach for and when is the difference between a clean repo and a support ticket at 11pm.

Every developer has stared at a repo with 47 branches named 'fix', 'fix2', 'test-thing', 'adi-temp', and 'FINAL_PLEASE'. Branch hygiene is one of those disciplines that seems trivial until it isn't β€” until a junior dev checks out the wrong branch, or a merge pipeline fails because it can't determine the base branch from the noise.

I spent three years as the sole platform engineer for a fintech startup where we ran 8-week feature sprints with 12 developers all branching off main simultaneously. We had a hard rule: the moment a PR merged, the branch got deleted β€” both locally and remotely β€” by the developer who opened it. Not by a cleanup cron job. Not by a script. By the person who created it, immediately. That discipline kept our branch list under 20 entries at all times and made git log --oneline actually readable.

This guide covers everything that matters in production: safe vs force delete, cleaning up stale remote-tracking refs, the difference between the remote branch and your local tracking reference, and the one-liner that nukes all branches you've already merged into main.

git branch -d vs git branch -D: The Critical Difference

Git gives you two local branch delete commands and the difference between them has saved and destroyed data in equal measure.

git branch -d <branch> is the safe delete. Git checks whether the branch tip is reachable from HEAD or from the branch it's supposed to be tracking upstream. If you have commits on that branch that aren't merged anywhere, Git refuses with: error: The branch 'feature/payment-retry' is not fully merged. That error message is Git doing you a favour.

git branch -D <branch> is the force delete β€” equivalent to --delete --force. It doesn't check anything. It just removes the ref. The commits still exist in the object store and are reachable via git reflog for 30 days by default, but the branch pointer is gone. This is the command you reach for when you've squash-merged a PR (the branch tip isn't reachable via normal ancestry) or when you genuinely want to abandon experimental work.

The mental model that's kept me out of trouble: -d is for branches you've properly merged via a merge commit. -D is for branches you've squash-merged, rebased, or are deliberately abandoning. Never use -D unless you can answer 'why does Git think this isn't merged?' β€” because sometimes the correct answer is 'I made a mistake and I'm about to lose work.'

git_delete_branch.sh Β· BASH
12345678910111213141516
# Safe delete β€” refuses if branch has unmerged commits
git branch -d feature/payment-retry

# Force delete β€” no safety check (use after squash-merge or to abandon work)
git branch -D feature/payment-retry

# Check what's merged into main before deleting
git branch --merged main
# Output: branches whose tips ARE reachable from main (safe to -d)

git branch --no-merged main
# Output: branches with commits NOT yet in main (need -D or review first)

# Practical: delete all local branches already merged into main
# (excluding main itself)
git branch --merged main | grep -v '\* main' | grep -v main | xargs git branch -d
β–Ά Output
Deleted branch feature/payment-retry (was a3f9c2e).

# --merged output example:
feature/add-retry-logic
feature/fix-null-check
* main

# --no-merged output example:
feature/wip-refactor
hotfix/urgent-null-ptr

Deleting Remote Branches with git push

Deleting a remote branch and deleting your local tracking reference for it are two separate operations. Most developers conflate them and then wonder why git branch -r still shows a deleted branch.

git push origin --delete <branch> sends a delete request to the remote. The remote branch on GitHub/GitLab/Bitbucket is gone. Other developers who fetch will get a notice that the branch has been deleted. This is the right command for post-merge cleanup.

The older syntax git push origin :<branch> (note the colon with no local branch name before it) does the same thing β€” it pushes 'nothing' to the remote branch reference, which deletes it. You'll see this in older scripts and blog posts. It works, but --delete is clearer and should be preferred in anything you write today.

One thing that caught me out early on: after running git push origin --delete feature/payment-retry, running git branch -r on a colleague's machine might still show origin/feature/payment-retry. That's their local remote-tracking reference β€” a cached copy of what their Git client last saw on the remote. It doesn't update automatically. They need to run git fetch --prune or configure pruning globally.

git_delete_remote_branch.sh Β· BASH
1234567891011121314151617
# Delete the remote branch (what lives on GitHub/GitLab/Bitbucket)
git push origin --delete feature/payment-retry

# Older syntax β€” same effect, less readable
git push origin :feature/payment-retry

# After remote delete: clean up YOUR stale remote-tracking refs
git fetch --prune
# This removes any local refs to remote branches that no longer exist
# e.g. removes origin/feature/payment-retry from your local tracking list

# Do both in one step (delete remote + prune all stale refs)
git push origin --delete feature/payment-retry && git fetch --prune

# Configure auto-prune so you never have stale refs again
git config --global fetch.prune true
# Now every git fetch and git pull automatically prunes dead tracking refs
β–Ά Output
To github.com:io/thecodeforge/payments-service.git
- [deleted] feature/payment-retry

# After git fetch --prune:
From github.com:io/thecodeforge/payments-service.git
- [deleted] (none) -> origin/feature/payment-retry

Delete Local and Remote Branch in One Workflow

In practice you almost always want to delete both the local and remote branch together. Here's the sequence I've drilled into every team I've worked with β€” it takes 10 seconds and leaves the repo clean.

One common confusion: you can't delete the branch you're currently on. Git will tell you error: Cannot delete branch 'feature/payment-retry' checked out at '/home/adi/payments-service'. Switch to main first, then delete.

Another gotcha: if you renamed a branch mid-development (git branch -m old-name new-name), the old remote branch name might still exist. Deleting the new-named branch locally does nothing to the old remote ref. You need to explicitly delete the old remote branch name too.

git_delete_full_workflow.sh Β· BASH
123456789101112131415161718192021222324
# Full cleanup workflow β€” run after a PR is merged

# Step 1: Switch away from the branch you want to delete
git checkout main
git pull origin main   # Get latest main before cleanup

# Step 2: Delete local branch
git branch -d feature/payment-retry
# Use -D if you squash-merged (the tip won't be reachable via ancestry)

# Step 3: Delete the remote branch
git push origin --delete feature/payment-retry

# Step 4: Prune stale remote-tracking refs on your machine
git fetch --prune

# --- ONE-LINER for the muscle-memory inclined ---
git checkout main && git pull && git branch -d feature/payment-retry && git push origin --delete feature/payment-retry

# --- NUCLEAR: delete all local branches merged into main ---
# Review the list first before running
git branch --merged main | grep -vE '^\*|^\s*main$|^\s*master$|^\s*develop$'
# If the list looks right, pipe to delete:
git branch --merged main | grep -vE '^\*|^\s*main$|^\s*master$|^\s*develop$' | xargs -r git branch -d
β–Ά Output
Switched to branch 'main'
Already up to date.
Deleted branch feature/payment-retry (was a3f9c2e).
To github.com:io/thecodeforge/payments-service.git
- [deleted] feature/payment-retry
⚠️
Never delete main, master, or develop remotelyThe --delete flag has no branch protection by itself β€” that's your remote's job. If you're running the 'delete all merged' one-liner in a script, always explicitly exclude main, master, develop, and any release branches with grep -vE. I've seen a staging pipeline delete its own develop branch with a naive cleanup script. The fix was 30 seconds with git reflog, but the panic was real.

Recovering a Deleted Branch with git reflog

You deleted a branch and immediately knew it was a mistake. Here's the thing β€” Git almost certainly still has those commits. The branch ref is gone but the commit objects remain in the local object store for 30 days (controlled by gc.reflogExpire). git reflog is your time machine.

git reflog shows a log of every time HEAD moved β€” including checkouts, commits, merges, and resets. Each entry has a short hash. Find the commit that was your branch tip before you deleted it and create a new branch pointing there.

This only works if you haven't run git gc aggressively and the 30-day window hasn't passed. For remote branch recovery after git push --delete, you need someone else on the team who still has the branch locally, or a backup from your remote (GitHub keeps deleted branch commits accessible for a period but makes it hard to recover β€” better to ask a teammate first).

git_recover_deleted_branch.sh Β· BASH
123456789101112131415161718192021
# You deleted feature/payment-retry by mistake. Recover it:

# Step 1: Find the commit that was the branch tip
git reflog
# Look for the last checkout of the deleted branch or the last commit on it
# Output:
# a3f9c2e HEAD@{0}: checkout: moving from feature/payment-retry to main
# 7b2d4f1 HEAD@{1}: commit: Add retry logic with exponential backoff
# 9c3e8a2 HEAD@{2}: commit: Add PaymentRetryService skeleton

# Step 2: Recreate the branch at the commit you want
git branch feature/payment-retry a3f9c2e
# Or use the reflog shorthand:
git branch feature/payment-retry HEAD@{1}

# Step 3: Verify you got the right commits
git log feature/payment-retry --oneline -5

# If the branch was deleted remotely and you still have it locally:
git push origin feature/payment-retry
# Recreates the remote branch from your local copy
β–Ά Output
# git reflog output (abbreviated):
a3f9c2e HEAD@{0}: checkout: moving from feature/payment-retry to main
7b2d4f1 HEAD@{1}: commit: Add retry logic with exponential backoff

# Recovery:
Branch 'feature/payment-retry' set up to track remote branch 'feature/payment-retry' from 'origin'.
CommandDeletesSafety CheckWhen to Use
git branch -dLocal branch refYes β€” checks if mergedNormal post-merge cleanup
git branch -DLocal branch refNo β€” force deleteAfter squash-merge or abandon work
git push origin --deleteRemote branchNo (use branch protection)After merging PR on remote
git fetch --pruneStale remote-tracking refsN/AAfter any remote branch deletion
git config fetch.prune trueAuto-prunes on every fetchN/ASet once globally β€” always recommended

🎯 Key Takeaways

  • git branch -d is safe β€” it checks for unmerged commits. git branch -D is force delete β€” use it after squash-merges or to abandon work, never blindly.
  • git push origin --delete removes the remote branch. git fetch --prune cleans up your local tracking refs. You need both for a clean slate.
  • Set git config --global fetch.prune true once and never think about stale remote-tracking refs again.
  • Deleted a branch by mistake? git reflog shows every HEAD movement for the last 30 days β€” find the commit hash and recreate the branch pointing to it.
  • Never run bulk-delete scripts without reviewing the list first and always explicitly excluding main, master, develop, and release branches.

⚠ Common Mistakes to Avoid

  • βœ•Using -D when -d fails without asking why β€” the merge check failing is Git telling you there are commits on that branch not in any other branch. Always run git log <branch> --not main first to see what you'd lose.
  • βœ•Deleting only the remote branch and leaving the local β€” or vice versa β€” resulting in a repo state where git branch -a shows ghosts of deleted branches.
  • βœ•Not setting fetch.prune true globally, resulting in remote-tracking refs that outlast the actual remote branches by months, confusing git branch -r output.
  • βœ•Running the 'delete all merged' one-liner without first reviewing the list β€” always pipe through grep -vE to exclude main, master, develop, and release/* branches.
  • βœ•Trying to delete the branch you're currently on β€” switch to main first, always.

Interview Questions on This Topic

  • QA developer on your team force-deleted a branch that wasn't fully merged. How would you recover the work?
  • QExplain the difference between a remote branch and a remote-tracking reference in Git.
  • QYour git branch -r output shows 30 branches that were deleted on the remote months ago. How do you fix this and prevent it in future?
  • QWhen would you use git branch -D instead of git branch -d, and what are the risks?

Frequently Asked Questions

What is the difference between git branch -d and git branch -D?

git branch -d is a safe delete that checks whether the branch has been fully merged before deleting it. If the branch has commits not reachable from HEAD or its upstream, Git refuses. git branch -D is a force delete that skips the check entirely. Use -d for normal post-merge cleanup and -D after squash-merges (where the tip isn't reachable via ancestry) or when deliberately abandoning work.

How do I delete both local and remote branch at the same time?

There's no single command for both, but the two-step sequence is quick: git branch -d feature/my-branch to delete locally, then git push origin --delete feature/my-branch to delete on the remote. Follow up with git fetch --prune to clean up stale remote-tracking refs on your machine.

Why does git branch -r still show a branch I deleted remotely?

git branch -r shows your local cache of remote branches, not the live state of the remote. Run git fetch --prune to sync the cache and remove references to branches that no longer exist on the remote. Set git config --global fetch.prune true to auto-prune on every fetch.

Can I recover a branch I accidentally deleted?

Yes, in most cases. Run git reflog to see a log of all HEAD movements including the last checkout or commit on the deleted branch. Find the commit hash that was the branch tip and run git branch <branch-name> <hash> to recreate it. This works for 30 days after deletion as long as you haven't run aggressive garbage collection.

πŸ”₯
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← PreviousGit Checkout -b: Creating and Switching BranchesNext β†’Git Squash Commits: Combine Multiple Commits into One
Forged with πŸ”₯ at TheCodeForge.io β€” Where Developers Are Forged