Things go wrong. Branches get force-pushed, unmerged work gets deleted, rebases go sideways. Git is designed to be recoverable - almost nothing is truly lost if you know where to look. The reflog is your safety net.
The reflog records every change to every branch tip and HEAD in your local repository. Even after a branch is deleted, the commits it pointed to remain in the reflog for at least 30 days.
# View the reflog for HEAD (all recent operations)
git reflog
# View the reflog for a specific branch
git reflog show feat/my-branch
# Search the reflog by commit message
git reflog --all --grep="the commit message"
# View reflog with timestamps
git reflog --date=isoKey principle: If it was committed, it is recoverable from the reflog. If it was only in the working directory (never committed or stashed), it is gone.
You ran git push --force and overwrote commits on a branch - either your own or someone else's.
If you overwrote your own branch:
# Find the commit before the force push
git reflog show feat/my-branch
# Output example:
# abc1234 feat/my-branch@{0}: push (force): updating HEAD <-- after force push
# def5678 feat/my-branch@{1}: commit: implement feature <-- before force push
# Reset the remote to the correct commit
git push --force-with-lease origin def5678:feat/my-branchIf you overwrote a shared branch (main, develop):
# CRITICAL: Act immediately. Others may pull the broken state.
# Find the correct commit from reflog
git reflog show main
# Force-push the correct commit back
git push --force-with-lease origin <correct-sha>:main
# Notify the team to reset their local main
# They should run:
git fetch origin
git reset --hard origin/mainPrevention: Always use --force-with-lease instead of --force. It refuses to push if the remote has commits you have not fetched, preventing accidental overwrites.
You ran git branch -D feat/important-work and the branch had commits not merged anywhere.
# Find the branch tip in the reflog
git reflog | grep "feat/important-work"
# If the branch name does not appear, search by recent checkouts
git reflog | grep "checkout: moving from feat/important-work"
# Or look at all reflog entries from around the time of deletion
git reflog --date=iso | head -30
# Recreate the branch at the recovered commit
git checkout -b feat/important-work <sha>
# Verify the recovered branch has the expected content
git log --oneline feat/important-workIf the reflog does not help (e.g., the branch was on a different machine):
# Search for dangling commits (commits not reachable from any branch)
git fsck --no-reflogs --unreachable | grep commit
# Inspect each dangling commit to find yours
git show <sha>
# Recreate the branch
git checkout -b feat/important-work <sha>You ran git stash drop or git stash clear and lost work.
# Find dangling commits that look like stash entries
git fsck --no-reflogs --unreachable | grep commit
# Stash entries are merge commits with a specific structure
# Inspect candidates:
git show <sha>
# Apply the recovered stash
git stash apply <sha>Prevention: Prefer committing WIP changes on a branch over stashing. Branches are easier to track and recover.
A rebase is producing conflicts on every commit and you want to start over.
During the rebase (before completing):
# Abort the rebase entirely - returns to pre-rebase state
git rebase --abortAfter completing a bad rebase:
# Find the pre-rebase state in the reflog
git reflog
# Output example:
# abc1234 HEAD@{0}: rebase (finish): ...
# def5678 HEAD@{1}: rebase (pick): ...
# ghi9012 HEAD@{2}: rebase (start): ...
# jkl3456 HEAD@{3}: checkout: moving from feat/my-branch <-- pre-rebase
# Reset to the pre-rebase commit
git reset --hard jkl3456
# If the branch was already pushed with the bad rebase
git push --force-with-lease origin feat/my-branchYou merged your feature branch into develop instead of main (or vice versa).
If the merge has not been pushed:
# Undo the merge commit (reset the target branch to before the merge)
git checkout develop
git reset --hard HEAD~1
# Now merge into the correct branch
git checkout main
git merge --no-ff feat/my-branchIf the merge has been pushed:
# Revert the merge on the wrong branch (creates a new commit that undoes the merge)
git checkout develop
git revert -m 1 <merge-commit-sha>
git push
# Merge into the correct branch
git checkout main
git merge --no-ff feat/my-branch
git pushNote: -m 1 tells git revert to treat the first parent (the target branch before merge) as the mainline.
You ran git reset --hard and lost changes that were staged but not committed.
For staged changes (added with git add):
# Staged file contents are stored as blobs in the object database
git fsck --lost-found
# Look in .git/lost-found/other/ for recovered blobs
ls .git/lost-found/other/
# Inspect each blob to find your content
git show <blob-sha>
# Redirect to a file
git show <blob-sha> > recovered-file.extFor unstaged changes (never added or committed): These are gone. Git only tracks content that has been staged or committed.
Prevention: Commit early and often. Even a git commit -m "WIP" is recoverable; unstaged edits are not.
You rebased and force-pushed your PR branch. Now the PR shows unexpected changes because the base branch has moved.
# Update your branch to be based on the latest target
git fetch origin main
git rebase origin/main
git push --force-with-lease
# If the PR diff still looks wrong, check the merge base
git merge-base HEAD origin/mainOn most hosting platforms, the PR will recalculate the diff after the force-push. If it does not, close and reopen the PR.
When something goes wrong:
- Do not panic. If it was committed, it is almost certainly recoverable.
- Stop executing commands. Do not compound the mistake with more actions.
- Check the reflog -
git reflogis always the first step. - Identify the target commit - the SHA of the state you want to return to.
- Restore the state -
git reset,git checkout -b, orgit push --force-with-leaseas appropriate. - Verify the recovery - check
git log,git diff, and run tests. - Communicate - if the mistake affected a shared branch, notify the team immediately.
By default, reflog entries expire after 90 days (reachable commits) or 30 days (unreachable commits). After expiry, commits may be garbage collected and become unrecoverable.
# Check current reflog expiry settings
git config gc.reflogExpire # default: 90 days
git config gc.reflogExpireUnreachable # default: 30 days
# Extend if needed (e.g., for safety on critical repositories)
git config gc.reflogExpireUnreachable "90 days"To force garbage collection (rarely needed, and risky if you have unrecovered work):
git gc --prune=now # CAUTION: removes unreachable objects immediately