Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXVIII · Git RecoveryRecovery

Recovering a deleted branch — the reflog is the recovery path

Advanced⏱ ~24 min🧪 Lab requiredgit

What you'll learn

  • Apply the reflog recipe to recover a branch deleted by `git branch -D` or `git push --delete`
  • Identify the OID at `HEAD@{1}` (or the appropriate reflog entry) as the recovery target
  • Recognise the 90-day retention boundary and the recovery paths past the window (other clones, fsck, remote refs)
  • Distinguish recovery from the local reflog (time-limited) from recovery from a remote ref pull/N (durable)

Prerequisites

Practice

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

Not yet marked complete on this device.

A deleted branch is the most common recovery scenario and the most reliably recoverable. The deletion is a ref rewrite — the branch pointer is removed or replaced — but the commits the branch pointed at are still in the object store and still reachable via the reflog on any clone that fetched the branch before the deletion. The recovery is mechanical: read the reflog, find the tip OID, recreate the branch at the OID, push the recovered branch to the remote.

The four-step discipline from the previous lesson applies in exactly this order. The first command is git reflog. The recovery command is git branch <name> <oid>.

The deletion is a ref rewrite

git branch -D <name> and git push --delete origin <name> delete the branch pointer. The commits are not deleted; they are orphaned. The commits remain in the object store as unreachable objects until the next git gc --prune=now on every clone that has them, and they remain reachable via the reflog on any clone that has the reflog entry recording the branch tip.

flowchart LR
    A["feature/iam-rotation at 8a3f9d2"] --> B["git branch -D feature/iam-rotation"]
    B --> C["branch pointer removed"]
    B --> D["object store: 8a3f9d2 still present"]
    B --> E["reflog: HEAD@{1} = 8a3f9d2"]
    C --> F["git branch recovered 8a3f9d2"]
    F --> G["branch pointer restored"]

The fork in the diagram is the recovery path. The branch is deleted in the branch namespace, but the OID is preserved in two places: the object store (8a3f9d2 still exists) and the reflog (HEAD@{1} names 8a3f9d2). The recovery is to read the reflog, copy the OID, and run git branch <name> <oid>. The branch pointer is restored; the orphan is reachable again.

The recovery recipe

# Step 1: locate the deleted branch tip
git reflog -20
# a1b2c3d HEAD@{0}: checkout: moving from feature/iam-rotation to main
# 8a3f9d2 HEAD@{1}: commit: rotate IAM credentials            <-- the tip
# 3e4f5a6 HEAD@{2}: commit: break out IAM role into module
# 7c8d9e0 HEAD@{3}: commit: scaffold feature/iam-rotation

# Step 2: copy the OID to a durable location
DELETED_TIP=$(git reflog -1 HEAD@{1} | awk '{print $1}')
echo "Recovered tip: $DELETED_TIP"

# Step 3: verify the OID is the right one
git show --stat $DELETED_TIP
# confirms the commit before any further action

# Step 4: recreate the branch
git branch feature/iam-rotation $DELETED_TIP

# Step 5: push the recovered branch to the remote
git push origin feature/iam-rotation

The recipe has five steps because the recovery has to be verified twice — once at the OID level (git show) and once at the branch level (git branch) — before the push. The push is the propagation step; the local branch is recovered before the remote is updated.

The reflog output is the clone’s history of HEAD updates, not the per-branch history. The clue is the commit: message: the entry immediately before the checkout: moving from feature/iam-rotation line is the most recent commit on the deleted branch — its tip.

The 90-day retention boundary

The recovery recipe works only while the reflog entry exists. The retention is configured by two settings:

git config gc.reflogExpire
# default: 90.days (reachable entries)
git config gc.reflogExpireUnreachable
# default: 30.days (unreachable entries)

The deleted branch’s tip is reachable via the reflog entry for 90 days by default. After 90 days, the reflog entry expires; the tip becomes an unreachable object; the entry is now governed by the 30-day unreachable expiry. After 30 days without the reflog entry, the tip is a prune candidate and the next git gc --prune=now removes it.

# Diagnostic: how much time is left on the reflog entry?
git reflog --expire=never HEAD@{1}
# if this errors, the entry has already expired

The 90-day window is the local-clone window. A clone that fetched the branch before the deletion has its own 90-day window. The recovery window is the union of all clones’ windows.

Recovery past the window

Past the reflog window, the recovery path is in order of preference:

  1. Other clones. Any clone that fetched the branch before the deletion may still have the OID in its reflog. The recovery is to obtain the OID from that clone’s reflog and push a new branch from it.
  2. Object store scan. git fsck --unreachable --no-reflogs on a clone that has the objects but no reflog entry lists the orphan commits. Each OID reported is recoverable with git branch recovered &lt;oid&gt;.
  3. Remote refs. The original pull request may have a ref in the refs/pull/&lt;n&gt;/head namespace on the remote. Most forges keep these refs for the lifetime of the PR. The recovery is to fetch the ref and create a branch from it.
  4. CI artefacts and backups. The commit may be referenced by an older CI artifact, a release tag, a backup archive, or a developer’s local backup. The OID is the unique identifier; if a backup has the bytes, the bytes are recoverable.
# Path 1: another clone
REMOTE_OID=$(ssh ops@git-mirror "git -C /srv/git/repo.git reflog -1 HEAD@{1}" | awk '{print $1}')
git fetch ssh://ops@git-mirror/srv/git/repo.git $REMOTE_OID
git branch feature/iam-rotation $REMOTE_OID

# Path 2: fsck on the local clone
git fsck --unreachable --no-reflogs
# dangling commit 8a3f9d2...
git branch feature/iam-rotation 8a3f9d2

# Path 3: forge PR ref
git fetch origin refs/pull/4821/head:pr-4821
git branch feature/iam-rotation pr-4821

Recovery when the branch was force-pushed

If the branch was deleted by git push --delete and the remote had it, the local reflog still has the pre-deletion tip. The recovery is the same recipe: read the reflog, recreate the branch locally, push it back. The remote will accept the push because the branch no longer exists.

If the branch was force-pushed (the branch’s tip was rewritten, not deleted), the recovery is different — see Lesson XVIII-05 on recovering a commit after a shared rebase.

Production discipline

  1. Never delete a branch without first reading the reflog. The discipline is: git reflog -10 before git branch -D. If the OID is captured, the deletion is recoverable; if the OID is not captured, the deletion is forever.
  2. Always copy the OID to a durable location before deleting. A PR comment, a ticket, a chat message — any location that survives the 90-day reflog window. The OID alone is the recovery target.
  3. Prefer the forge’s PR ref for long-term backup. Most forges keep refs/pull/&lt;n&gt;/head for the lifetime of the PR. The ref is a durable reference to the branch tip; the recovery is git fetch origin refs/pull/&lt;n&gt;/head.
  4. Document the recovery in the runbook. The recipe above is a one-page runbook entry; paste it into the team’s on-call documentation.

Cross-course references

  • Git, CI/CD & GitOps — Part XVII (Reflog) — the prerequisite for this lesson; the reflog’s location and scope.
  • Git, CI/CD & GitOps — Part VIII (Branches) — the branch lifecycle; the deletion is the terminal event.
  • GitOps with Argo CD — Part VI (MergeStrategies) — the GitOps controller’s clone and its gc.reflogExpire never setting; the controller’s recovery window is effectively infinite so that a deleted branch on the remote is recoverable from the controller’s clone.

Quiz

Knowledge check · 4 questions

  1. Q1. An engineer has just run `git branch -D feature/iam-rotation` and the branch was pushed yesterday. What is the precise sequence to recover the branch?

  2. Q2. After `git branch -D feature/x`, the commits the branch pointed at remain in the object store as orphan objects and are recoverable via the reflog for the 90-day default retention window.

  3. Q3. Name the four recovery paths past the 90-day reflog window, in order of preference.

  4. Q4. Walk the recovery for an engineer who deleted `feature/iam-rotation` and the deletion is 91 days old.

    An engineer deleted `feature/iam-rotation` 91 days ago in a clean-up. The branch was the source of a pull request that was merged. The merge commit is on `main`. The engineer needs to recover the branch to investigate a regression introduced by the merge. The local clone has run `git gc --prune=now` weekly; the reflog entry has expired.

Passing score: 75%. Answers are checked in this browser.