Recovering Git History and Lost Code

Learn how to inspect local Git history, reflogs, and recovery copies without rewriting the repository before you know which version is correct.

By Ian Fang Intermediate 30 minutes
A student inspects branching code history and creates a separate recovery path.

Git may be able to recover code that disappeared from your current branch, but recovery begins with inspection. A reflog records local movements of references such as HEAD; it does not guarantee that every file or commit still exists, and it is not a substitute for a remote or tested backup.

The safe pattern is: protect the repository, inspect history, create a recovery reference, compare the candidate, and restore only after verification.

Protect the repository first

If this is a real incident, make a copy of the repository before experimenting. Work from the copy or from a separate clone. Do not begin with reset --hard, clean, garbage collection, reflog deletion, or a force-push.

Record the current state:

git status
git branch --show-current
git log --oneline --decorate -n 10

Save the output in your incident log after removing private paths or repository details that should not be shared. These commands inspect the repository; they do not restore anything.

Understand what may still exist

There are several different places to look:

Location What it may contain Important limitation
Working tree Unsaved or currently present files A later save may have overwritten the useful state
Commit history Committed versions The needed work may never have been committed
Reflog Recent local movements of a branch or HEAD It is local and entries may eventually expire
Remote repository Pushed commits and branches It may not contain the latest local work
Editor local history Saved file contents in the editor Settings and retention limits vary
Backup Files captured by the backup system Coverage and restore access must be verified

The Git reflog documentation describes reflogs as records of when reference tips were updated locally. The Git user manual also describes reflogs as a way to find commits after a branch was moved. This is different from ordinary Git history, which is shared through commits and references.

Practice in a disposable repository

Do not learn recovery on your only course repository. Create a small practice repository:

mkdir git-recovery-practice
cd git-recovery-practice
git init
printf 'first version\n' > notes.md
git add notes.md
git commit -m "Add first version"
printf 'second version\n' > notes.md
git commit -am "Add second version"
git log --oneline

The exact initial-branch message may differ by Git version and configuration. The important result is two commits with a known file.

Now identify the earlier commit from git log, and move the practice branch back:

git reset --hard HEAD~1

This command is intentionally destructive to the practice repository’s current working tree. Never run it on real coursework merely because it appears in a tutorial. The exercise gives us a controlled incident to inspect.

Inspect the reflog

Run:

git reflog --date=local

Look for the entry showing the branch or HEAD before the reset. You may see a reference such as HEAD@{1}. Treat that name as a pointer for inspection, not as proof that it is the version you want.

For the disposable exercise, an illustrative reflog might look like this:

9f3a2c1 HEAD@{0}: reset: moving to HEAD~1
2b7e410 HEAD@{1}: commit: Add second version
9f3a2c1 HEAD@{2}: commit: Add first version

The identifiers and times will differ on your machine. The useful clue is the action: the branch moved back to the first commit, while the reflog still names the second commit as the previous HEAD. Inspect it without changing the branch:

git show --stat HEAD@{1}
git show HEAD@{1}:notes.md

If the file path contains spaces or shell-special characters, quote it. If the candidate looks correct, create a new branch at that commit rather than moving the current branch again:

git switch -c recovered-notes HEAD@{1}

You now have a named recovery reference. Compare it with the current state before deciding whether to copy a file, merge work, or return to the original branch.

Recover a file into a separate directory

When you need one file rather than the whole branch, write the candidate to a new location:

git show HEAD@{1}:notes.md > recovered-notes.md

Open and inspect recovered-notes.md. Check the expected headings, code, dates, and other meaningful content. Do not overwrite the original until you know which version is correct and have another copy of the current state.

What recovery looks like in a student incident

Jordan runs git reset --hard HEAD~1 after an assignment test fails. The command returns successfully, but the branch now lacks a function Jordan wrote earlier. Jordan does not immediately reset again. A repository copy is preserved, the current branch and commit are recorded, and git reflog --date=local shows the earlier HEAD.

Jordan uses git show to inspect the candidate commit and exports only the missing file into a new recovery directory. The recovered file contains the expected function, but that is not enough: Jordan compares the diff, runs the assignment’s permitted tests from the recovery copy, and checks that no later edits were silently discarded. Only after that comparison does Jordan decide whether to copy the function into the working branch or create a separate branch for instructor review.

If the reflog candidate is incomplete, Jordan records that result and checks the remote, editor history, exported submission, and backup. The result is “this local candidate did not contain the expected function,” not “the work never existed.” That wording keeps the next search grounded in evidence.

Know when the evidence is not enough

Recovery becomes less predictable when the candidate is not a complete, readable project. Stop and record the result instead of escalating commands just because the deadline is close.

Finding What it tells you Next safe action
The reflog points to a commit, but the file is absent That commit may predate the file, use another path, or never contain the missing edit Inspect the commit tree and search other refs, editor history, and backups
The candidate contains the file, but the project does not run The code may depend on later files, configuration, or an environment that was not recovered Preserve the candidate, compare the diff, and rebuild only in a separate copy
The reflog has no useful candidate Local reference movement did not preserve the needed state, or the relevant entry has expired Check a remote, exported submission, tested backup, or authorized copy; then communicate the deadline risk
Git reports object or repository errors The repository may need specialized recovery, and repeated writes could complicate it Stop experiments, keep the repository copy, and seek qualified help

Do not turn git fsck into a guessing game. It can report unreachable objects, but a dangling object is not automatically the correct assignment. If you inspect one, record its identifier, export it separately, and compare its contents with the incident log before treating it as a candidate. Recovery is complete only when the version is both present and verified for the work you need to submit.

The Git fsck documentation describes git fsck as a consistency and reachability check and documents unreachable or dangling objects. It can help identify objects that are no longer referenced, but it cannot decide which object represents your assignment. Treat it as an advanced diagnostic step after the basic history and backup checks, not as a magic restore command.

If Git does not contain the work

Git cannot recover edits that were never recorded in Git. Search the editor’s local history, a tested backup, an exported submission, or an authorized teammate copy. For example, Visual Studio Code documents a Timeline view that can include local file history and a “Local History: Find Entry to Restore” action. Its retention and file-size settings still apply, so do not assume every save exists.

If the work is missing from all known sources, record that fact as the result of the search, not as a claim that the data is mathematically unrecoverable. Move to the communication step while preserving the evidence.

Common mistakes

  • Running reset --hard before copying the repository.
  • Confusing the reflog with a shared backup.
  • Recovering a commit without checking its contents.
  • Force-pushing a recovery branch over a remote branch.
  • Running git fsck --lost-found and treating every dangling object as the answer.
  • Assuming an editor history or backup has unlimited retention.

Do this now

Complete the disposable practice exercise, then write one sentence explaining the difference between normal commit history and a local reflog. Verify the recovered file by comparing its contents with the known second version.

Log what you learned

Record the repository’s starting state, the reference you inspected, the recovery copy you created, and the evidence that made the candidate version trustworthy. If this was a real incident, record which recovery sources remain untested.

Further reading