LEARN · PHASE 02 · LESSON 16

Recovering Lost Work

Four commands cover almost every lost-work situation you will ever have — and

Type
Learn
Prerequisites
Phase 02, Lessons 12–15
Time
~45 minutes

Learning Objectives

  • Pick the right one of soft / mixed / hard reset for a given situation
  • Find a lost commit through the reflog and get back to it
  • Choose revert over reset when the branch has been pushed
  • Apply a pre-agent commit so any failure afterwards is a review, not a loss

The Problem

An agent ran for twenty minutes, edited fourteen files, and told you it was "about 60% done" before something went sideways. Two of the files look right. Three look like a different design than you wanted. You cannot tell which.

You have two reactions available and both are bad. The first is git checkout . — throw everything away, including the two good files and all the thinking. The second is commit first, think later — which turns a recoverable mess into a permanent one you now have to unpick in public.

There is a third option, and it exists because git never throws anything away without leaving a pointer behind.


The Concept

The thing to unlearn: nothing is lost until garbage collection runs. Every command that "destroys work" moves a label. The old commits stay in the object store, and the reflog records where every label pointed. Recovery is mostly arithmetic over that list.

flowchart TD
    Q{What did you lose?}
    Q -->|A file you edited but never committed| A[git restore / git checkout --<br/>discards the edit; commit first if you want it]
    Q -->|A committed change you regret| B[git revert<br/>adds an undo commit]
    Q -->|Commits you want gone, unpushed| C[git reset --soft / --mixed / --hard]
    Q -->|Anything at all, "where was it?"| D[git reflog<br/>then reset back to the entry]
    Q -->|Uncommitted work you need to set aside| E[git stash<br/>Lesson 17]
Mermaid source — renders as a diagram where mermaid.js is available

The four commands, in order of how much they destroy

CommandMoves label?Destroys uncommitted work?Keeps the old commits?
git reset --soft <sha>YesNoYes, and the changes stay staged
git reset --mixed <sha> (default)YesNo — unstages insteadYes
git reset --hard <sha>YesYesYes (in reflog)
git revert <sha>No — adds a commitNoYes, and history stays honest

--soft and --mixed are safe. --hard is the one that can cost you, and the cost is always the same thing: uncommitted work, never committed work.

The rule that saves you

Never reset --hard with uncommitted changes you have not read.

Committed work is always recoverable through the reflog. Uncommitted work is not in the object store at all — it exists only in your folder. reset --hard is the one command in this lesson that reaches into that folder and overwrites it.

So: git status first. If it shows changes, git add -A && git commit first — even a commit labelled wip is infinitely better than a folder you just emptied. This is one line and it prevents the majority of horror stories.

The reflog is the undo nobody teaches

git reflog                     # every position HEAD has held, newest first
git reset --hard HEAD@{3}      # go back to where it was three moves ago

HEAD@{3} is not a commit message, not a branch — it is "the third most recent position of this label". It works after rebase, after reset, after a branch was deleted, after a bad merge. If you take one thing from this lesson: when something feels unrecoverable, run git reflog before anything else.

The three real incidents worth knowing

Isolated work with no commit is not a backup. Three agents produced complete, correct work in three separate worktrees. All three branches pointed at main. Nothing to merge. Isolation without a commit is just three folders nobody was looking at.

git reset --hard after a merge you did not mean to make. History is intact; git reset --hard ORIG_HEAD or a reflog entry gets you back.

Amending a pushed commit. It works locally, the push is rejected, and git log --oneline on the remote shows a commit you no longer have. It is there — git reflog again — but you cannot push a replacement without rewriting what others fetched, which is Lesson 14's rule about shared history.

The reframe: git never deletes your commits, it moves labels — so recovery

is not luck or forensics, it is reading the reflog and pointing a label back.


Build It

1. Create a mistake worth recovering from

cd ~/git-drill
git switch main
printf 'good\n' > keep.txt && git add keep.txt && git commit -m "good state"
printf 'bad\n'  > oops.txt && git add oops.txt && git commit -m "oops"
printf 'junk\n' > junk.txt
git log --oneline

Note the hash of the "good state" commit.

✅ You pass when you can name that hash and there are two commits above it.

2. Recover with --soft, then --hard

git reset --hard <good-hash>
git log --oneline
git reset --soft HEAD~1
git status

--hard moves the label and discards junk.txt (uncommitted, gone). --soft brings the "good state" commit back and keeps the tree changes staged.

✅ You pass when git log --oneline ends at "good state" after the first command, and after reset --soft HEAD~1 git status shows changes staged against it.

3. Use the reflog instead of guessing

git reflog

Your previous positions are listed with short hashes, including the one before the --hard.

✅ You pass when you can read the "oops" commit in the reflog output and say that it is still there.

4. Undo a commit honestly with revert

git checkout -b keep-both
git revert HEAD        # or: git revert <oops-hash>
git log --oneline

History now contains both the mistake and its undo. Nothing was rewritten, so this is safe on shared history.

✅ You pass when git log --oneline shows the original commit and a revert commit above it, and you can say why that matters for a branch you pushed.

5. Watch uncommitted work disappear

printf 'expensive uncommitted work\n' > precious.txt
git status                              # precious.txt is untracked
git reset --hard <good-hash>
git status                              # precious.txt is gone. untracked files are not in git's way.

Note: reset --hard does not delete untracked files — it restores tracked ones. To lose untracked work you need git clean, which is a different and much more dangerous command.

✅ You pass when precious.txt still exists after reset --hard, and you can state the command that would have removed it.

6. Break a merge and undo it

git switch feature
git merge main        # possibly conflicted
git merge --abort     # back to before the merge, while it's in progress

✅ You pass when the merge is gone and your branch is where it was before you started it.


Use It

Command / conceptWhen you reach for it
git statusBefore anything destructive. Always first.
git reflog"Where was it?" — before you conclude anything is lost
git reset --soft <sha>Undo commits, keep changes staged. Safe.
git reset --hard <sha>Undo commits and discard uncommitted edits. Read git status first.
git revert <sha>Undoing on shared history — history stays honest
git merge --abortAn in-progress merge went wrong
ORIG_HEADThe position before the last merge or reset
git cherry-pick <sha>Taking one commit from elsewhere without the rest
WIP commitgit add -A && git commit -m "wip" before anything risky
GCWhen unreachable objects are actually reclaimed. The reason "recoverable" has a shelf life.

Exercises

  1. Make five real mistakes in a scratch repo — a bad commit, a bad merge, an uncommitted file you clobbered, an amended commit, a deleted branch — and recover each one. Record the exact command. ✅ You pass when every recovery came from a command in the table above, and you can say for each one whether committed or uncommitted work was at risk.
  1. Delete a branch holding unmerged commits with -D. Find the work again. ✅ You pass when git reflog got it back and you wrote down how long it is safe to wait before garbage collection makes it permanent.
  1. Write your own two-line rule for before any agent run. Then read AGENTS.md in this repo and check whether your rule is already in force. ✅ You pass when your rule is a command, not an intention, and you can point at where this repo already states it.

Key Terms

TermWhat people sayWhat it actually means
recovery"undoing my mistake"Moving a label back. The commits were never deleted.
reset --soft / --mixed"soft reset"Moves the branch; keeps your working tree. Safe.
reset --hard"starting over"Moves the branch and overwrites tracked files. The only command here that can destroy uncommitted work.
git revert"undo"A new commit that undoes an old one. Correct on shared history.
reflog"undo for git"Every position each label has held, in order. HEAD@{n} indexes it.
ORIG_HEAD"the backup"Where HEAD was before the last merge or reset.
garbage collection"cleanup"When unreachable objects are actually reclaimed. Gives "recoverable" a shelf life.
untracked file"my file"Not in git's object store. reset --hard will not delete it; git clean will.

What this lesson skipped

git fsck --lost-found and manual object plumbing. When the reflog has already been pruned and only dangling objects remain. Real, and much rarer than the reflog not being checked.

Recovering work that was never committed and the folder was overwritten. Git cannot help. The only recovery is a filesystem undelete tool or the editor's local history. This is why the lesson's rule is "commit before anything risky" — there is no second mechanism.

Interactive rebase as a recovery tool. git rebase -i to drop or reorder commits. Same underlying reflog safety, more surface area.

Check yourself

5 questions. Answer before you reveal — recognising the right option is not the same as being able to produce it.

  1. You have a bad merge commit on a branch you already pushed. Three people have pulled it. What is the correct undo?

    1. git revert on the merge commit, so history keeps both the mistake and its correction
    2. git reset --hard to before the merge and force-push, so the bad commit disappears
    3. git rebase -i and drop the merge commit, then force-push
    4. Delete the branch and start it again from main
    Show answer

    A. git revert on the merge commit, so history keeps both the mistake and its correction

    Shared history means other people already hold the merge commit, so history must be corrected rather than rewritten. revert adds an inverse commit and pushes cleanly. Resetting and force-pushing (B) and rebasing and force-pushing (C) both leave everyone else with a history that no longer contains the commit they pulled, which is Lesson 14's rule stated as a concrete failure. Deleting the branch (D) destroys the label everyone is working from, not the merge.

  2. An agent just spent twenty minutes editing six files. You want to reset the branch back to yesterday but keep the good edits uncommitted so you can read them. Which reset mode?

    1. git reset --hard, because you want a clean slate
    2. git reset --soft, because the edits were not committed so they are already in your working tree and stay there
    3. git reset --mixed, because it preserves working tree changes
    4. git revert, because revert preserves working tree changes
    Show answer

    B. git reset --soft, because the edits were not committed so they are already in your working tree and stay there

    Working tree contents are untouched by any reset — the mode only decides whether the difference between old and new position stays staged or becomes unstaged. So --soft (B) leaves the six edited files exactly as the agent left them and simply moves the branch label back. --hard (A) is the one mode that overwrites tracked files, which is the opposite of what you just asked for. Both --mixed and revert keep the edits too (C, D), so the question's real answer is that the mode barely matters here — and the option naming it as the reason is the trap.

  3. Someone says a branch's work is gone for good because they ran git branch -D on it and reset --hard twice since. What is the accurate answer?

    1. They are gone — garbage collection removes unmerged commits immediately
    2. They are probably still reachable through the reflog, which records the branch's past positions, until garbage collection runs
    3. They can be recovered only from the remote, so the work is lost if it was never pushed
    4. Git keeps every object forever by design, so nothing is ever lost
    Show answer

    B. They are probably still reachable through the reflog, which records the branch's past positions, until garbage collection runs

    The reflog survived both deletions — it records where labels pointed regardless of whether anything references them now. That makes recovery likely, not guaranteed, because garbage collection reclaims unreachable objects eventually. Immediate loss (A) and a remote-only answer (C) both overstate the danger; never-lost (D) understates it, since a long-enough idle period plus gc really does end it. The honest answer is 'probably, and stop doing other destructive commands'.

  4. You are about to run git reset --hard on a repo where an agent made an uncommitted change you have not read. What does the lesson say to do first?

    1. Run it — committed work is always recoverable through the reflog
    2. Run git status; if anything is uncommitted, commit it first, even as a WIP commit
    3. Stash it, which is always the safer alternative to committing
    4. Copy the files to a temp directory first, which git cannot undo
    Show answer

    B. Run git status; if anything is uncommitted, commit it first, even as a WIP commit

    The reflog protects commits; uncommitted work lives only in your folder and is not in the object store, so reset --hard can destroy it outright — that is the whole distinction. Committing first, even as wip, costs one command (B). Stashing (C) also preserves it, but it is not 'always safer' — a stash you forget about is work nobody reads, whereas a WIP commit is visible to everyone. Copying files by hand (D) is the manual version of the same one-command fix.

  5. Three agents each finished a task in three separate git worktrees. None of them committed. What has actually been produced?

    1. Three branches ready to merge, since the worktree branches were separate
    2. Three folders of work that exist nowhere in history, because nothing was committed
    3. Three commits on three branches, created automatically when the worktrees closed
    4. Nothing recoverable, because uncommitted work was never committed
    Show answer

    B. Three folders of work that exist nowhere in history, because nothing was committed

    A branch only exists where a commit exists. No commit means the branches still point at whatever they started from, and the edits are loose files in three folders. This is a real incident from this repo's own history — the work was real and complete and still had nowhere to be merged from. The fix is not a git feature but an instruction: tell every spawned agent to commit (A's premise, C's automation, and D's despair all fail for the same reason).