cd ../writing
// git · disaster recovery

The git recovery guide — undoing every mistake.

Most git "disasters" are recoverable. The data is still there — git almost never actually deletes anything for 30 days. You just need to know where to look. This is the working field guide: each scenario, the exact diagnostic, the exact recovery command. Print it out and tape it next to your monitor.

10 scenarios reflog is your friend 30 days recovery window © use freely

01The most important command — reflog

Before anything else: git reflog. The reflog is git's local history of where your branches have pointed. Every commit, reset, rebase, checkout, merge — all recorded with a timestamp and a unique reference. The reflog is local-only and retained for 30 days by default.

✓ view the reflog
$ git reflog
a1b2c3d HEAD@{0}: reset: moving to HEAD~3
e4f5g6h HEAD@{1}: commit: Add feature X
i7j8k9l HEAD@{2}: commit: Refactor utils
m0n1o2p HEAD@{3}: pull: Fast-forward

Every line is a state your repository was in. If you've lost work, the SHA of the commit you wanted is almost certainly here. To go back to any state: git reset --hard HEAD@{2}.

The reflog is the answer to 80% of git disasters. Memorize the command.

02You committed to the wrong branch

Common: you meant to commit on feature-x but you were still on main. The commit is on main, you need it on feature-x.

✓ move the commit
# If you haven't pushed yet:
$ git log --oneline -1            # note the SHA
$ git reset --hard HEAD~1          # undo on main
$ git checkout feature-x
$ git cherry-pick <SHA>             # apply to feature branch

If you pushed already, don't reset main — that requires a force push and rewrites history others may have pulled. Instead, cherry-pick the commit to feature-x and revert it on main with git revert.

03Undo your last commit (keep the changes)

You committed too early — the message is wrong, or you want to add more files.

✓ soft reset keeps your changes staged
$ git reset --soft HEAD~1
# Your changes are staged, ready to re-commit
$ git commit -m "Better message"

Three reset modes:

  • --soft — moves HEAD, keeps changes staged. "Re-do the commit"
  • --mixed (default) — moves HEAD, keeps changes unstaged. "Re-stage and re-commit"
  • --hard — moves HEAD, throws away changes. "Pretend I never did that"

04You force-pushed and broke main

Worst-case scenario: you ran git push --force on main and overwrote commits other people had. Don't panic. The reflog still has them.

✓ recover from a bad force-push
$ git reflog
# Find the SHA of where main pointed before the force-push

$ git reset --hard <good-SHA>
$ git push --force-with-lease       # restore on remote

--force-with-lease is safer than --force: it refuses to push if someone else has pushed in the meantime, preventing you from overwriting their work a second time.

Prevention: set git config --global push.default current and never use --force on shared branches. Use --force-with-lease instead, and only on your own feature branches.

05You deleted a branch with unmerged commits

You ran git branch -D feature-x and git happily deleted it. The branch is gone, but the commits aren't.

✓ recover the deleted branch
$ git reflog
# Find the last commit that was on feature-x (look for "checkout: moving from feature-x")

$ git checkout -b feature-x <SHA>

If the reflog doesn't show what you need, run git fsck --lost-found. This scans every dangling object in the repo and surfaces commits that aren't reachable from any branch. The lost commit will be in there.

06You dropped a stash

Stashes have their own reflog. git stash drop doesn't immediately delete — it just removes the reference. The commit is still in the repo.

✓ recover a dropped stash
$ git fsck --no-reflog | grep dangling
# Lists dangling commits — your stash is one of them

$ git show <SHA>          # inspect the contents
$ git stash apply <SHA>  # reapply it

07You completed a bad merge

You merged a feature branch into main and the result is broken. You haven't pushed yet.

✓ undo a merge
$ git reset --hard HEAD~1
# For a merge commit, HEAD~1 is the parent on main
# HEAD^2 would be the parent from the feature branch

If you've pushed the merge, don't reset. Revert it instead:

✓ revert a pushed merge
$ git revert -m 1 <merge-SHA>
# -m 1 means: keep the changes from the first parent (main)
# and undo the changes from the second parent (feature branch)

08You deleted a file in a commit and need it back

You deleted important.js three commits ago. You need it back.

✓ restore a file from any past commit
# Find when the file was deleted
$ git log --diff-filter=D --summary -- important.js

# Restore it from the commit BEFORE deletion
$ git checkout <commit-before-deletion>^ -- important.js
$ git commit -m "Restore important.js"

The ^ after the SHA means "parent of." So <deletion-commit>^ is the commit where the file still existed.

09You committed a secret

You committed an API key, password, or .env file. Even if you remove it in the next commit, the secret is in your history forever — and if you've pushed, it's compromised.

Step 1: rotate the secret immediately. Assume it's been scraped by bots within seconds of pushing to a public repo. Don't waste time on git first.

Step 2: rewrite history to remove it. The cleanest tool is git filter-repo (Python package, replaces the older filter-branch):

✓ remove a file from all history
$ pip install git-filter-repo
$ git filter-repo --path .env --invert-paths
$ git push --force-with-lease --all

This rewrites every commit that touched .env, removing it. Then force-push all branches. Everyone with a clone needs to delete their local repo and re-clone — their history no longer matches yours.

10You botched a cherry-pick or rebase

You started a rebase, hit conflicts, made a mess. You want to abort and go back.

✓ abort an in-progress operation
$ git rebase --abort
$ git cherry-pick --abort
$ git merge --abort

# If you've already finished and want to undo:
$ git reflog
$ git reset --hard HEAD@{n}   # where n is before the rebase started

11Prevention — habits that save you

Most disasters are preventable:

  • Always check git status before git commit — confirms what's being committed.
  • Always check git log --oneline before pushing — confirms what's going up.
  • Use --force-with-lease, never --force — adds a safety check.
  • Protect main on GitHub/GitLab. Require pull requests. Block force pushes. Block direct commits.
  • Configure git config --global rerere.enabled true — git records how you resolve conflicts and replays them automatically next time you hit the same conflict.
  • Run a pre-commit hook for secrets. git-secrets from AWS Labs catches common patterns before they're committed.

The principle

Git is a content-addressable database. It almost never deletes data — it just removes references. As long as the reflog hasn't been garbage-collected (which is 30 days by default, configurable with gc.reflogExpire), your data is recoverable. The trick is knowing where to look. git reflog and git fsck --lost-found together cover almost every recovery scenario.

Most engineers are afraid of git because they don't trust it. Once you've recovered from a few "disasters," that fear inverts: you trust git completely, and you experiment freely, because you know nothing is actually lost. That confidence is the dividend of learning the recovery commands. It pays back every week.