Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXVII · ReflogRecovery

Recovering from a bad rebase — reflog, ORIG_HEAD, and the cherry-pick recipe

Advanced⏱ ~22 mingit

What you'll learn

  • Explain why a bad rebase loses the rebased branch’s reflog entries but preserves them in HEAD’s reflog
  • Use ORIG_HEAD as a quick alias for the pre-rebase tip during the recovery window
  • Apply the cherry-pick recovery recipe to replay the lost commits onto the post-rebase tip
  • Apply the branch-from-orphan recipe as an alternative that preserves the original OIDs
  • Distinguish the two recovery recipes by their trade-offs (commit OIDs, parent chains, authorship)

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 bad rebase is the second-most-common recovery scenario after the --hard reset. The mental model is the same: the lost commits are orphans in the object store, reachable only via reflog entries. But the reflog behaviour differs in one important way: a rebase rewrites the branch ref but not the branch’s reflog file in the same way as --hard. The rebased branch’s reflog retains its old entries until the retention window expires. Meanwhile, HEAD’s reflog records every step of the rebase, including the moment the branch tip moved. The two reflogs together contain the full set of OIDs the engineer needs to recover.

Why a rebase changes the reflog layout

A rebase is a sequence of operations: read the original tip, replay each commit onto a new base, then rewrite the branch pointer to the new tip. Each replayed commit is a fresh OID (because the parent changed, the content hash changed); the original commits are not modified but are now unreachable from the branch.

git rebase main
# original: feature/iam at A → B → C
# replayed: feature/iam at A' → B' → C'
# A, B, C are now orphans; A', B', C' are the new branch tip

The reflog effects:

  • HEAD’s reflog gains entries for every step of the rebase: the moment HEAD moved to the new base, the moment HEAD moved to each replayed commit. HEAD@{1} (immediately after the rebase) is the post-rebase tip; HEAD@{2} is the last pre-rebase state; entries in between are the intermediate replay steps.
  • The branch’s reflog (refs/heads/feature/iam) records the moment the branch pointer moved from C to C’. The old entries (the moments A, B, C were committed) are retained in the file.
flowchart LR
    A["HEAD reflog"] --> A1["HEAD@{0}: rebase finished"]
    A1 --> A2["HEAD@{1}: checkout, moving to main"]
    A2 --> A3["HEAD@{2}: pre-rebase tip C"]
    B["feature/iam reflog"] --> B1["feature/iam@{0}: rebase finished"]
    B1 --> B2["feature/iam@{1}: commit: C"]
    B2 --> B3["feature/iam@{2}: commit: B"]

Both reflogs retain the pre-rebase OIDs. HEAD’s reflog is the denser of the two because it captures the entire rebase sequence.

ORIG_HEAD as a quick alias

Many operations that move HEAD also write the previous value of HEAD to a special ref called ORIG_HEAD. Rebase is one of them. During the recovery window (before any other operation overwrites ORIG_HEAD), ORIG_HEAD is the pre-rebase tip.

git rebase main
# rebase finished, the engineer realises it was wrong

git rev-parse ORIG_HEAD
# 7e8f9a0  <-- the pre-rebase tip

git reset --hard ORIG_HEAD
# the branch is back to the pre-rebase tip; the rebase is undone

ORIG_HEAD is overwritten by the next operation that moves HEAD. A git status, a git diff, or any other operation that writes to HEAD will replace it. The reflog is the durable record; ORIG_HEAD is a short-lived convenience.

The cherry-pick recovery recipe

When the rebase produced an obviously wrong history but the new history has been merged or pushed, the engineer may want to keep the new history and replay the lost commits on top of it. The recipe is git cherry-pick against the original OIDs.

# Step 1: find the lost OIDs from HEAD's reflog
git reflog -20
# a1b2c3d HEAD@{0}: rebase finished; refs/heads/feature/iam onto main
# 9f3c1d7 HEAD@{1}: cherry-pick C  <-- the replayed C
# ...
# 7e8f9a0 HEAD@{N}: checkout: moving from feature/iam to main  <-- pre-rebase

# Step 2: identify the original commits (the ones before the rebase)
# these appear in HEAD's reflog as the commits that were "replayed" or "picked"
# a typical interactive rebase reflog contains lines like:
#   HEAD@{N}: rebase -i (pick): <commit subject>

# Step 3: cherry-pick the originals onto the current tip
git cherry-pick $ORIGINAL_A $ORIGINAL_B $ORIGINAL_C

# Step 4: verify the replay matches the intent
git log --oneline -10

The cherry-pick produces new commits on top of the current tip. The new commits have new OIDs (because their parents are the new history) but the same diffs as the originals. The originals remain orphans, reachable via the reflog for the retention window.

The branch-from-orphan recovery recipe

When the engineer wants to preserve the original OIDs (for example, because the original commits are referenced by an external system), the recipe is to create a new branch at an orphan OID.

# Step 1: identify the pre-rebase tip from the branch's reflog
git reflog feature/iam -10
# 7e8f9a0 feature/iam@{1}: commit: C  <-- pre-rebase tip
# 4d2c8e0 feature/iam@{2}: commit: B
# 8a3f9d2 feature/iam@{3}: commit: A

# Step 2: create a new branch at the pre-rebase tip
git switch -c feature/iam-recovered 7e8f9a0

# Step 3: verify the new branch has the original commits
git log --oneline -5
# 7e8f9a0 (HEAD -> feature/iam-recovered) C
# 4d2c8e0 B
# 8a3f9d2 A

# Step 4: if the recovered branch should replace the original, force-push
git push --force-with-lease origin feature/iam-recovered:feature/iam

The branch-from-orphan recipe preserves the original OIDs. The cherry-pick recipe produces new OIDs. The choice depends on whether the originals are externally referenced (use branch-from-orphan) or whether the new history is acceptable and only the diffs need to be applied (use cherry-pick).

flowchart LR
    A["bad rebase"] --> B{"which recipe?"}
    B -- "preserve OIDs" --> C["branch-from-orphan\ngit switch -c recovered &lt;oid&gt;"]
    B -- "preserve new history" --> D["cherry-pick originals\ngit cherry-pick &lt;oids&gt;"]
    C --> E["force-push with --force-with-lease"]
    D --> F["merge or push as new commits"]

Choosing between the two recipes

CriterionCherry-pickBranch-from-orphan
Original commit OIDs preservedNo (new OIDs)Yes
Parent chainNew parents (post-rebase)Original parents
Authorship metadataPreserved (cherry-pick default)Preserved
Author datePreservedPreserved
External references to OIDsBrokenIntact
Force-push requiredNo (additive)Yes (rewrite)
Subject lineOriginalOriginal

The decision is: if the originals are referenced by anything external (CI artifacts, signed attestations, signed tags pointing at the originals), use branch-from-orphan. If the new history is fine and only the diffs need to land, use cherry-pick.

Production discipline

  1. Copy the pre-rebase tip into a durable location immediately after the rebase. The OID is recoverable from the reflog for 90 days; the copy is recoverable indefinitely.
  2. Default to cherry-pick when the new history is acceptable. Cherry-pick is additive — it does not require a force-push and does not break collaborators’ clones.
  3. Default to branch-from-orphan when the originals are externally referenced. Signed tags, attestations, and CI artifacts that name the original OIDs require the originals to remain reachable.
  4. Test the recovery on a scratch clone first. Both recipes are easy to apply; both are easy to apply incorrectly. A scratch clone lets the engineer verify the recipe before running it on the real repository.

Cross-course references

  • Git, CI/CD & GitOps — Part XI (Rebasing) — Part XI lesson 4 introduced git rebase --abort as the safe verb during a rebase. This lesson is the recovery for a rebase that has already completed and been pushed.
  • Git, CI/CD & GitOps — Part XV (Reset) — the --hard reset recovery is the same pattern (reflog → OID → reset) applied to a different operation.
  • Linux for Production Sysadmins — Part XXVII (Backup and Recovery) — the two recipes mirror the two restore strategies: additive (cherry-pick is like restoring files into an existing tree) and reconstructive (branch-from-orphan is like restoring an entire snapshot).

Quiz

Knowledge check · 4 questions

  1. Q1. After a bad rebase, where are the original pre-rebase commit OIDs recoverable from for the 90-day default retention window?

  2. Q2. ORIG_HEAD holds the previous value of HEAD only until the next operation that moves HEAD overwrites it; for a recovery that happens hours after the bad rebase, the reflog is the reliable record.

  3. Q3. State the two recovery recipes for a bad rebase and the criterion for choosing between them.

  4. Q4. Recover from a bad rebase that has lost the original commit OIDs, choosing between cherry-pick and branch-from-orphan based on whether the originals are externally referenced.

    An engineer rebased `feature/iam-rotation` onto `main` and force-pushed. Two hours later, the team discovers that the rebase replayed three commits that should not have been replayed (a debug commit, an accidentally-squashed fix, and a commit that depended on a state already in main). The three commits are referenced by signed CI artifacts from the original branch. The engineer needs to recover.

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