EduFlow
Git & GitHub
0%

Section 4 · Professional Habits

Undoing Safely: Revert, Reset and the Reflog

18 min3 min readChoose wisely

Lesson at a glance

Choose the right undo for any mistake (restore, amend, revert or reset in its three modes) and use the reflog to recover commits you thought were gone.

  1. 1Pick the tool by location
  2. 2Amend: fix the last commit
  3. 3Revert: undo in public
  4. 4Reset: move the branch, three ways
  5. 5The reflog: Git’s flight recorder

No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.

Everyone breaks something in Git eventually. You commit to the wrong branch, push a bug, reset away a morning’s work. The good news: Git almost never throws data away, and nearly every mistake has a clean undo. The trap is reaching for whichever undo command you half-remember. Pick by asking one question: where does the mistake live?

Pick the tool by location

Where the mistake is Command Rewrites history?
Unstaged edits in a file git restore <file> No (but discards the edits)
Staged, not committed git restore --staged <file> No
Last commit, not pushed git commit --amend Yes, your local commit only
Local commits, not pushed git reset Yes, local only
Commits already pushed and shared git revert <commit> No

The table follows a single rule: once other people have a commit, you add new commits on top instead of changing it.

Amend: fix the last commit

Forgot a file or wrote a bad message, and haven’t pushed?

Terminal
git add contact.css
git commit --amend --no-edit              # same message, file included
git commit --amend -m "Add contact form"  # or a new message

Amend doesn’t edit the commit; it replaces it with a new one with a new ID. That’s fine locally and a problem if you’ve pushed, for the same reason rebase is.

Revert: undo in public

You merged a commit to main that broke the mobile nav. It’s pushed, and teammates have pulled. Don’t rewrite anything. Add a commit that reverses it:

Terminal
git revert HEAD          # if it's the latest commit
git revert 8f1e2aa       # or any commit by ID

Git creates a new commit, Revert "Move nav into header", that applies the exact opposite changes. History still shows the mistake and its fix, which is honest and safe: everyone gets the revert with a normal pull. To revert a merge commit, tell Git which parent to keep as the mainline, usually the first: git revert -m 1 <merge-commit>.

Sometimes only part of a commit was wrong. If the nav move was fine except for styles.css, you don’t need to revert the whole thing. Bring that one file back to how it was before the commit, check it, and commit the result as an ordinary fix:

Terminal
git restore --source=8f1e2aa~1 styles.css
git diff
git commit -am "Restore mobile nav styles"

That’s still “adding history, not rewriting it”, just with a smaller scope.

Reset: move the branch, three ways

git reset <commit> moves the current branch to another commit. What it does to the index and working tree depends on the mode, and this is where people get hurt.

A table of three rows. reset --soft moves the branch only; index and working tree keep the changes, which are now staged. reset --mixed, the default, moves the branch and resets the index; the working tree keeps the changes, now unstaged. reset --hard moves the branch and resets both index and working tree, so the changes are discarded. Branch (HEAD) Index Working tree --soft moved kept kept --mixed moved reset kept --hard moved reset overwritten --mixed is the default when you give no mode
The three reset modes and the three areas

Three situations, three modes:

Terminal
git reset --soft HEAD~2    # squash: the last two commits' changes become staged, ready for one commit
git reset HEAD~1           # un-commit (--mixed): changes return as unstaged edits to rework
git reset --hard HEAD~1    # discard: the commit and its changes are gone from the branch and files

A classic use: you committed to main when you meant to be on a branch. Commit or stash any uncommitted edits first (--hard would wipe them), create the branch where you are, then move main back:

Terminal
git branch contact-form        # the branch keeps your commit
git reset --hard origin/main   # main goes back to match GitHub
git switch contact-form

The reflog: Git’s flight recorder

Here’s the safety net that makes all of this less frightening. Every time HEAD moves (commit, switch, reset, rebase, merge), Git writes a line in the reflog:

Terminal
git reflog
Text
2b7f0c1 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~3
9e4a1d5 HEAD@{1}: commit: Add contact form validation
4c8b2e7 HEAD@{2}: commit: Add contact form
a71d3f0 HEAD@{3}: checkout: moving from footer to main

After that accidental git reset --hard HEAD~3, the three commits aren’t on any branch, but they still exist and HEAD@{1} points at the newest. Bring them back:

Terminal
git reset --hard HEAD@{1}          # put main back where it was
# or, more cautiously, keep them on a new branch to inspect first
git switch -c rescue 9e4a1d5

The reflog is local (it isn’t pushed or cloned) and entries expire: by default after 90 days, or 30 days for commits no branch can reach. That’s plenty of time to notice a mistake.

What the reflog can’t save is work that was never committed. git restore and git reset --hard overwrite uncommitted edits in your files, and no log knows about them. So the real safety habit is simple: commit early and often on your branch. You can always squash commits before review; you can’t recover edits you never committed.

Some work-in-progress isn’t ready for a commit, though. Next you’ll park it with git stash, and stop Git from tracking files it never should.

Key takeaways

  • Pick the undo by where the mistake lives (working tree, index, local commit or shared commit), not by which command you remember.
  • git revert <commit> adds a new commit that cancels an old one, which is the safe way to undo anything already pushed.
  • git reset moves the current branch: --soft keeps changes staged, --mixed keeps them unstaged, --hard discards them.
  • git commit --amend replaces the last commit, so use it only before pushing.
  • git reflog lists where HEAD has been, so commits lost to a reset or rebase can be found and restored for weeks afterwards.

Further reading

Check your understanding

4 questions

Question 1 of 4

You committed “Add contact form” a minute ago, haven’t pushed, and realise you forgot to stage contact.css. What’s the cleanest fix?

Question 2 of 4

Yesterday you merged a commit to main that broke the mobile nav. It’s pushed and your teammates have pulled. How do you undo it?

Question 3 of 4

You run git reset --soft HEAD~2 on two unpushed commits. Where are their changes now?

Question 4 of 4

You ran git reset --hard HEAD~3 by mistake and three commits vanished from git log. How do you get them back?

Practice

Choose wisely

Pick the undo

Your most recent commit on main, 8f1e2aa Move nav into header, broke the mobile menu. You pushed it an hour ago and two teammates have already pulled. git status shows a clean working tree.

Text
8f1e2aa (HEAD -> main, origin/main) Move nav into header
c39be11 Add projects page
b2e04f3 Add navigation bar with base styles

Which command should you run before pushing again?

Pick the undo