skip to content

questions

20

After git reset --hard discarded your last three commits, how do you get them back?

level: juniorimportance: must knowfreq 74%

answer

  1. Commits are not deleted, pointers move
  2. HEAD remembers everywhere it has been
  3. One local log per repository, newest first
  4. git reflog, then git branch <name> <hash>

basics

~20 s

Nothing was deleted: git reset --hard only moved the branch pointer. Find the old tip's hash with git reflog, verify it with git show, then re-anchor it using git branch rescue <hash> or git reset --hard <hash>.

solid answer

~40 s

`git reset --hard` rewrites a ref and forces the index and working tree to match it; the commit objects themselves are untouched and simply become unreachable. So the first move is `git reflog`, which lists every position HEAD has held, most recent first, with the reason (`commit`, `reset`, `rebase`, `checkout`). I find the entry from just before the reset, confirm it with `git show <hash>` or `git log --oneline <hash> -3`, then re-anchor it — usually `git branch rescue <hash>`, because that creates a real ref without disturbing my current state, and I can merge or cherry-pick from there. `git reset --hard ORIG_HEAD` is the quick version immediately after the mistake. The one thing that does not come back is uncommitted, never-staged work: no object was ever written for it.

code

console · 9 lines
console
$ git reflog
8f1c2a9 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~3
4b7de10 HEAD@{1}: commit: add retry backoff
0c9a331 HEAD@{2}: commit: extract client factory
77ab512 HEAD@{3}: commit: parse timeout config

$ git show 4b7de10 --stat
$ git branch rescue 4b7de10
$ git log --oneline rescue -3

go deeper

for a junior

Memorise the two-step reflex: run git reflog, then git branch <name> <hash>. Being able to say out loud that reset --hard moves a pointer rather than deleting commits is most of the credit here.

for a middle

Explain the mechanics: commits are content-addressed objects, a branch is a file holding one hash, and the reflog is a local append-only log of HEAD movements. Note that uncommitted changes are the only real casualty.

for a senior

Show the incident habits: verify the hash with git show before acting, prefer an additive rescue branch over a second destructive command, and know that the recovery window is bounded by reflog expiry and garbage collection.

for a principal

Frame it as blast radius. Argue for practices that make this a non-event — frequent WIP commits, pushing topic branches, and treating any command that rewrites refs on shared branches as requiring a recorded pre-image.

## What `git reset --hard` actually does Git keeps three pieces of state: the working tree (files on disk), the index (the staging area), and HEAD (the commit your current branch points at). `git reset --hard <target>` moves the current branch to `<target>` and then forces both the index and the working tree to match that commit. The destructive-sounding part is only the working-tree overwrite. The command does not delete a single commit object. ## Why the commits still exist A commit is an immutable object stored under `.git/objects`, addressed by the hash of its own content. A branch is nothing more than a small file under `.git/refs/heads/` (or a line in `.git/packed-refs`) containing one hash. Moving a branch backwards leaves the old commits with no ref pointing at them — they become *unreachable*, which is not the same as deleted. Unreachable objects sit in the object database until garbage collection eventually prunes them, which normally takes weeks. ## Step 1 — find the hash in the reflog Every time HEAD moves, Git appends a line to `.git/logs/HEAD`. `git reflog` prints that log newest-first, each line tagged with the operation that caused the move: `commit`, `commit (amend)`, `reset`, `rebase (finish)`, `checkout`, `merge`, `pull`. The entry immediately below your `reset:` line is the tip you had before the reset. `git reflog show <branch>` does the same for one branch's own log, and `git log -g --oneline` (`--walk-reflogs`) walks reflog entries with full log formatting. The reflog is strictly local and per-repository. It is not fetched, not pushed, and a fresh clone starts with an empty one — so this recipe only works in the clone where the mistake happened. ## Step 2 — verify before you act Abbreviated hashes look alike after a bad day. `git show <hash>` prints the commit and its diff; `git log --oneline <hash> -5` shows it plus its ancestry. Confirm you have the right tip *before* moving anything, since a second wrong `--hard` just adds another layer to unwind. ## Step 3 — re-anchor the commits Recovery means making the commits reachable again from some ref: - `git branch rescue <hash>` — safest. It creates a ref without touching your current branch or working tree, so the commits are protected from garbage collection and you can inspect, merge, or cherry-pick at leisure. - `git reset --hard <hash>` — puts the current branch back exactly where it was. Fine when your working tree is clean and you are certain. - `git cherry-pick <hash>` — when you only want some of the lost commits on top of where you are now. There is also a shortcut: `git reset` records the pre-reset HEAD in `ORIG_HEAD`, so `git reset --hard ORIG_HEAD` undoes the reset directly. It holds only the most recent such operation, so it is a first-minute tool, not a general one. ## What genuinely does not come back `git reset --hard` also blows away modifications to tracked files that were never committed. Those never became objects, so there is nothing to recover — this is the real loss, and it is why "commit early, even a WIP commit" is good advice. One partial exception: content you had run `git add` on was hashed into a blob object at that moment, so it survives as a dangling blob and can be dug out with `git fsck --lost-found`, though you must work out which file each blob was. Untracked files are not removed by `reset --hard` at all; that is `git clean`. ## The clock is running Unreachable commits are recoverable until garbage collection prunes them. Reflog entries expire on their own schedule and unreachable loose objects are pruned after a grace period, so if you discover the loss weeks later, or someone has run an explicit prune, the window may already be closed. While you are recovering, avoid commands that trigger housekeeping and do not run an aggressive prune. ## The habit interviewers are listening for The answer they want is "reflog first, panic never", plus the reflex of creating a rescue branch rather than issuing a second destructive command. Candidates who say the work is gone, or that they would re-clone from the remote, are telling you they do not understand that commits are content-addressed objects and branches are just pointers.

  • What is genuinely unrecoverable after a hard reset?
    Modifications to tracked files that were never committed. No object was ever written for them, so there is nothing in the object database to find. Content you had staged with git add is a partial exception: it exists as a dangling blob and can be dug out with git fsck --lost-found, though you have to work out which file it belonged to.
  • Why create a rescue branch instead of running another reset --hard?
    A branch is additive: it makes the lost commits reachable again without moving your current branch or overwriting your working tree, so a misidentified hash costs nothing. A second hard reset is another destructive operation performed while you are already unsure of the state, and it can bury changes you have made since.
  • The commits were made in a teammate's clone. Does your reflog help?
    No. Reflogs are local files under .git/logs and are never transferred by fetch, push, or clone. Recovery has to happen in the repository where the commits were created, or from a remote-tracking ref if they were pushed before being lost. In a fresh clone there is no prior HEAD history to consult.

Resetting a branch is like moving a bookmark, not shredding the pages. The pages are still in the book; the reflog is the list of every page the bookmark has sat on.

saying these in an interview costs you the question

  • Says reset --hard permanently deletes the commits
  • Proposes re-cloning from the remote to recover local commits
  • Believes the reflog lives on the server and is fetched
  • Thinks git revert undoes a local hard reset
  • Expects uncommitted working-tree edits to return with the commits

context

open as a page

In Git, how does git reflog differ from git log?

level: juniorimportance: must knowfreq 70%

basics

~20 s

git log walks commit ancestry from a ref, showing shared project history. git reflog lists a local journal of where HEAD or a branch has pointed over time, newest first, including commits no longer reachable from any branch.

open as a page

In Git, how do you unstage a file without losing the edits in your working tree?

level: juniorimportance: must knowfreq 76%

basics

~10 s

Run git restore --staged <file>. It rewrites that path in the index from HEAD, leaving the file on disk exactly as you edited it. The older equivalent is git reset -- <file>.

open as a page

In Git, what is the difference between git stash pop and git stash apply?

level: juniorimportance: must knowfreq 74%

basics

~20 s

Both restore a saved stash into your working tree; pop also deletes the stash entry afterwards, while apply leaves it in the list. If a pop hits a conflict, the entry is kept, so nothing is lost.

open as a page

In Git, how does HEAD@{2} differ from HEAD~2?

level: middleimportance: must knowfreq 58%

basics

~10 s

HEAD~2 is graph navigation: two commits back along parent links from the current commit. HEAD@{2} is reflog navigation: the commit HEAD pointed at two ref movements ago, regardless of ancestry.

open as a page

In Git, what is the difference between reset --soft, --mixed, and --hard?

level: middleimportance: must knowfreq 82%

basics

~20 s

All three move the current branch pointer to the target commit. --soft stops there, leaving the index and working tree untouched; --mixed also resets the index; --hard additionally overwrites the working tree, destroying uncommitted changes.

open as a page

How do you restore a branch that was deleted with git branch -D?

level: middleimportance: should knowfreq 55%

basics

~20 s

Deleting a branch removes only its ref and its own reflog, not its commits. Recover the tip hash from the message git branch -D printed, from HEAD's reflog, or from git fsck, then recreate it with git branch <name> <hash>.

open as a page

In Git, which operations append reflog entries, and which refs keep a reflog at all?

level: middleimportance: should knowfreq 42%

basics

~10 s

Any update of a journalled ref appends an entry: commit, amend, checkout, switch, reset, merge, rebase, pull, cherry-pick, fetch. By default core.logAllRefUpdates journals HEAD, branches under refs/heads, remote-tracking refs, and notes — not tags.

open as a page

In Git, what does git clean -fdx remove, and why preview it with -n first?

level: middleimportance: should knowfreq 48%

basics

~20 s

git clean -fdx deletes untracked files (-f), descends into untracked directories (-d), and also removes files your ignore rules exclude (-x) — build output, local env files, dependency folders. Nothing it deletes is in Git, so -n previews the list first.

open as a page

Why did Git split git checkout into the git switch and git restore commands?

level: middleimportance: should knowfreq 52%

basics

~20 s

git checkout did two unrelated jobs: moving HEAD to another branch or commit, and overwriting files from a tree. Git 2.23 split them so switch handles refs and restore handles file content, removing an ambiguous and silently destructive interface.

open as a page

How does Git store a stash internally — what commits and refs does git stash push create?

level: middleimportance: should knowfreq 36%

basics

~20 s

A stash is ordinary commits. git stash push writes a commit whose tree is your working tree, with your HEAD commit as first parent and a commit recording the index state as second; refs/stash points at it, and stash@{n} indexes that ref's reflog.

open as a page

Why does git stash leave new untracked files behind, and which flags include them?

level: middleimportance: should knowfreq 55%

basics

~20 s

By default git stash only records tracked content — index changes and modifications to files Git already knows. Untracked files are invisible to it. Use -u (--include-untracked) to add untracked files, or -a (--all) to include ignored files too.

open as a page

When the reflog has no entry for a lost commit, how do you find it with git fsck?

level: seniorimportance: should knowfreq 40%

basics

~20 s

git fsck walks the whole object database and reports objects nothing points at. Use git fsck --lost-found to write dangling commits into .git/lost-found/commit/, inspect candidates with git show, then re-anchor the right one with git branch.

open as a page

What closes the window for recovering unreachable commits in Git, and how do you keep it open?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Garbage collection closes it. Unreachable commits survive until gc expires the reflog entries holding them and prunes objects past the grace period, so recovery work starts by stopping housekeeping — set gc.auto to 0 and never run a prune with an immediate expiry.

open as a page

How long do Git reflog entries survive, and which config keys control their expiry?

level: seniorimportance: should knowfreq 40%

basics

~10 s

Reflog entries are pruned during garbage collection: gc.reflogExpire drops entries older than 90 days by default, and gc.reflogExpireUnreachable drops entries for commits unreachable from the ref after 30 days. Both accept never.

open as a page

In Git, what is ORIG_HEAD, and why can it be stale after two operations in a row?

level: seniorimportance: should knowfreq 36%

basics

~20 s

ORIG_HEAD is a single ref Git overwrites with the previous HEAD position whenever a command moves HEAD drastically — reset, merge, rebase, am. Because it holds only one value, a second such command overwrites it, so it points one step back, not to the original.

open as a page

Your git stash pop conflicts — what state is the repository in and how do you finish safely?

level: seniorimportance: should knowfreq 48%

basics

~20 s

You have conflict markers in the affected files, those paths unmerged in the index, and the stash entry still in the list because pop only drops after a clean apply. Resolve, git add the files, then drop the entry by hand.

open as a page

Why is Git's reflog not a substitute for a backup or a team-wide safety net?

level: principalimportance: should knowfreq 28%

basics

~20 s

The reflog is per-repository local state: never pushed, fetched, or cloned, pruned after weeks, absent from bare repositories by default, and covering only refs that moved in that one working copy. It dies with the disk it sits on.

open as a page

How do you recover a stash entry that was removed with git stash drop?

level: middleimportance: nice to knowfreq 26%

basics

~20 s

A stash entry is a real commit, and dropping it removes only the reflog slot naming it. If you still have the hash the drop printed, git stash apply <hash> works directly; otherwise find it among unreachable commits with git fsck and filter for the WIP message.

open as a page

What does git stash push --keep-index do, and when would you use it?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

It stashes everything as usual but leaves the staged changes in place in the index and working tree, so you can build and test exactly the content you are about to commit, with all unstaged work temporarily out of the way.

open as a page