Fixing Mistakes in Git¶
reset: moving the branch pointer backward¶
git reset moves the current branch's pointer to a different commit
— what it does to the staging area and working directory depends on
the flag:
--soft is the safest — it un-commits without touching any files,
useful for "I want to redo my last commit message or split it into
smaller commits" while keeping all the actual changes staged and
ready. --hard is the most destructive — it discards uncommitted
work in the working directory, not just the commits being reset
past; this is the flag most likely to lose real work if run without
checking git status first.
revert: undoing without rewriting history¶
git revert <commit> creates a new commit whose change is the
inverse of the target commit — it doesn't remove or rewrite anything,
it adds a new commit on top. This is what makes revert safe on
shared/public branches where reset --hard followed by a force-push
would not be: everyone else's history stays valid, they just pull one
more commit like any other.
reflog: the actual safety net¶
Every time HEAD moves — a commit, a checkout, a reset, a rebase —
Git records it in the reflog, a local, private log of where HEAD has
pointed, kept even for commits that are no longer reachable from any
branch.
git reflog
# a1b2c3d HEAD@{0}: reset: moving to HEAD~3
# e4f5g6h HEAD@{1}: commit: fix the thing
# ...
git reset --hard e4f5g6h # recover the "lost" commit
After a reset --hard that turns out to have discarded commits that
were actually needed, or a rebase gone wrong, the commits usually
still exist — they're just no longer pointed at by any branch, so
git log doesn't show them and they'll eventually be garbage
collected (not immediately — Git keeps unreferenced objects for a
grace period, default 90 days). The reflog is how to find the exact
hash to recover before that happens.
cherry-pick: applying one commit elsewhere¶
git cherry-pick a1b2c3d
Takes the change introduced by one specific commit and replays it as
a new commit on the current branch — useful for "this one bugfix on
main also needs to land on the release branch" without merging
everything else main has. Same replay mechanism as rebase (a new
commit object, different hash, same resulting change), just for a
single hand-picked commit instead of a whole range.
Common pitfall¶
Running git reset --hard to discard a bad merge or rebase, without
first checking git status for uncommitted changes, can silently
destroy work that was never committed at all — reset --hard doesn't
distinguish "changes from commits being reset past" from "changes that
were sitting uncommitted in the working directory," it overwrites the
working directory to match the target commit regardless. git stash
before any --hard operation is a cheap habit that turns a
potentially unrecoverable mistake into a recoverable one — a stash
can always be reapplied later even if the reset itself needed to
happen.