Git, CI/CD & GitOpsVIII · BranchingBranching
Branch deletion and recovery — git branch -d, git branch -D, and the reflog resurrection path
What you'll learn
- Distinguish -d (safe delete, refuses if unmerged) from -D (force delete, orphans the tip)
- Identify the window during which a deleted branch can be resurrected via the reflog
- Resurrect a deleted branch by creating a new branch at the orphaned commit OID
- Recognise the role of git gc in making orphaned commits unrecoverable
- Use git reflog and git fsck --lost-found to locate orphaned commits
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
Deleting a branch is the most dangerous routine operation in
Git. It is dangerous because the failure mode is silent: there
is no “are you sure?” prompt by default, no trash can to recover
from, no undo button. The command removes a single file under
.git/refs/heads/ and the operation is done. The commits that
were only reachable from that file become orphans; they stay in
the object store until garbage collection runs, and they stay
reachable from any clone that previously fetched the branch,
but the local branch is gone. The safety net is the reflog: a
90-day journal of every ref movement, which lets an engineer
who realises their mistake within the window resurrect the
branch by recreating it at the recorded OID.
The safe delete: -d
git branch -d <name> is the safe delete. Git walks the commit
graph from the branch tip and asks: is this commit reachable
from any other ref? If yes, the branch ref can be safely removed
because the commits will continue to be reachable from the other
ref. If no, Git refuses:
git branch -d feature/iam-rotation
# error: The branch 'feature/iam-rotation' is not fully merged.
# If you are sure you want to delete it, run 'git branch -D feature/iam-rotation'.
The reachability check considers every ref: every local branch, every remote-tracking ref, every tag. If any of them can reach the branch tip, the delete proceeds. The check is cheap — Git does not need to walk the entire graph; it stops at the first ref that can reach the tip.
# Safe delete succeeds when the branch is merged
git branch -d bugfix/cert-renewal
# Deleted branch bugfix/cert-renewal (was 4d2c8e0).
# Safe delete refuses when the branch has unique commits
git branch -d feature/iam-rotation
# error: The branch 'feature/iam-rotation' is not fully merged.
The safe delete is the right command for routine cleanup. In
production, automated scripts that prune merged branches should
use -d and inspect the exit code; a non-zero exit indicates
a branch whose tip is not reachable from any other ref, and the
script should log the refusal rather than escalating to -D.
The force delete: -D
git branch -D <name> is the force delete. It removes the
branch ref without consulting the commit graph. Any commits
that were only reachable from this branch become orphans —
they are still in the object store, but they have no ref
pointing at them and are candidates for garbage collection.
# Force delete, no questions asked
git branch -D feature/iam-rotation
# Deleted branch feature/iam-rotation (was 9f3c1d7).
The flag is uppercase -D to signal “this is a sharp tool”. The
same syntax also accepts the long form --delete --force but
-D is the universal abbreviation.
flowchart LR
A["branch tip reachable?"] -->|"yes"| B["git branch -d succeeds"]
A -->|"no"| C["git branch -d refuses"]
C -->|"force"| D["git branch -D succeeds"]
D --> E["tip becomes orphan"]
E -->|"reflog still has it"| F["recoverable for 90 days"]
E -->|"reflog expired + gc ran"| G["unrecoverable"]
The diagram shows the decision flow. -d is the safe path that
refuses unsafe deletes; -D is the path that bypasses the check
and accepts the orphan state.
The reflog as a safety net
The reflog is a per-ref journal of every ref movement. For
branches, every commit that HEAD moved to is recorded; for
remote-tracking refs, every fetch is recorded. The reflog is
the mechanism that makes recovery from an accidental -D
possible.
# View the reflog for HEAD (every commit HEAD has pointed at)
git reflog
# 9f3c1d7 HEAD@{0}: commit: rotate iam keys
# 6f4e5a6 HEAD@{1}: checkout: moving from main to feature/iam-rotation
# 8a3f9d2 HEAD@{2}: commit: bump terraform module
# 4d2c8e0 HEAD@{3}: commit: rotate iam keys (initial)
When a branch is deleted, the OID is still in the reflog for the default 90 days. Any clone that previously fetched the branch will have the same OID in its reflog (or in its remote-tracking ref’s reflog), and the commits are reachable from any clone that fetched them.
The reflog entries for a deleted branch can be found by
walking git reflog and looking for the branch name in the
action description:
# Find the OIDs that any branch tip pointed at in the last 30 days
git reflog --all | grep 'feature/iam-rotation'
# 9f3c1d7 feature/iam-rotation@{0}: commit: rotate iam keys
# 6f4e5a6 feature/iam-rotation@{1}: branch: Created from main
The output includes both the OIDs and the timestamps. The most recent commit’s OID is the tip of the deleted branch.
Resurrecting a deleted branch
Recovery is a two-step process: find the OID, create a new branch at it.
# Step 1: locate the orphaned tip
git reflog --all | grep 'feature/iam-rotation'
# 9f3c1d7 feature/iam-rotation@{0}: commit: rotate iam keys
# Step 2: resurrect the branch at the recorded OID
git branch feature/iam-rotation 9f3c1d7
# Restored branch 'feature/iam-rotation'
# Step 3: restore the upstream configuration
git branch --set-upstream-to=origin/feature/iam-rotation
The resurrected branch is identical to the original at the
moment of deletion: same tip, same history, same commits. The
reflog entry feature/iam-rotation@{0} becomes the new
feature/iam-rotation@{1} (the new creation), and the
deleted branch’s history is preserved in the reflog.
If the reflog has expired (or the work was on a clone whose
reflog has expired), the next-best path is git fsck:
# Find dangling commits (orphans that no ref points at)
git fsck --lost-found
# dangling commit 9f3c1d7
# dangling commit 6f4e5a6
git fsck --lost-found writes the orphaned objects to
.git/lost-found/commit/ and .git/lost-found/other/. The
engineer can read the commit objects and identify the relevant
ones by inspecting their messages with git cat-file -p <oid>.
The role of git gc
Garbage collection is the mechanism that actually frees the
disk space used by orphaned commits. Without git gc, orphans
accumulate forever (they are not deleted just because no ref
points at them). With git gc, orphans are pruned when their
reflog entry has expired.
# Manually run gc with the default prune window
git gc
# Aggressive: prune everything not reachable now
git gc --prune=now
# Conservative: only prune objects older than 30 days
git gc --prune=30.days
The default behaviour is to keep unreachable objects for 2
weeks (the --prune default) and to expire reflog entries
older than 90 days (reachable) or 30 days (unreachable). The
combination means:
- An orphan that was created today is recoverable via the reflog for at least 30 days, possibly 90.
- An orphan that was created 90+ days ago and whose reflog
entry has expired is a candidate for the next
git gc --prune=nowand may already be gone.
In production, repositories with aggressive gc policies (some hosted Git services run gc weekly) have a shorter recovery window than self-hosted repositories with default policies. An engineer who needs a 30-day recovery window should explicitly extend the reflog retention before the window closes.
Production discipline
- Use
-dfor every routine delete. The safe delete is the default;-Dis the exception. The exit code of-dis the ground truth: a non-zero exit means the branch had unique commits and was preserved. - Log orphaned OIDs on
-D. A-Dshould never be silent. The cleanup script should write the deleted tip OIDs to a log so the team can resurrect the branch if the decision is reversed. - Extend the reflog before the window closes. A high-stakes
branch that was deleted in error can be protected by
running
git reflog expire --expire=180.days --allon every clone that may have the work. - Use
git fsck --lost-foundfor post-reflog recovery. When the reflog has expired, the orphaned commits may still be in the object store until the nextgit gc.git fsck --lost-foundfinds them and writes them to a recoverable location. - Document the recovery window in the team’s runbook. “If you deleted a branch by mistake within the last 90 days, use the reflog. After 90 days, recovery is not guaranteed.”
Cross-course references
- Ansible for Production Sysadmins - Part XXXVIII (Review)
uses
git branch -dfor routine cleanup and reserves-Dfor branches that have been explicitly confirmed as abandoned in a team sync. - GitOps with Argo CD - Part IV (AppSources) discusses the recovery window for application source branches: an Argo Application that points at a deleted branch enters a degraded state, and the recovery is to recreate the branch via the reflog of a clone that previously fetched it.
- Terraform for Production Sysadmins - Part XII (State)
draws the analogy between
git branch -Dandterraform state rm: both are operations that orphan an object and require explicit verification before they are run in production.
Quiz
Knowledge check · 4 questions
Q1. An engineer accidentally runs `git branch -D feature/iam-rotation` on a branch with 7 unique commits. How long do they have to resurrect the branch before the commits become unrecoverable?
Q2. `git branch -d <name>` refuses to delete a branch whose tip commit is not reachable from any other ref, while `git branch -D <name>` deletes it unconditionally.
Q3. How do you resurrect a deleted branch using the reflog, in two steps?
Q4. Diagnose a deletion incident where a feature branch with 14 unique commits was force-deleted, and recommend a recovery plan and a process fix.
A nightly cleanup script ran `git branch -D` on every branch that had not been touched in 90 days. The script deleted `feature/iam-rotation`, which had 14 unique commits that were never merged into main. The engineer who owned the branch discovered the deletion 4 days later when they tried to push a follow-up commit. The team's policy is that unreviewed work must be preserved for 180 days. The on-call is investigating.
Passing score: 75%. Answers are checked in this browser.