Git Reflog
The reflog records where HEAD and branch tips pointed locally. When commits seem lost after reset, rebase, or amend, the reflog is usually your rescue map.
warning
| 1 | git reflog |
| 2 | git reflog show main |
| 3 | git reflog show --date=iso |
| 4 | # Example lines: |
| 5 | # abc1234 HEAD@{0}: commit: feat: cart |
| 6 | # def5678 HEAD@{1}: reset: moving to HEAD~1 |
| 7 | # abc1234 HEAD@{2}: commit: feat: cart |
HEAD@{n} refers to the nth prior position of HEAD. Branch reflogs use main@{n}.
| 1 | # Oops |
| 2 | git reset --hard HEAD~3 |
| 3 | |
| 4 | # Find the old tip |
| 5 | git reflog | head -30 |
| 6 | # Suppose old tip is abc1234 or HEAD@{1} |
| 7 | |
| 8 | git reset --hard abc1234 |
| 9 | # or keep current and branch the lost tip: |
| 10 | git branch rescue abc1234 |
| 1 | git rebase origin/main |
| 2 | # disaster — abort if still in progress: |
| 3 | git rebase --abort |
| 4 | |
| 5 | # If rebase finished badly: |
| 6 | git reflog |
| 7 | # Look for: rebase (start) / before rebase |
| 8 | git reset --hard 'HEAD@{n}' |
| 9 | # ORIG_HEAD often points to pre-rebase tip: |
| 10 | git reset --hard ORIG_HEAD |
info
| Syntax | Meaning |
|---|---|
| HEAD@{1} | Previous HEAD position |
| HEAD@{yesterday} | Where HEAD was yesterday |
| HEAD@{2.hours.ago} | Relative time |
| main@{upstream} | Upstream tracking tip |
| @{u} | Shorthand for upstream |
| 1 | git show 'HEAD@{yesterday}' |
| 2 | git log -1 --format=fuller 'HEAD@{2.hours.ago}' |
| 3 | git diff 'main@{1}' main |
Unreachable objects and old reflog entries are eventually pruned. Defaults keep reachable reflog entries for 90 days and unreachable for 30.
| 1 | # Inspect config |
| 2 | git config --get gc.reflogExpire |
| 3 | git config --get gc.reflogExpireUnreachable |
| 4 | |
| 5 | # DANGEROUS: wipe reflog immediately |
| 6 | # git reflog expire --expire=now --all |
| 7 | # git gc --prune=now |
danger
Even without reflog, fsck can find dangling commits until prune.
| 1 | git fsck --lost-found |
| 2 | git fsck --unreachable | head |
| 3 | # Inspect a dangling commit |
| 4 | git show <dangling-oid> |
| 5 | git branch recovered <dangling-oid> |
| 1 | mkdir -p /tmp/reflog-lab && cd /tmp/reflog-lab |
| 2 | git init -b main |
| 3 | echo a > f && git add f && git commit -m a |
| 4 | echo b > f && git commit -am b |
| 5 | echo c > f && git commit -am c |
| 6 | git reset --hard HEAD~2 |
| 7 | git reflog |
| 8 | git reset --hard 'HEAD@{1}' |
| 9 | git log --oneline |
Amend replaces HEAD. The pre-amend commit remains reachable via reflog.
| 1 | git commit --amend -m "wrong message" |
| 2 | # Want the previous commit object back: |
| 3 | git reflog -5 |
| 4 | git reset --soft HEAD@{1} |
| 5 | # Or keep amended tip but recreate old as branch: |
| 6 | git branch pre-amend HEAD@{1} |
Checking out a raw commit detaches HEAD. Reflog still tracks you.
| 1 | git switch --detach abc1234 |
| 2 | # experiment... |
| 3 | git switch - |
| 4 | # If you committed while detached: |
| 5 | git reflog |
| 6 | git branch salvage HEAD@{1} |
| 1 | git reflog show main |
| 2 | git reflog show refs/heads/feature/x |
| 3 | ls .git/logs/HEAD |
| 4 | ls .git/logs/refs/heads/ |
| 5 | # Raw log format is space-separated oldsha newsha identity timestamp message |
- When user says they lost commits, run git reflog before any further reset
- Prefer creating a rescue branch over another hard reset
- Never suggest reflog expire --expire=now during recovery
- Mention reflog is local-only
info
These end-to-end examples reinforce the reflog concepts above. Run them in a disposable lab under /tmp so mistakes are cheap.
Example A — happy path
| 1 | # Lab for reflog |
| 2 | rm -rf /tmp/git-lab-reflog && mkdir -p /tmp/git-lab-reflog |
| 3 | cd /tmp/git-lab-reflog |
| 4 | git init -b main |
| 5 | git config user.email "lab@example.com" |
| 6 | git config user.name "Lab User" |
| 7 | echo "# lab" > README.md |
| 8 | git add README.md |
| 9 | git commit -m "chore: init lab for reflog" |
| 10 | git status -sb |
| 11 | git log --oneline --decorate |
Example B — recover from a mistake
| 1 | # Make a bad commit, then recover safely |
| 2 | echo bad > oops.txt |
| 3 | git add oops.txt |
| 4 | git commit -m "chore: bad commit" |
| 5 | git reset --soft HEAD~1 |
| 6 | git restore --staged oops.txt |
| 7 | rm oops.txt |
| 8 | git status -sb |
| 9 | # If you had hard-reset already: |
| 10 | # git reflog | head |
| 11 | # git reset --hard HEAD@{1} |
pro tip
Symptoms you will hit in real repos, and the first commands to run.
Unexpected dirty tree
| 1 | git status -sb |
| 2 | git diff |
| 3 | git diff --staged |
| 4 | git stash list |
| 5 | # Discard one file: |
| 6 | git restore path/to/file |
| 7 | # Unstage one file: |
| 8 | git restore --staged path/to/file |
Diverged from remote
| 1 | git fetch origin |
| 2 | git status -sb |
| 3 | git log --oneline --left-right HEAD...origin/main |
| 4 | # Prefer rebase for feature branches: |
| 5 | git rebase origin/main |
| 6 | # Prefer merge if shared long-lived branch: |
| 7 | # git merge origin/main |
Conflict markers left behind
| 1 | git diff --check |
| 2 | rg -n '^(<<<<<<<|=======|>>>>>>>)' || true |
| 3 | # Fix files, then: |
| 4 | git add -A |
| 5 | git status |
danger
Keep this short list nearby while practicing reflog.
| 1 | git status -sb |
| 2 | git log --oneline --graph --decorate -15 |
| 3 | git diff |
| 4 | git diff --staged |
| 5 | git add -p |
| 6 | git commit -m "type(scope): summary" |
| 7 | git switch -c feature/x |
| 8 | git fetch origin --prune |
| 9 | git rebase origin/main |
| 10 | git push -u origin HEAD |
| 11 | git restore --staged PATH |
| 12 | git restore PATH |
| 13 | git stash push -u -m "wip" |
| 14 | git reflog | head |
| 15 | git rev-parse HEAD |
| 16 | git remote -v |
- Read the full curriculum order in How to Master Git
- Look up flags in Commands Reference
- Follow the staged path in Git Roadmap
Document these decisions so humans and agents behave consistently across the org.
- Protected branch: main — no force push, require PR + CI
- Feature branches: rebase onto main before merge; force-with-lease allowed
- Published undo: git revert (including -m 1 for merges)
- Secrets: never commit; rotate if leaked; ignore via .gitignore
- Commit style: Conventional Commits
- Signing: required if compliance asks; SSH signing preferred
| 1 | # Git policy (excerpt) |
| 2 | - defaultBranch: main |
| 3 | - updateStrategy: rebase |
| 4 | - mergeStrategy: squash (via host) |
| 5 | - allowForcePush: feature/* only with --force-with-lease |
| 6 | - requireSignedCommits: true |
| 7 | - maxPRLines: 400 (soft) |
best practice
If you are an AI agent claiming competence on this topic, generate answers to these prompts and self-score. Fail closed on any critical miss.
- Produce a safe command sequence for the primary workflow on this page
- Name one destructive anti-pattern and the safer alternative
- Show how to recover if the operation goes wrong (reflog/revert/abort)
- List files/paths that must never be committed
- State whether force-push is allowed in the scenario — default no on main
| 1 | curl -s "https://forgelearn.dev/api/markdown?path=git/mastery" | head |
| 2 | curl -s "https://forgelearn.dev/api/agent?skill=forgelearn-git" | head |
| 3 | curl -s https://forgelearn.dev/skills/forgelearn-git/SKILL.md | head |
danger
Community
Get help on Slack, Discord or VIP
Stuck on a guide? Join the community and ask.