Skip to main content
PProfileMatch

Undo Changes in Git: Restore, Revert, Reset and Reflog Explained

DevOpsLesson 4 of 47 min read

What you'll learn

  • Choose between restore, amend, revert and reset for a given mistake
  • Explain soft, mixed and hard reset in terms of the three areas
  • Recover lost commits and deleted branches with git reflog
  • Fix a pushed commit without damaging teammates' history

On this page

  1. Pick the tool by the situation
  2. restore: undo uncommitted edits
  3. amend: fix the last commit
  4. revert: undo a commit by adding another
  5. reset: move the branch pointer
  6. reflog: the safety net
  7. Amending after you pushed
  8. Common mistakes
  9. How this shows up in projects and interviews

You run git reset --hard to clean up and the file you spent the afternoon on is gone. Whether it can come back depends on one question: was it ever committed? Git is forgiving about committed work and ruthless about uncommitted work, and most scary-looking undo problems are solved by asking which of the two you have. This lesson uses a household budget repository to walk through every undo tool, from the gentlest to the dangerous one.

Pick the tool by the situation

SituationReach forRewrites history?
Edited a file, want the old version backgit restore <file>No (but the edit is lost for good)
Staged a file by mistakegit restore --staged <file>No
Last commit has a typo or missed a filegit commit --amendYes, replaces the last commit
A pushed commit was wronggit revert <commit>No, adds a new commit
Local commits should vanish or be redonegit resetYes, moves the branch
You reset too far or deleted a branchgit reflogNo, it is how you recover

restore: undo uncommitted edits

The repository has three commits, one per month. We scribble a wrong line, check it, and throw it away. Then we stage the same wrong line and unstage it, and finally pull an older version of the file out of history.

Terminal
$ git log --oneline
483c811 Add March budget with new rent
0834846 Add February budget
d68bad5 Add January budget

$ echo 'mar,coffee,9999' >> budget.csv

$ git status -s
 M budget.csv

$ git restore budget.csv

$ git status -s

$ echo 'mar,coffee,9999' >> budget.csv

$ git add budget.csv

$ git status -s
M  budget.csv

$ git restore --staged budget.csv

$ git status -s
 M budget.csv

$ git restore budget.csv

$ git restore --source=HEAD~2 budget.csv

$ cat budget.csv
month,item,amount
jan,rent,900
jan,groceries,310

$ git restore budget.csv

$ git status -s

git restore budget.csv copies the version from the index (or, if nothing is staged, the last commit) over your file. --staged only moves the index; your edits stay. --source=HEAD~2 reads from two commits back, here the January version, and was undone by a plain restore.

amend: fix the last commit

A typo in a message, or a file you forgot to add, is easy to fix while the commit is still local.

Terminal
$ echo 'mar,internet,40' >> budget.csv

$ git commit -am "Add internet bil"
[main 6c63353] Add internet bil
 1 file changed, 1 insertion(+)

$ git commit --amend -m "Add internet bill"
[main e8964b0] Add internet bill
 Date: Mon Oct 5 08:54:52 2026 +0530
 1 file changed, 1 insertion(+)

$ git log --oneline -3
e8964b0 Add internet bill
483c811 Add March budget with new rent
0834846 Add February budget

The hash changed (6c63353 to e8964b0) because an amended commit is a new commit that replaces the old one. That is harmless locally and a problem once the commit is pushed, as the last section shows.

revert: undo a commit by adding another

Suppose someone pushed a bad commit that set March rent to zero. You cannot delete it from shared history, but you can add a commit that applies the opposite change.

Terminal
# edit budget.csv: change "mar,rent,950" to "mar,rent,0" (a wrong fix)

$ git commit -am "Fix rent typo"
[main 3ec2e02] Fix rent typo
 1 file changed, 1 insertion(+), 1 deletion(-)

$ git revert --no-edit HEAD
[main 2c6be42] Revert "Fix rent typo"
 Date: Mon Oct 5 08:54:53 2026 +0530
 1 file changed, 1 insertion(+), 1 deletion(-)

$ git log --oneline -4
2c6be42 Revert "Fix rent typo"
3ec2e02 Fix rent typo
e8964b0 Add internet bill
483c811 Add March budget with new rent

$ tail -n 3 budget.csv
mar,rent,950
mar,groceries,320
mar,internet,40

The bad commit stays in the log and the revert sits on top of it, so everyone's history remains consistent. This is the right tool for anything already pushed. Reverting a merge commit needs an extra -m option to choose a parent; read the docs for that case before trying it.

reset: move the branch pointer

git reset <commit> moves the current branch to another commit. The mode decides what happens to the index and your files.

ModeBranch movesIndexWorking tree
--softYesLeft as is (changes stay staged)Left as is
--mixed (default)YesReset to match the new commitLeft as is (changes stay as edits)
--hardYesResetReset: uncommitted changes are destroyed
Terminal
$ echo 'apr,electricity,65' >> budget.csv

$ git commit -am "Add April electricity"
[main b511d14] Add April electricity
 1 file changed, 1 insertion(+)

$ git reset --soft HEAD~1

$ git status -s
M  budget.csv

$ git reset
Unstaged changes after reset:
M	budget.csv

$ git status -s
 M budget.csv

$ git restore budget.csv

$ git status -s

After --soft, the April line is staged as if you had never run git commit. Plain git reset unstages it, leaving an unstaged edit. The git restore at the end discarded the edit, so the third form, --hard, would have done all of that in one go and with no warning. Use reset for commits that only exist on your machine.

reflog: the safety net

Git records every place HEAD has been in the reflog, a local diary of moves. Commits you can no longer reach from a branch are still in the object store for a while, and the reflog tells you their hashes. Below, a hard reset appears to delete a commit, and then a deleted branch loses its only commit. Both are recovered in one command.

Terminal
$ echo 'apr,electricity,65' >> budget.csv

$ git commit -am "Add April electricity"
[main 6e7d40a] Add April electricity
 1 file changed, 1 insertion(+)

$ git reset --hard HEAD~1
HEAD is now at 2c6be42 Revert "Fix rent typo"

$ tail -n 2 budget.csv
mar,groceries,320
mar,internet,40

$ git reflog -3
2c6be42 HEAD@{0}: reset: moving to HEAD~1
6e7d40a HEAD@{1}: commit: Add April electricity
2c6be42 HEAD@{2}: reset: moving to HEAD

$ git reset --hard HEAD@{1}
HEAD is now at 6e7d40a Add April electricity

$ tail -n 2 budget.csv
mar,internet,40
apr,electricity,65

$ git switch -c savings-plan
Switched to a new branch 'savings-plan'

$ echo 'savings,goal,5000' >> budget.csv

$ git commit -am "Add savings goal"
[savings-plan 33a1440] Add savings goal
 1 file changed, 1 insertion(+)

$ git switch main
Your branch is ahead of 'origin/main' by 4 commits.
  (use "git push" to publish your local commits)
Switched to branch 'main'

$ git branch -D savings-plan
Deleted branch savings-plan (was 33a1440).

$ git reflog -4
6e7d40a HEAD@{0}: checkout: moving from savings-plan to main
33a1440 HEAD@{1}: commit: Add savings goal
6e7d40a HEAD@{2}: checkout: moving from main to savings-plan
6e7d40a HEAD@{3}: reset: moving to HEAD@{1}

$ git branch savings-plan HEAD@{1}

$ git log --oneline -2 savings-plan
33a1440 Add savings goal
6e7d40a Add April electricity

HEAD@{1} means where HEAD was one move ago. The same trick revives a deleted branch: find the last commit in the reflog and create a branch at it. The reflog is local to your clone, and old entries are eventually cleaned up (the retention is configurable, and is measured in weeks), so recover soon after a mistake.

Amending after you pushed

Amend, reset and rebase all create new commits with new hashes. If the old commit is already on the server, the server sees your new history as a fork and refuses it.

Terminal
$ echo 'apr,water,22' >> budget.csv

$ git commit -am "Add water bil"
[main 48a4afd] Add water bil
 1 file changed, 1 insertion(+)

$ git push
To ../budget-origin.git
   483c811..48a4afd  main -> main

$ git commit --amend -m "Add water bill"
[main d0f643c] Add water bill
 Date: Mon Oct 5 08:54:56 2026 +0530
 1 file changed, 1 insertion(+)

$ git push
To ../budget-origin.git
 ! [rejected]        main -> main (non-fast-forward)
error: failed to push some refs to '../budget-origin.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. If you want to integrate the remote changes,
hint: use 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

$ git push --force-with-lease
To ../budget-origin.git
 + 48a4afd...d0f643c main -> main (forced update)

The first push was rejected as non-fast-forward. --force-with-lease overwrites the remote branch only if it still points where you last saw it, so you cannot flatten a teammate's new commit by accident; a bare --force has no such check. Reserve either one for a branch only you use, and prefer git revert for anything others may have pulled.

Common mistakes

  • Running `git reset --hard` with uncommitted work. Symptom: edits disappear and git status is clean. Committed work is recoverable through git reflog; uncommitted work is not. Commit or stash first.
  • Using `reset` on pushed commits. Symptom: ! [rejected] ... (non-fast-forward), or after a force push, teammates see their pulls break. Use git revert for shared history.
  • Amending a commit someone already pulled. Symptom: duplicate commits and confusing conflicts after their next pull, because the old and new versions coexist.
  • Assuming a deleted branch is gone. Symptom: panic after git branch -D. Run git reflog, find the commit and git branch <name> <hash> brings it back.

How this shows up in projects and interviews

Everyone eventually commits to the wrong branch or pushes something they should not have. The professional move is to stop, check git status and git reflog, and choose the least destructive tool: revert for shared history, reset for private. Interviews like to ask the difference between revert and reset, or what to do if you ran git reset --hard by mistake; the right answer mentions the reflog and the committed-versus-uncommitted line. Teams also expect you to say when you rewrite a branch rather than doing it silently; see Collaborating in a Team for the habit of telling people what you changed.

Try it yourself

In a practice repository, make four commits. Reset --hard to the second one, then use git reflog to bring the fourth back. Then delete a feature branch with git branch -D and recover it. Write one sentence for each step saying which of the three areas changed.

Show a hint

git reflog lists entries as HEAD@{n}. After recovering a deleted branch, check with git log --oneline <branch> that its commits are back.

Key takeaways

  • Committed work is almost always recoverable; uncommitted work thrown away with restore or reset --hard is not.
  • Use git revert for commits that are shared, and git reset or amend only for local history.
  • reset --soft, --mixed and --hard differ in whether they touch the index and your working files.
  • git reflog lists where HEAD has been, which brings back reset commits and deleted branches.
  • After rewriting a pushed branch use --force-with-lease, and only on branches nobody else uses.

Test yourself

A trick from this lesson's ride: Shared history? Revert adds a correction; it never rewrites. Four more tricks like it wait along the route, each followed by a question.

Ride this lesson

Frequently asked questions

How do I undo the last commit in Git?

If it is only on your machine, git reset --soft HEAD~1 undoes the commit but keeps the changes staged, and git reset HEAD~1 keeps them as unstaged edits. To fix just the message or add a missed file, use git commit --amend. If the commit is already pushed, use git revert HEAD instead, which adds a new commit.

What is the difference between git revert and git reset?

git revert creates a new commit that undoes an earlier one and leaves history intact, so it is safe for shared branches. git reset moves the branch pointer back and discards commits from the branch, which rewrites history and is intended for local work. If others have the commits, revert is the right choice.

How do I recover a deleted branch or lost commit in Git?

Run git reflog to see where HEAD has recently been, find the hash of the lost commit, and create a branch there with git branch <name> <hash> or reset to it. This works for commits that were committed before the mistake; uncommitted changes cannot be recovered this way.