Git Recovery & History Surgery
Destroy work four different ways and get it back, then remove a secret from history and understand why that is not the fix.
- Time
- 45 min
- Level
- Intermediate
- Objectives
- 4 objectives
- Cost
- Free
Before you start
You will need
- git 2.30+
You will be able to
- Recover commits after a hard reset, a deleted branch or a bad rebase
- Choose between revert, reset and restore deliberately
- Remove a file from history, and know what that does not achieve
Cost — Free
— a local repository is enough.
Success criteria
0 of 4
The scenario#
You reset the wrong branch. A colleague force-pushed over your work. There is an API key in a commit from three weeks ago.
All three are recoverable, and knowing that changes how confidently you work.
Set up a repository to break#
mkdir /tmp/git-recovery && cd /tmp/git-recovery && git init
for i in 1 2 3 4 5; do echo "line $i" >> file.txt; git add -A; git commit -qm "commit $i"; done
git log --oneline1. reset --hard, undone#
git reset --hard HEAD~3
git log --oneline # two commits left
git reflog # every position HEAD has held
git reset --hard HEAD@{1} # backgit reflog is the repository's own undo history. A commit stays reachable
for about 90 days even when no branch points at it. Almost nothing done locally
is truly destructive.
2. A deleted branch#
git switch -c feature && echo work >> file.txt && git commit -qam "feature work"
git switch master
git branch -D feature # "permanently" deleted
git reflog | grep feature # the commit hash is still here
git switch -c feature-restored <hash>3. An abandoned rebase#
git rebase -i HEAD~3 # not available non-interactively; abort instead
git rebase --abort # puts everything back exactly as it was--abort is always available mid-rebase, and it is complete. Trying a rebase
costs nothing.
4. revert, reset or restore#
The three are not interchangeable, and picking the wrong one on a shared branch is how you create the next problem:
| Command | Does | Use when |
|---|---|---|
git restore <file> | Discards uncommitted changes to a file | You edited something by mistake |
git reset --hard <ref> | Moves the branch, discards commits | The work is local and unpushed |
git revert <commit> | Adds a commit undoing another | The commit is already pushed |
Never reset a branch other people have pulled. revert is the shared-branch
answer: history stays intact and everyone's clone still agrees.
git revert --no-edit HEAD
git log --oneline -2 # the original and its revert, both present5. A secret in history#
echo "AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG" > .env
git add .env && git commit -qm "add config"
echo "more work" >> file.txt && git commit -qam "unrelated"
git log --oneline -- .env # still there
git rm --cached .env -q && echo ".env" >> .gitignore
git commit -qm "stop tracking .env"
git log -p --all -S 'wJalrXUtnFEMI' | head -5 # the value is STILL in historyThat last command is the point. Removing the file going forward does nothing about the commits that already contain it, and every clone has them.
# git-filter-repo is the maintained tool; BFG is the alternative
pip install git-filter-repo
git filter-repo --path .env --invert-paths --force
git log --all --oneline -S 'wJalrXUtnFEMI' # goneThe order that actually matters:
- Rotate the credential. Assume it is compromised the moment it was pushed. This is the only step that protects anything.
- Then rewrite history.
- Then force-push and tell everyone with a clone to re-clone.
Doing 2 without 1 is theatre. The key was in a public repository, in CI logs, in forks, and quite possibly in a scraper's database within minutes.
When it goes wrong#
reflog does not show the commit
Reflog is per-clone and local. If the work was only ever on another machine or a deleted remote branch, it is not here.
filter-repo refuses to run
It requires a fresh clone by default. Use --force only when you understand it is rewriting this working copy.
Colleagues' branches break after a rewrite
Expected — every commit hash changed. They must re-clone or rebase onto the new history.
The secret still appears on GitHub after rewriting
GitHub keeps unreferenced commits accessible for a while and caches PR views. Contact support to purge, and rotate regardless.
The concept behind it
Next up
Lab 7 of 58 on the project path