The safety net and the reflog
Intermediate Git CLI
Why this matters
Most Git panic is unnecessary. The commands that look destructive usually only move a pointer, and Git keeps a private list of every position that pointer has held. Knowing this changes how the rest of the section reads: you are not defusing a bomb, you are looking something up.
The safety net
Every time HEAD moves, Git writes a line in the reflog: commits, checkouts, merges, resets, rebases, pulls. The commit objects themselves stay in the repository, unreferenced but intact, for about ninety days.
Three consequences, and they are the whole lesson:
- A commit you made can be found again, even after
git reset --hardor deleting its branch. - The reflog is local. It is never pushed, never fetched, and belongs to your clone alone. Your colleague's reflog cannot help you and yours cannot help them.
- Work you never committed is not in it. Git only records what it has seen.
Reading it
$ git reflogb81702c HEAD@{0}: reset: moving to HEAD~1
d3b92ec HEAD@{1}: checkout: moving from docs/12-windows-install-steps to main
95a0014 HEAD@{2}: commit: docs: add Windows steps to installation guide (#12)
d3b92ec HEAD@{3}: checkout: moving from main to docs/12-windows-install-steps
d3b92ec HEAD@{4}: commit: chore: add CI configuration and contributing guide
b81702c HEAD@{5}: commit: docs: add getting started guide and FAQ
28593d2 HEAD@{6}: commit: feat: add the trail list command
e653def HEAD@{7}: commit (initial): chore: start the trailguide projectRead it as a diary, newest first:
| Part | Means |
|---|---|
b81702c |
Where HEAD pointed after that action |
HEAD@{0} |
The most recent position; HEAD@{1} the one before it |
reset: moving to HEAD~1 |
What you did |
So HEAD@{2} above is the moment the Windows steps commit was made. That commit is not in git log any more, because the branch was reset, but its hash is right there.
git reflog --date=relative adds "11 seconds ago" style timing, which is easier to search when you are looking for "the thing I had before lunch".
Bringing something back
$ git reset --hard HEAD@{1}HEAD is now at d3b92ec chore: add CI configuration and contributing guideThat is the undo for a bad reset: find the line describing the state you want, and reset to it. The same works with the raw hash, which is safer to read than a counted position:
$ git reset --hard d3b92ecGit also keeps ORIG_HEAD, a bookmark for where you were before the last big move (a reset, a merge, a rebase, a pull). When you have just done one thing wrong, it is the fastest answer:
$ git reset --hard ORIG_HEADA branch, not just a commit
Deleting a branch deletes a name, not the commits. Git even prints the hash as it goes:
$ git branch -D docs/12-windows-install-stepsDeleted branch docs/12-windows-install-steps (was 95a0014).Recreating it is one command:
$ git switch -c docs/12-windows-install-steps 95a0014Switched to a new branch 'docs/12-windows-install-steps'If you did not note the hash, the reflog has it, and so does git fsck --lost-found, which lists commits nothing points at any more:
$ git fsck --lost-founddangling commit 95a001407cde528f92b4c796b2e359e301e2b863Per-branch reflogs
git reflog shows HEAD. Each branch has its own, which is often the clearer question to ask:
$ git reflog show maind3b92ec main@{0}: reset: moving to ORIG_HEAD
28593d2 main@{1}: reset: moving to HEAD~2
d3b92ec main@{2}: reset: moving to HEAD@{1}
b81702c main@{3}: reset: moving to HEAD~1
d3b92ec main@{4}: commit: chore: add CI configuration and contributing guide
b81702c main@{5}: commit: docs: add getting started guide and FAQ"Where was main yesterday morning?" is answered by git reflog show main, and main@{yesterday} is valid syntax for it.
What really is lost
The list is short, and every item is about work Git never recorded:
| Gone for good | Why |
|---|---|
Edits you never staged or committed, after git restore or git checkout -- |
Git never had a copy |
Uncommitted edits after git reset --hard |
Same |
Untracked files after git clean -fd |
Git never tracked them |
| A stash you dropped, after the reflog expires | Recoverable for a while, then not |
| Commits older than the reflog's expiry, once garbage collection runs | Roughly ninety days by default |
| Anything in a clone you deleted from disk | Not a Git question |
Everything else is a lookup. That is why the advice throughout this course is to commit early: a commit is the moment work becomes recoverable.
How to do it
The three commands worth memorising:
$ git reflog
$ git reset --hard HEAD@{1}
$ git reset --hard ORIG_HEADRead the reflog before acting. The temptation is to type a fix immediately; thirty seconds of reading tells you which line to aim at.
There is no reflog view. The Command Palette has Git: Undo Last Commit, which is a soft reset of one commit, and the Timeline view in the Explorer shows a file's history including local file snapshots taken by VS Code itself, which sometimes rescues uncommitted work Git never saw.
Git → Show History covers commits, but the real equivalent is Local History (right-click a file or folder → Local History → Show History), which is IntelliJ's own recording of every save, independent of Git. It is the best tool in this course for recovering work that was never committed.
Nothing on the platform corresponds to the reflog: it is local by definition. What the platform does keep is whatever you pushed, so a branch you pushed before breaking things locally can simply be fetched again.
The same. If the commit ever reached GitHub, it is on GitHub, and git fetch brings it back regardless of what your local repository looks like.
Common mistakes
- Panicking and running more commands. Each one adds a reflog entry but also moves you further from the state you want. Stop and read.
- Using
git reset --hardwith uncommitted changes in the working tree, which throws them away as a side effect. - Expecting the reflog to be on the server. It is yours alone.
- Assuming uncommitted work is recoverable. Only your editor's local history might have it.
- Waiting. The reflog expires; recover today, not next quarter.
Try it yourself
Goal: destroy a commit on purpose and bring it back.
- In your practice repository, make a commit: change a line in
docs/faq.md,git add,git commit -m "docs: test commit". - Confirm it is there with
git log --oneline -2. - Run
git reset --hard HEAD~1and confirm withgit log --oneline -2that it is gone. - Run
git reflogand find the line whose message iscommit: docs: test commit. - Run
git reset --hard <that hash>and confirm the commit is back. - Now delete a branch with
git branch -Dand recreate it from the hash Git printed.
Expected result: the commit returns, and you have done the recovery once in calm conditions, which is the point of the exercise.
Show solution
Step 4 is the skill. The reflog line you want is almost always identifiable by its message rather than its position, which is why reading it beats counting HEAD@{n} entries. Doing this once when nothing is at stake means you will remember it when something is.