Software engineer recovering a conflicted Git stash on a laptop in a modern office

Recover a Conflicted Git Stash Without Losing the Original State

Thu, Oct 8, 2026

A conflicting git stash pop can leave more than conflict markers. Some pending bytes may already be present in the worktree or index while the stash entry still exists. In this controlled scenario, an advanced commit conflicts with a previously saved stash. The pop returns a nonzero status, Git retains the stash after the conflict, and the repository is only partially changed. The unresolved file, conflict.txt, carries merge evidence, while clean.txt, staged.txt, and new.txt already contain the saved bytes.

The recovery question is therefore narrower than ordinary conflict resolution: which changes reached the current checkout, is the original stash object still the same object, and can the original file contents plus staged/unstaged partition be reconstructed at the stash creation base without destroying the first failure? Restoration and integration are separate decisions. A complete old-base reconstruction can be accepted as recovery evidence while integration into the advanced branch remains unapproved.

The laboratory uses only newly created local directories, one advanced branch, and one detached linked worktree. The commissioning preflight ran on October 8, 2026 with Git 2.51.1, CPython 3.12.14, Linux 6.18.44 x86_64, and glibc 2.39. Those observations apply to this fixture and runtime, not to every possible conflict, path type, Git version, or external failure mode.

1. Define the state you intend to recover

Before running another mutating command, define the acceptance target. “Original state” does not mean merely seeing familiar text in the working directory. It means recovering the literal pending bytes and the same staged/unstaged partition that existed immediately before the stash was created.

  • Pending file contents: the exact bytes for every path in the declared fixture, including terminal newlines and the untracked file saved with the stash.

  • Index partition: which tracked edits were staged, which remained only in the worktree, and which path was still untracked.

Treat four Git states as separate evidence: the creation-base HEAD, the current advanced HEAD, the index, and the stash object. They are related, but they can diverge after a conflicted apply. A retained stash does not imply an unchanged checkout, and a file that looks correct in an editor does not establish that its index entry is correct.

The documented stash commit structure identifies the stash creation HEAD as the first parent and records the saved index through the second parent. Reconstructing the pending work at the first parent gives reviewers the context in which those edits originally existed. It does not decide how the edits should be reconciled with the advanced commit. Readers who need a broader command refresher can review everyday Git version-control foundations; this article starts after a stash already exists and focuses on evidence-based recovery.

2. Record repository and stash identity before another mutation

Capture identifiers before reset, checkout, restore, add, commit, another stash operation, or manual conflict editing. Record the explicit current working directory, the repository root, the current HEAD, the current refs/stash object, and the stash first parent. Use the object ID for later commands rather than relying only on the moving name stash@{0}.

repo_root=$(git rev-parse --show-toplevel)
advanced_head=$(git rev-parse HEAD)
stash_oid=$(git rev-parse refs/stash)
base_oid=$(git rev-parse "${stash_oid}^1")
printf '%s\n' "$repo_root" "$advanced_head" "$stash_oid" "$base_oid"

Record the values exactly as Git returns them. Do not publish invented hashes as though they were fixture output. The stash object ID identifies the selected object, while its first parent identifies the creation base used by the recovery worktree. The second parent is the saved index commit for the stash object model; the protocol still validates the restored index independently rather than assuming the parent relationship alone proves success.

Also save git status --porcelain=v1 -z and git ls-files --stage -z. The porcelain v1 status format is intended for stable script parsing, and the NUL-delimited form preserves path boundaries. The status record supplies the index/worktree classification; the staged listing supplies mode, object ID, stage, and path. These are different forms of evidence. For the separate question of whether an ignored path is already tracked, use tracked-file and index policy validation rather than expanding this recovery protocol into an ignore-policy tutorial.

Keep a moving stash label separate from its recorded object

The symbolic position stash@{0} can change if another stash is created, dropped, or cleared. Recording the object ID prevents a later command from silently selecting a different stash. That recording is evidence of identity, not independent storage: an object ID written in a report does not by itself keep an otherwise unreachable object available forever. The laboratory therefore leaves refs/stash unchanged and verifies that it still resolves to the recorded object after both the conflict and the recovery apply.

The git stash branch behavior is relevant because it starts from the stash creation base and drops the stash only after a successful application. This protocol deliberately avoids that removal behavior. It uses apply by recorded object ID in a separate worktree, preserving the shared stash reference while evidence is still under review.

3. Build the disposable repository and independent byte manifest

Create the laboratory only inside a newly owned directory. The driver below passes an explicit cwd to every Git subprocess so repository identity remains reviewable. That discipline complements explicit command-directory provenance: no command depends on a previous shell changing directories. The fixture has no remotes and makes no network calls. It sets a synthetic identity, fixed commit dates, an empty hooks directory, disabled signing, disabled rerere, and disabled system/global configuration. It does not touch an existing project.

Author expected contents before executing the conflict

The acceptance manifest is authored before recovery. It is not derived from whatever bytes happen to appear afterward. The three tracked files begin at the base commit. Before stashing, conflict.txt and clean.txt are modified but unstaged, staged.txt is modified and staged, and new.txt is untracked. Each expected value includes a final newline.

Path

Base bytes

Pending bytes

Original partition

conflict.txt

base\n

stashed-version\n

Unstaged tracked edit

clean.txt

clean-base\n

clean-stashed\n

Unstaged tracked edit

staged.txt

index-base\n

staged-version\n

Staged tracked edit

new.txt

Absent

untracked-stashed\n

Untracked path

Save the following file as git_preflight.py beside an otherwise empty location, then run the command shown below. The script creates its own timestamp-independent temporary directory beneath the script directory and preserves the resulting repository and JSON evidence for inspection.

python git_preflight.py
from pathlib import Path
import hashlib
import json
import os
import platform
import subprocess
import tempfile

if not debug:
    raise RuntimeError("Run without -O or PYTHONOPTIMIZE; proof assertions must be enabled")
ROOT = Path(__file__).resolve().parent
lab = Path(tempfile.mkdtemp(prefix="stash-lab-", dir=ROOT))
report_path = lab / "git_preflight_results.json"
repo, recovery = lab / "source", lab / "recovery"
hooks = lab / "empty-hooks"
hooks.mkdir()
env = {"PATH": os.environ["PATH"], "LANG": "C", "LC_ALL": "C",
       "GIT_CONFIG_NOSYSTEM": "1", "GIT_CONFIG_GLOBAL": os.devnull,
       "GIT_TERMINAL_PROMPT": "0", "GIT_AUTHOR_DATE": "2000-01-01T00:00:00+00:00",
       "GIT_COMMITTER_DATE": "2000-01-01T00:00:00+00:00"}
ledger = []

def git(cwd, args, expected=(0,)):
    p = subprocess.run(["git", args], cwd=cwd, env=env, capture_output=True)
    ledger.append({"cwd": str(cwd), "args": list(args), "rc": p.returncode,
                   "stdout": p.stdout.decode(), "stderr": p.stderr.decode()})
    if p.returncode not in expected:
        raise RuntimeError(f"unexpected Git result for {args}: {p.returncode}")
    return p.stdout

def write(name, value):
    (repo / name).write_bytes(value)

def snapshot(path):
    return {"head": git(path, "rev-parse", "HEAD").decode().strip(),
            "status": git(path, "status", "--porcelain=v1", "-z").decode(),
            "index": git(path, "ls-files", "--stage", "-z").decode(),
            "unmerged": git(path, "ls-files", "--unmerged", "-z").decode(),
            "files": {n: (path / n).read_bytes().decode() for n in
                      ("conflict.txt", "clean.txt", "staged.txt", "new.txt")
                      if (path / n).exists()}}

result = {"fixture": "refonte-stash-v1", "lab": str(lab),
          "python": platform.python_version(), "platform": platform.platform(),
          "script_sha256": hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
          "commands": ledger, "status": "INCOMPLETE"}
try:
    result["git_version"] = git(lab, "--version").decode().strip()
    git(lab, "init", "--initial-branch=main", str(repo))
    for key, value in (("user.name", "Synthetic Fixture"),
                       ("user.email", "[email protected]"),
                       ("commit.gpgsign", "false"), ("core.autocrlf", "false"),
                       ("core.hooksPath", str(hooks)), ("rerere.enabled", "false"),
                       ("stash.index", "false")):
        git(repo, "config", key, value)
    base_files = {"conflict.txt": b"base\n", "clean.txt": b"clean-base\n",
                  "staged.txt": b"index-base\n"}
    for n, v in base_files.items(): write(n, v)
    git(repo, "add", "--", *base_files)
    git(repo, "commit", "-m", "fixture base")
    base = git(repo, "rev-parse", "HEAD").decode().strip()
    intended = {"conflict.txt": b"stashed-version\n", "clean.txt": b"clean-stashed\n",
                "staged.txt": b"staged-version\n", "new.txt": b"untracked-stashed\n"}
    for n, v in intended.items(): write(n, v)
    git(repo, "add", "--", "staged.txt")
    before = snapshot(repo)
    git(repo, "stash", "push", "--include-untracked", "-m", "fixture pending edits")
    oid = git(repo, "rev-parse", "refs/stash").decode().strip()
    assert git(repo, "rev-parse", f"{oid}^1").decode().strip() == base
    assert git(repo, "status", "--porcelain=v1", "-z") == b""
    write("conflict.txt", b"advanced-version\n")
    git(repo, "add", "--", "conflict.txt")
    git(repo, "commit", "-m", "advance conflicting line")
    advanced = git(repo, "rev-parse", "HEAD").decode().strip()
    assert git(repo, "status", "--porcelain=v1", "-z") == b""
    git(repo, "stash", "pop", expected=(1,))
    conflicted = snapshot(repo)
    assert conflicted["head"] == advanced
    assert git(repo, "rev-parse", "refs/stash").decode().strip() == oid
    stages = {i: git(repo, "show", f":{i}:conflict.txt").decode() for i in (1, 2, 3)}
    assert stages == {1: "base\n", 2: "advanced-version\n", 3: "stashed-version\n"}
    for n in ("clean.txt", "staged.txt", "new.txt"):
        assert (repo / n).read_bytes() == intended[n]
    assert len(conflicted["unmerged"].split("\0")[:-1]) == 3
    git(repo, "worktree", "add", "--detach", str(recovery), base)
    git(recovery, "stash", "apply", "--index", oid)
    recovered = snapshot(recovery)
    assert recovered["head"] == base
    assert recovered["unmerged"] == ""
    assert recovered["status"] == before["status"]
    assert recovered["index"] == before["index"]
    for n, v in intended.items(): assert (recovery / n).read_bytes() == v
    assert git(repo, "rev-parse", "refs/stash").decode().strip() == oid
    assert snapshot(repo) == conflicted
    result.update(status="PASS", base_oid=base, advanced_oid=advanced, stash_oid=oid,
                  before_stash=before, after_conflict=conflicted,
                  conflict_stage_contents=stages, recovery=recovered,
                  retained_stash=True, original_conflicted_tree_unchanged=True)
finally:
    report_path.write_text(json.dumps(result, indent=2) + "\n")
    print("Evidence: " + str(report_path))
print(json.dumps({k: result[k] for k in ("status", "git_version", "lab",
      "retained_stash", "original_conflicted_tree_unchanged")}, indent=2))

The driver performs the full negative-and-positive sequence rather than checking only whether a final apply returns zero:

  1. Initialize a new main repository and commit the three base files.

  2. Write the four pending values, stage only staged.txt, and snapshot HEAD, porcelain status, staged entries, unmerged entries, and file contents.

  3. Create a stash with --include-untracked, record refs/stash, prove the first parent is the base commit, and prove the source checkout is clean.

  4. Commit advanced-version to conflict.txt, then run git stash pop while explicitly expecting exit status 1.

  5. Snapshot the partial result, prove the stash object is retained, read stages 1, 2, and 3 for conflict.txt, and verify the three nonconflicting paths have the intended bytes.

  6. Create a detached linked worktree at the recorded base, apply the recorded stash with --index, and compare the complete recovery snapshot with the independent pre-stash snapshot.

  7. Verify that refs/stash still resolves to the same object and that the original conflicted checkout has not changed during recovery.

The commissioning preflight ran the supplied driver on October 8, 2026. It recorded Git 2.51.1 and CPython 3.12.14 on Linux 6.18.44 x86_64 with glibc 2.39. Its assertions passed for this small fixture: pop returned 1, the stash object remained the same, conflict.txt had three unresolved stages, the nonconflicting bytes were present, and the recovery worktree restored all four paths with the original status and staged-entry snapshots. The JSON ledger contains each command’s cwd, arguments, return code, stdout, and stderr. This is scoped execution evidence, not a compatibility guarantee for unrelated repositories or Git versions.

4. Reproduce the pop conflict against the advanced base

The transition order matters. First establish the base commit. Next create the pending edits and save the independent before snapshot. Then stash the tracked and untracked work, verify a clean source checkout, commit the advanced edit to conflict.txt, and only then attempt the pop. A different order tests a different state transition.

git stash push --include-untracked -m "fixture pending edits"
git add -- conflict.txt
git commit -m "advance conflicting line"
git stash pop

In the commissioning run, git stash pop returned exit status 1. That failure was the intended negative case. The advanced HEAD did not move, refs/stash still resolved to the recorded object ID, and the repository contained both an unresolved index entry for conflict.txt and successfully applied changes on other paths. The stash manual documents that a pop removes the stash only after a successful application; a conflict keeps it available. The retained entry is therefore expected, but the exact partial repository state must still be measured rather than inferred.

Do not hide the nonzero status behind a later command or treat the retained stash as proof that nothing changed. Conversely, do not assume every unsuccessful Git command has identical partial effects. This protocol accepts only the specific command sequence and the evidence collected immediately afterward. An unexpected return code, a changed HEAD, a different stash object, missing command output, or a repository path that cannot be tied to the ledger makes the reproduction incomplete and moves the decision to HOLD.

5. Inspect conflict stages as well as visible file contents

Conflict markers are only one view of the failure. The index carries the structured merge inputs. Use the NUL-delimited unmerged listing to identify unresolved paths without relying on display formatting, then inspect the stage entries for the path under test.

git ls-files --unmerged -z
git ls-files --stage -z -- conflict.txt

The git ls-files stage documentation defines the stage field exposed by --stage and the unresolved entries selected by --unmerged. Ordinary resolved entries use stage 0. Up to three higher stages can store merge inputs, but a reviewer must interpret the entries actually present for the conflict rather than imposing the same three-stage pattern on every path or conflict type.

Read the three stored inputs for this conflict

For this fixture, conflict.txt has stage 1 for the base input, stage 2 for the advanced checkout side, and stage 3 for the stashed side. Read the stored blobs directly:

git show :1:conflict.txt
git show :2:conflict.txt
git show :3:conflict.txt

The observed contents were base, advanced-version, and stashed-version, each followed by a newline. These are fixture observations. Other conflicts may omit a stage or produce a different set of inputs. Removing visible markers from the worktree file is not enough to prove resolution because the unresolved stage entries can remain in the index. The recovery acceptance check therefore requires an empty unmerged listing in the separate recovery worktree, not merely a visually tidy file.

6. Account for nonconflicting and untracked changes already applied

After the failed pop, inventory every declared path. The commissioning run found the intended stashed bytes in clean.txt, staged.txt, and new.txt even though conflict.txt remained unresolved. That means the current checkout is not a safe blank slate for another pop. It already contains partial results that must be preserved as evidence or reconciled under an explicit in-place plan.

The original staged/unstaged partition did not survive the failed application. In this fixture, clean.txt had been unstaged before the stash but appeared staged after the conflict. staged.txt also contained the saved bytes. new.txt was restored with its untracked bytes and remained outside the tracked index; porcelain status represents that path separately from stage-0 tracked entries. Record the actual result instead of assuming that a failed pop preserved each path’s original classification.

The --include-untracked stash option is the bounded reason new.txt participates in this experiment. It does not include ignored files, and ignored-file policy is outside the recovery claim. The evidence must therefore distinguish “file exists with expected bytes” from “file is tracked at stage 0” and from “file has the original pre-stash status code.” Those are separate assertions.

7. Choose deliberately among pop, apply and branch

The three related commands have different reference effects and recovery tradeoffs. Choose from documented behavior rather than treating them as interchangeable aliases.

Command

Stash reference effect

Starting context

Use in this protocol

git stash pop

Drops after a successful apply; a conflict retains the stash

Current checkout

Used once to create and preserve the negative case. Do not repeat it over the partial checkout.

git stash apply --index

Leaves the stash reference unchanged

Chosen checkout

Selected for recovery by recorded object ID in the detached original-base worktree.

git stash branch

Creates a branch at the creation base and drops the stash after successful apply

Stash creation base

Documented alternative, but its success-path removal is not desired while evidence remains under review.

A second pop on the conflicted checkout is not a recovery protocol. The first attempt has already changed that checkout, so another application would act on a new and partially modified state. The selected path is a detached worktree at the recorded base followed by git stash apply --index using the recorded object ID. Starting at the creation base removes the deliberate advanced-branch conflict from the comparison, while apply preserves the stash reference. It can still fail for other reasons; its zero status is necessary but not sufficient evidence of exact restoration.

8. Restore the pending work in a linked worktree at the original base

A linked worktree supplies an independent HEAD, index, and working directory while sharing repository objects and most references. The git-worktree documentation permits a detached worktree to start at an explicit commit. That makes the recorded first parent an appropriate comparison point without resetting, cleaning, or editing the original conflicted checkout.

git worktree add --detach <owned-recovery-path> "$base_oid"
git -C <owned-recovery-path> stash apply --index "$stash_oid"

Keep the command directory explicit in the ledger. The worktree-add command is issued from the source repository and names the recorded base commit. The apply command is issued with the recovery worktree as its cwd and names the recorded stash object. In the commissioning run, worktree creation and apply both returned zero. The detached HEAD remained at the base commit, the recovery index had no unresolved entries, and the recovered snapshot matched the independent before-stash status and staged-entry snapshots.

The separate worktree preserves the first failure for review. It does not copy that failure elsewhere or turn the recovery directory into a separate repository. The source checkout remains at the advanced HEAD with its unresolved index and partially applied paths. The recovery checkout holds the old-base reconstruction. Reviewers can compare them without forcing either state to masquerade as the other.

Preserve the shared stash reference during recovery

Linked worktrees share the repository’s stash reference. A pop, drop, or clear issued from the recovery directory would therefore affect the same refs/stash observed from the conflicted worktree. Use apply, not pop, and verify that refs/stash still resolves to the recorded object after recovery. The script also snapshots the source checkout again and requires byte-for-byte equality with the earlier conflicted snapshot.

This separation is not a backup against object-database loss, storage failure, concurrent ref changes, or excluded data. Both worktrees depend on the same repository storage. The protocol protects the conflicted checkout from deliberate cleanup while creating a second index/worktree context for comparison; it does not guarantee that all possible user data is safe.

9. Prove both contents and the original staging partition

Accept recovery only when every declared condition passes. A successful apply status or a content-only diff is insufficient because it can miss a changed staging partition, an unexpected base, an unresolved index, or an extra path.

  • Original-base identity: the recovery HEAD equals the recorded stash first parent.

  • Resolved index: git ls-files --unmerged -z is empty in the recovery worktree.

  • Exact status partition: the complete NUL-delimited porcelain status equals the independent pre-stash snapshot.

  • Exact staged entries: the complete NUL-delimited ls-files --stage output equals the pre-stash snapshot, including modes, blob IDs, stages, and paths.

  • Literal bytes: conflict.txt, clean.txt, staged.txt, and new.txt match the authored manifest, including terminal newlines.

  • Preserved first failure: the source conflicted snapshot and retained stash object are unchanged after recovery.

All six checks passed in the named commissioning environment. Do not copy its generated object IDs into portable expected output; object IDs are recorded evidence for that run. The acceptance claim is also bounded to the four fixture paths and the saved status/index manifests. It does not cover ignored files, concurrent edits, secrets handling, repository corruption, or external storage loss, and it should never be summarized as a guarantee of no data loss.

10. Decide how to integrate recovered work with later commits

Recovery establishes what the pending work was. It does not establish what the advanced branch should become. Make one of four explicit decisions from the evidence:

  • RECOVER SEPARATELY: retain the original-base worktree as the accepted reconstruction of the developer’s pending work. Preserve or commit it only under an owner-approved local plan.

  • RECONCILE IN PLACE: resolve the source checkout’s conflict only when the repository maintainer approves the intended content and verifies the resulting index and tests. Do not treat marker removal as approval.

  • HOLD: stop when object identity, bytes, index state, command output, or pre-stash evidence is missing or contradictory.

  • REVIEW LATER INTEGRATION: carry the accepted recovery record into a separate comparison against the advanced commit and decide how, whether, and where to integrate it.

The laboratory ends before any integration commit. It does not reset the advanced branch, switch the source checkout, rewrite history, or choose between advanced-version and stashed-version. That restraint is important: an exact reconstruction at the old base can be technically successful while the correct combined business behavior remains unknown.

Keep restoration acceptance separate from integration approval

A maintainer reviewing integration should receive the base, advanced, and stash object IDs; the stage-1/2/3 contents for conflict.txt; the four-path byte manifest; the pre-stash and recovered status/index snapshots; the command ledger; and the recovery decision. Those artifacts explain what was restored and how it was verified. They do not prove that the recovered logic is compatible with later commits or approved for release.

A later local integration review may compare the recovered worktree with an explicitly chosen branch, run project-specific tests, and create a reviewed commit. It may also decide that some recovered edits should not be integrated. Keep that outcome distinct from the recovery acceptance record so a future reader can tell whether the question was “Did we reconstruct the pending work?” or “Did we approve these changes for the advanced line?”

11. Capture an auditable recovery record before cleanup

Retain the evidence before deleting any laboratory path. At minimum, record the Git and Python versions, platform string, script digest, fixture name, explicit command directories, base/advanced/stash object IDs, ordered return codes, stdout, stderr, pre-stash snapshot, conflict snapshot, stage contents, recovery snapshot, and final decision. The report should make failed assertions visible by leaving status as INCOMPLETE unless every acceptance check reaches PASS.

{
  "cwd": "<owned-lab>/source",
  "args": ["stash", "pop"],
  "rc": 1,
  "stdout": "<captured verbatim by the driver>",
  "stderr": "<captured verbatim by the driver>"
}

If an outer logging layer is added later, preserve the Git subprocess status rather than substituting the logger’s status. The separate discussion of preserving command failures through logging explains that boundary. This fixture avoids a shell pipeline and captures subprocess results directly.

Inspect the repository and JSON report before cleanup. Cleanup must be limited to the newly created owned paths. Do not force-remove a dirty worktree merely to make the laboratory disappear, and do not translate this example into reset or clean commands for a real checkout. Evidence review comes first; removal is a separate, deliberate housekeeping action.

12. Handle missing or contradictory recovery evidence

Move the decision to HOLD when any required fact cannot be established. Common examples include:

  • Unresolvable identity: the recorded stash object no longer resolves, refs/stash points elsewhere without an explained change, or the first parent is not the expected base.

  • Incomplete historical baseline: the original status, staged entries, or literal expected bytes were never captured, so a later clean apply cannot prove the old partition.

  • Unexpected repository contents: extra files appear, a declared path is missing, a mode differs, or the recovered index contains unmerged or unanticipated entries.

  • Contradictory execution record: return codes, stdout/stderr, cwd, or snapshots disagree with the claimed command sequence.

  • Concurrent mutation: another process or user changes a shared ref, worktree, or object reachability while the recovery is being measured.

A new disposable run can reproduce the demonstration, but it cannot recreate missing historical user data or prove what an unrecorded staging partition once was. Dropped-stash forensics, destructive resets, broad repository repair, and ignored-file recovery are outside scope. Preserve what evidence remains, stop mutating the affected repository, and escalate the unresolved facts instead of converting a partial match into an acceptance claim.

13. Assign acceptance and integration ownership

Recovery and integration need named owners because they answer different questions. The developer who created the pending work supplies context and validates the intended manifest. The repository maintainer validates object identity, command provenance, index evidence, and the recovery decision. The release owner approves only a later integrated result after project-specific review. Broader team practices are discussed in Git collaboration in data engineering, but this procedure remains a local stash-recovery protocol.

Evidence state

Recovery decision

Integration decision

Primary owner

All identity, byte, status, index, and preservation checks pass

RECOVER SEPARATELY

Review later; not approved by recovery alone

Repository maintainer with developer confirmation

Required historical evidence is absent or contradictory

HOLD

No integration action

Repository maintainer

Disposable fixture differs from the expected controlled result

INVESTIGATE FIXTURE

Not applicable until explained

Test owner or tooling maintainer

Recovered state is accepted and an explicit reconciliation plan exists

Recovery record remains accepted

Integrate only after separate review and tests

Release owner and repository maintainer

Require a failure-sensitive regression record

A regression record must include both halves of the experiment: the advanced-base conflict and the original-base recovery. It must exercise all four paths, require the expected pop failure, inspect the unresolved stages, verify the retained stash object, restore with --index, compare the original status and index snapshots, and prove the conflicted checkout stayed unchanged. Skipping the negative case or recording only a zero apply status removes the evidence that distinguishes this workflow from a generic unstash example.

The repository owner maintains the expected manifest and reviews changes to the fixture. The person running the test retains the full ledger rather than copying only a PASS label. The decision record should state who accepted recovery, who owns later integration, and which evidence was unavailable. That makes the protocol useful for incident recovery without turning the demonstration into an automatic business-resolution engine.

14. Strengthen version-control practice with Refonte Learning

The operational lesson is not simply that Git kept a stash. It is that reliable recovery depends on object identity, explicit command context, index evidence, literal byte expectations, failure-sensitive automation, and a clear boundary between reconstruction and integration. Those habits are useful wherever engineers move changes through build and delivery systems, because they replace assumptions with reviewable state transitions.

Refonte Learning’s DevOps Engineering program provides a broader foundation in Git and GitHub, Linux and scripting, CI/CD, containers, cloud platforms, Terraform, and monitoring. The published program details describe a three-month schedule with an estimated 12–14 hours per week, project work, and educational guidance. Admission refers to students pursuing a bachelor’s or higher-level degree, and the page separately describes certificates after successful completion and a potential internship. These are program features, not a promise that this specialist stash/worktree procedure is included or that enrolment guarantees an internship, recovery expertise, or employment.

For this experiment, the endpoint is an evidence-backed recovery decision: the original pending work is reconstructed at its recorded base, the conflicted checkout remains available for review, and integration with the advanced commit still requires explicit approval. Carry that standard forward: preserve identity, prove state, and keep the next decision separate.