Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXVIII · Git RecoveryRecovery

Recovering a dropped stash — git stash list, the reflog, and the .git/logs/refs/stash file

Advanced⏱ ~22 mingit

What you'll learn

  • Apply the two recovery paths for a dropped stash (git stash list, .git/logs/refs/stash) and the recovery recipe (git stash apply, git stash branch <name> <oid>)
  • Identify the OID of a dropped stash from the reflog even after `git stash list` shows nothing
  • Recognise why a dropped stash is recoverable longer than a deleted branch (the stash log is a separate reflog with its own retention)
  • Distinguish `git stash apply <stash@{n}>` (replays the stash on the current branch) from `git stash branch &lt;name&gt; &lt;oid&gt;` (creates a branch from the stash)

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

Not yet marked complete on this device.

A dropped stash is the most surprising recovery scenario because the engineer often does not know the dropped stash is recoverable. git stash list shows nothing; the working tree is missing the staged and unstaged changes that were in the stash; the engineer believes the work is gone. The work is in the stash log — .git/logs/refs/stash — and is recoverable via the same reflog reading that recovers a deleted branch.

The mechanism is the same: the stash is a ref, the ref has a log, the log records the previous value of the ref. The drop is a ref rewrite; the previous value is preserved in the log.

The stash is a ref, not a clipboard

The most common misconception about git stash is that the stashed changes are like a clipboard: copy, paste, discard. The mental model is wrong. The stash is a ref — refs/stash — that points at a commit containing the working tree state at the moment of git stash. The git stash list output shows the entries in the reflog of refs/stash, not in a separate list.

flowchart LR
    A["working tree + index"] -->|"git stash"| B["commit on refs/stash"]
    B --> C["stash@{0} = latest"]
    B --> D["stash@{1} = previous"]
    B --> E["stash@{2} = older"]
    C -->|"git stash drop stash@{0}"| F["stash@{1} promoted to stash@{0}"]
    F --> G["stash@{0} removed"]
    F --> H["dropped OID in .git/logs/refs/stash"]
    H -->|"git stash apply"| I["replay on current branch"]
    H -->|"git stash branch new &lt;oid&gt;"| J["create branch from stash"]

The fork in the diagram is the recovery path. The drop removes the entry from git stash list but the reflog of refs/stash records the drop. The OID is preserved in the reflog and is recoverable via git reflog show stash.

The two recovery paths

A dropped stash is recoverable from two places, in order of preference:

  1. git stash list. If the drop was the most recent action and no other stash operations have happened, the older entries are still in the list. The recovery is git stash apply <stash@{n}> or git stash pop <stash@{n}>.
  2. .git/logs/refs/stash. If the drop was the last stash operation and the list is empty, the log file contains the OID of the dropped commit. The recovery is read the log, identify the OID, and either git stash apply &lt;oid&gt; or git stash branch &lt;name&gt; &lt;oid&gt;.
# Path 1: stash list still has the entry
git stash list
# stash@{0}: WIP on main: 7e8f9a0 bump ansible collection version
# stash@{1}: WIP on feature/iam-rotation: 8a3f9d2 rotate IAM credentials
git stash apply stash@{1}    # replay on the current branch
# or
git stash pop stash@{1}      # apply and drop

# Path 2: stash list is empty after the drop
git reflog show stash
# a1b2c3d stash@{0}: WIP on main, WIP on main: 7e8f9a0 ...     <-- still here
# 8a3f9d2 stash@{1}: WIP on feature/iam-rotation: ...          <-- the dropped
git stash apply 8a3f9d2
# or
git stash branch feature/iam-rotation-recovery 8a3f9d2

The git reflog show stash invocation is the same recipe as git reflog for HEAD, restricted to the refs/stash reflog. The stash@{n} notation is shorthand for the same OID.

git stash apply versus git stash branch

The two recovery commands have different shapes:

flowchart LR
    A["dropped stash OID"] --> B["git stash apply OID"]
    A --> C["git stash branch name OID"]
    B --> D["replay on current branch"]
    B --> E["stash not removed from log"]
    C --> F["create branch at OID"]
    C --> G["stash removed from log"]
    C --> H["branch checked out"]
  • git stash apply &lt;oid&gt; replays the stash commit on the current branch. The stash is not removed from the log; the working tree gains the changes. The recovery is additive on the working tree.
  • git stash branch &lt;name&gt; &lt;oid&gt; creates a new branch at the stash OID and checks it out. The stash is removed from the log. The recovery is a branch from the stash.

The branch form is preferred when the engineer wants to preserve the stash as a recoverable branch. The apply form is preferred when the engineer wants to merge the stash into the current branch.

The recovery recipe

# Step 1: locate the dropped stash
git reflog show stash -20
# a1b2c3d stash@{0}: WIP on main: 7e8f9a0 bump ansible collection version
# 8a3f9d2 stash@{1}: WIP on feature/iam-rotation: 8a3f9d2 rotate IAM credentials  <-- dropped

# Step 2: copy the OID to a durable location
DROPPED_OID=$(git reflog show stash -1 stash@{1} | awk '{print $1}')
echo "Dropped stash OID: $DROPPED_OID"

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

# Step 4: recover via stash apply or stash branch
git stash apply $DROPPED_OID
# or
git stash branch feature/iam-rotation-recovery $DROPPED_OID

The recipe is the four-step discipline with one twist: the reflog being read is refs/stash, not HEAD. The reading is via git reflog show stash (or the equivalent git stash list if the entry is still tracked).

The case where the stash is truly gone

A stash is truly gone when:

  1. The stash log has expired (past the 90-day default).
  2. The clone has run git gc --prune=now after the stash log expired.
  3. No other clone has the stash in its object store or reflog.

In that case, the recovery is partial: the engineer can identify the moment the stash was made (the commit message in the stash log) and approximate the work from the commit that was current when the stash was made. The recovery is not mechanical; the engineer reconstructs the work from memory and from the diff against the parent commit.

The mitigation is to never drop a stash without first recording the OID. The OID is the recovery target; the OID alone is sufficient to recover the stash past the local reflog window from any clone that has the objects.

Production discipline

  1. Never git stash drop without first reading the reflog. The discipline is: git reflog show stash before git stash drop. If the OID is captured, the drop is recoverable; if the OID is not captured, the drop is forever.
  2. Always copy the OID to a durable location before dropping. A PR comment, a ticket, a chat message. The OID alone is the recovery target.
  3. Prefer git stash branch &lt;name&gt; over git stash pop for recovery. The branch form preserves the stash as a named branch; the pop form removes the stash from the log. A branch is recoverable; a stash is bounded by the log’s retention window.
  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 XI (Stash) — the stash mechanics; the prerequisite for this lesson.
  • Git, CI/CD & GitOps — Part XVII (Reflog) — the reflog locations and scopes; the prerequisite for the reading step.
  • GitOps with Argo CD — Part VII (DriftRecovery) — the GitOps controller’s stash mechanics; the controller uses stashes for its own diff tracking and the recovery recipe is the same.

Quiz

Knowledge check · 4 questions

  1. Q1. An engineer has just run `git stash drop stash@{0}` and realised the stash was the only record of half-finished work. What is the correct recovery sequence?

  2. Q2. The stash log (`.git/logs/refs/stash`) is a separate reflog from the HEAD reflog and is governed by the same 90-day default retention configuration.

  3. Q3. What is the difference between `git stash apply &lt;oid&gt;` and `git stash branch &lt;name&gt; &lt;oid&gt;` for recovering a dropped stash?

  4. Q4. Walk the recovery for an engineer who has dropped a stash and the stash list is empty.

    An engineer has dropped `stash@{0}` and `git stash list` is empty. The engineer had been working on a Terraform module for IAM roles; the stash contained the half-finished module. The branch was `feature/iam-rotation` and the engineer has switched to `main` since the drop. The engineer needs to recover the work to commit it on the feature branch.

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