Skip to content

Undo safely

  • Lesson 11
  • 45 min
  • Module 4: Resolve and recover
Lesson 11 of 15 · Git and Team Development EssentialsUndo safely

What you will be able to do

Undo a mistake without damage to your team’s work. After this lesson, you can choose the correct command for each case: an edit, a staged change, a local commit, or a commit that the team has.

Before you startWhat to know and to have readyAbout 45 minutes

What you need to know

What you need on your computer

  • The lab ~/projects/c14-lab in the state m04, with your merge pushed. You pushed it at the end of Conflicts step by step: Resolve, test and commit.
  • In ~/projects/c14-lab/ticket-notes, run git pull. The output must say Already up to date. or Fast-forward.
  • If git pull says Not possible to fast-forward, your merge from the last lesson is not complete. Do the worked example in Conflicts step by step first, and push.
  • To start again from a known state, run sh nextia-learning-labs/C14/setup.sh m04 --reset in ~/projects. Then do the worked example in Conflicts step by step again, up to the last git push.
  • Your own commits, such as the merge commits, have different hashes from this lesson. The hashes of Sam’s commits from the setup script are the same.

Git has three commands that “undo”. They work on different places, and only one of them is safe for work that your team already has.

  • Restore changes files in your working tree or your staging area. It does not touch any commit. Use it for changes that you did not commit yet.
  • Revert makes a new commit that does the opposite of an old commit. If the old commit added a line, the revert commit removes it. The old commit stays in the history. Use it for commits that other people already have.
  • Reset moves your current branch to a different commit. The commits after that point are no longer on the branch. Use it only for commits that nobody else has.
Command What it changes Safe for pushed commits?
git restore --staged <file> The staging area. Your edit stays in the file. Not used for commits
git restore <file> The file in the working tree. Your edit is lost. Not used for commits
git revert <hash> Adds a new commit to the branch. Yes
git reset <hash> Moves the branch label back. No

Why is reset not safe for pushed commits? Your teammates already have those commits. If you move main back and then force the push, the commits disappear from the remote. Your teammates’ next pull then fails or brings the commits back, and anything built on them breaks. This is rewriting shared history. Do it only if the whole team agrees first, and never on a shared branch such as main as a quick fix. A revert adds history and removes nothing, so everyone can pull it normally.

A decision tree. First question: is the change committed? If no, ask: is it staged? If staged, use git restore --staged to unstage it and keep the edit. If not staged, use git restore to discard the edit, which cannot be undone. If the change is committed, ask: is the commit pushed or shared? If yes, use git revert to make a new commit that undoes it. If no, ask: do you want to keep the changes in your files? If yes, use git reset --soft HEAD~1. If no, use git reset --hard HEAD~1, which removes the commit and the changes.
Choose the undo command from two facts: is the change committed, and does anyone else have it?
Text description of the diagram
  1. Is the change committed? If no, go to question 2. If yes, go to question 3.
  2. Is the change staged? If yes, run git restore --staged <file>: the change leaves the staging area and stays in your file. If no, run git restore <file>: the edit is discarded, with no undo.
  3. Is the commit pushed, or does anyone else have it? If yes, run git revert <hash>: a new commit undoes it. If no, go to question 4.
  4. Do you want to keep the changes in your files? If yes, run git reset --soft HEAD~1: the commit goes, and its changes stay staged. If no, run git reset --hard HEAD~1: the commit and its changes go.

Amira adds a test line to tickets.txt and stages it by mistake. git status names the command that removes it from the staging area:

git status
On branch main
Your branch is up to date with 'origin/main'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: tickets.txt
Terminal (Amira)
git restore --staged tickets.txt
git status --short
Output
M tickets.txt

The M moved to the second column. The change is not staged now, but it is still in the file. She does not want the line at all.

Terminal (Amira)
git restore tickets.txt
git status --short

The second command prints nothing: the working tree is clean, and the test line is gone.

Worked example: undo Sam’s faulty commit

Section titled “Worked example: undo Sam’s faulty commit”

Amira looks at contacts.txt. The Network team is missing:

contacts.txt
Accounts team: ext. 4300
Help desk: ext. 4100

She lists the commits that changed this file. A -- before a file name means “only commits that changed this file”:

Terminal (Amira)
git log --oneline -- contacts.txt
Output
f3bc1c5 Sort contacts by name
55b7943 Add contacts

The message says “sort”, but a message is not the change. She checks what the commit really did:

Terminal (Amira)
git show f3bc1c5
Output
commit f3bc1c576e109cc78d291f676c393381d5177526
Author: Sam Okafor <sam@example.com>
Date: Tue Oct 6 09:08:00 2026 -0400
Sort contacts by name
diff --git a/contacts.txt b/contacts.txt
index 0ce0116..209c541 100644
--- a/contacts.txt
+++ b/contacts.txt
@@ -1,3 +1,2 @@
Help desk: ext. 4100
Network team: ext. 4410
Accounts team: ext. 4300
Help desk: ext. 4100

Sam sorted the list, and he also deleted Network team: ext. 4410 by mistake. Sam pushed this commit, and he pushed Add ticket 104 after it. Both commits are on the remote, and Amira’s merge from the last lesson is built on them. So she must not remove them from the history. She reverts the faulty commit.

git revert makes the opposite change as a new commit. --no-edit keeps the message that Git writes:

Terminal (Amira)
git revert --no-edit f3bc1c5
Output
[main 6643efd] Revert "Sort contacts by name"
Date: Mon Oct 5 22:05:28 2026 -0400
1 file changed, 2 insertions(+), 1 deletion(-)

She looks at the new commit:

Terminal (Amira)
git log -1
Output
commit 6643efd8cee1fce74247da2fd0441d3117d7b7bc
Author: Amira Khan <amira@example.com>
Date: Mon Oct 5 22:05:28 2026 -0400
Revert "Sort contacts by name"
This reverts commit f3bc1c576e109cc78d291f676c393381d5177526.

The message names the full hash of the commit that it undoes. Anyone who reads the history later can find both commits.

A revert undoes one commit only. Amira checks both files:

Terminal (Amira)
cat contacts.txt
grep "Ticket 104" tickets.txt
sh check.sh
Output
Help desk: ext. 4100
Network team: ext. 4410
Accounts team: ext. 4300
Ticket 104: laptop will not charge. Replaced the power adapter.
PASS: 4 ticket lines checked

grep prints the lines of a file that contain the text. The Network team is back. Sam’s later commit Add ticket 104 and her merge are still there. The revert is an ordinary new commit, so a normal push works:

Terminal (Amira)
git push
Output
To /Users/amira/projects/c14-lab/team-remote.git
b850b9e..6643efd main -> main

The sort was useful, so Amira tells Sam about the revert. Sam can sort the list again in a new commit, this time with all three teams.

Reset moves your branch label to another commit. It has three modes. Each mode moves the branch. They differ in what happens to the changes of the commits that leave the branch:

Mode The branch The staging area Your files
git reset --soft HEAD~1 Moves back one commit Keeps the changes, staged Keep the changes
git reset HEAD~1 (--mixed, the default) Moves back one commit Changes are not staged Keep the changes
git reset --hard HEAD~1 Moves back one commit Matches the commit Changes are discarded

HEAD~1 means “one commit before HEAD”. HEAD~2 means two commits before.

To try reset without risk, use a disposable clone: a separate copy of the repository that you delete after the practice. Nothing that you do in it changes your ticket-notes. Do not push from it.

Amira makes the clone next to the others and adds a commit there:

Terminal (Amira)
cd ~/projects/c14-lab
git clone team-remote.git scratch
cd scratch
Output
Cloning into 'scratch'...
done.

She adds the line Ticket 105: monitor flickers. Replaced the cable. to tickets.txt, and commits it with git commit -am "Add ticket 105". The commit exists only in scratch:

git log --oneline -2
5dd5cac (HEAD -> main) Add ticket 105
6643efd (origin/main, origin/HEAD) Revert "Sort contacts by name"

Soft: the commit goes, and its change stays staged. Use this to change the last commit before you push it.

Terminal (Amira, in scratch)
git reset --soft HEAD~1
git status --short
Output
M tickets.txt

She commits again with the same message, then tries the default mode.

Mixed: the commit goes, and its change stays in the file, not staged.

Terminal (Amira, in scratch)
git commit -m "Add ticket 105"
git reset HEAD~1
Output (last lines)
Unstaged changes after reset:
M tickets.txt

Hard: she commits once more, then removes the commit and its change.

Terminal (Amira, in scratch)
git commit -am "Add ticket 105"
git reset --hard HEAD~1
Output (last line)
HEAD is now at 6643efd Revert "Sort contacts by name"

git status --short prints nothing. The ticket 105 line is gone from the file. In Recover work, you learn how to get a commit like this back.

Guided practice: revert, then reset in a clone

Section titled “Guided practice: revert, then reset in a clone”
  1. Go to your clone, and check that it is up to date:

    Terminal window
    cd ~/projects/c14-lab/ticket-notes
    git pull
  2. Add the line Test line. Delete me. to the end of tickets.txt, and save. Then stage it:

    Terminal window
    git add tickets.txt
  3. Unstage it, and check:

    Terminal window
    git restore --staged tickets.txt
    git status --short

    The output is M tickets.txt: the change is in the file, but not staged.

  4. Run git diff tickets.txt. Check that the only change is the test line. Then discard it:

    Terminal window
    git restore tickets.txt
    git status --short

    git status --short prints nothing.

  5. Find the commit that removed the Network team:

    Terminal window
    git log --oneline -- contacts.txt
    git show f3bc1c5

    The diff shows -Network team: ext. 4410.

  6. Revert it:

    Terminal window
    git revert --no-edit f3bc1c5

    The output starts with [main and shows Revert "Sort contacts by name".

  7. Check the result:

    Terminal window
    cat contacts.txt
    grep "Ticket 104" tickets.txt
    sh check.sh

    contacts.txt has three teams. The ticket 104 line is still there. The test prints PASS.

  8. Run git log -1. The message contains This reverts commit f3bc1c576e109cc78d291f676c393381d5177526.

  9. Push:

    Terminal window
    git push

    The output shows main -> main.

  10. Make a disposable clone:

    Terminal window
    cd ~/projects/c14-lab
    git clone team-remote.git scratch
    cd scratch
  11. In scratch, add a line for ticket 105 to tickets.txt. Save, then commit:

    Terminal window
    git commit -am "Add ticket 105"
  12. Run git reset --soft HEAD~1, then git status --short. The output is M tickets.txt: the change is staged.

  13. Commit again with git commit -m "Add ticket 105". Then run git reset HEAD~1, then git status --short. The output is M tickets.txt: the change is not staged.

  14. Check that you are in scratch with pwd. Then commit again and reset with --hard:

    Terminal window
    git commit -am "Add ticket 105"
    git reset --hard HEAD~1
    git status --short

    git status --short prints nothing. The ticket 105 line is gone.

  15. Delete the disposable clone. The next lesson makes a new one. rm -rf deletes a folder with no undo, so check the name before you press Enter:

    Terminal window
    cd ~/projects/c14-lab
    rm -rf scratch

Your turn: revert one commit, keep the next

Section titled “Your turn: revert one commit, keep the next”

Sam makes another mistake, and then does good work after it. Play both people.

  1. In sam-notes, run git pull. In contacts.txt, change Accounts team: ext. 4300 to Accounts team: ext. 4030. Commit with the message Update the accounts team extension.
  2. Still as Sam, add the line Ticket 105: new starter has no email account. Created the account. to tickets.txt. Commit with the message Add ticket 105. Push both commits.
  3. In ticket-notes, pull. The correct extension is 4300. Undo Sam’s wrong change and keep his ticket 105. Push the result.

Write down the commands that you used. Explain why you did not use git reset.

Show a model answer
Terminal window
cd ~/projects/c14-lab/ticket-notes
git pull
git log --oneline -3
Output (last lines)
f30d4c7 Add ticket 105
815c329 Update the accounts team extension
6643efd Revert "Sort contacts by name"

Your hashes are different. Check the change before you revert it. git show 815c329 shows -Accounts team: ext. 4300 and +Accounts team: ext. 4030. Then:

Terminal window
git revert --no-edit 815c329
cat contacts.txt
tail -1 tickets.txt
sh check.sh
git push
Output (part)
[main 2bf80ca] Revert "Update the accounts team extension"
Help desk: ext. 4100
Network team: ext. 4410
Accounts team: ext. 4300
Ticket 105: new starter has no email account. Created the account.
PASS: 5 ticket lines checked

tail -1 prints the last line of a file. The extension is 4300 again, and Sam’s ticket 105 is still there.

Why not reset? Both of Sam’s commits are on the remote. To remove 815c329 with reset, you must move main back past it, and that also removes Add ticket 105. The remote then rejects your push, because your main is behind. A revert undoes only the one wrong commit, and everyone can pull it.

Common mistake

Reset a pushed commit, then the push is rejected

Symptom: you used reset to remove a commit that was already pushed. Here, Amira tries to remove f3bc1c5 in a disposable clone with git reset --hard f3bc1c5~1:

Output
HEAD is now at e058b3f Add ticket 103

grep "Ticket 104" tickets.txt prints nothing. Sam’s later commit and the merge left main too. Then git status and git push show:

Output
Your branch is behind 'origin/main' by 5 commits, and can be fast-forwarded.
(use "git pull" to update your local branch)
Output
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to '/Users/amira/projects/c14-lab/team-remote.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. ...

The number of commits can be different in your repository.

Cause: reset moved your main back. It removed the faulty commit and every commit after it. The remote still has those commits, so your main is behind it. Git rejects the push, because it would delete your team’s commits from the remote.

Fix: do not force the push. Get the shared commits back, then revert.

  1. Run git pull. It is a fast-forward: main moves forward to the same commit as origin/main.

    Output (first lines)
    Updating e058b3f..dbad696
    Fast-forward
  2. Run git log --oneline -5, and look for a Revert commit. In this lab, main already has your revert from the worked example. So stop here, and delete the disposable clone.

  3. In a real repository, run git revert --no-edit <hash> in your own clone, not in a disposable clone. Then run sh check.sh and git push.

A force push, git push --force, would replace the remote main with yours, and Sam’s commits would disappear from it. Never force a push to a shared branch to fix a mistake. If your team really must rewrite shared history, agree on it together first.

Check your understanding

Predict: Amira reverts a commit that added the line 'Ticket 110: ...'. What does the history look like after the revert, and is the old commit still in it?

Show the answer

The history has one more commit at the top: Revert "...", with This reverts commit <hash>. in its message. That new commit removes the line. The old commit is still in the history, with its line, so git show <old hash> still shows it. Revert adds history. It never removes it.

Check your understanding

Practical check: for each situation, name the command. (a) You staged a file by mistake. (b) You edited a file, and you want the last committed version back. (c) A commit is on the remote and is wrong. (d) Your last commit is not pushed, and you want to change it.

Show the answer
  • (a) git restore --staged <file>. The edit stays in the file.
  • (b) git restore <file>. Read git diff <file> first: the edit cannot be recovered.
  • (c) git revert <hash>, then test and push. Other people’s later commits stay.
  • (d) git reset --soft HEAD~1. The changes stay staged. Fix them, then commit again.

Recap

  • Restore works on files that you did not commit: --staged unstages, and without it, the edit is discarded.
  • Revert makes a new commit that undoes an old one. It is the safe way to undo a commit that the team already has. Later commits stay.
  • Reset moves your branch back. Use it only for commits that nobody else has. --soft and the default keep your changes. --hard discards them.
  • Do not rewrite shared history to fix a mistake. If reset makes a push fail, pull, then revert.
  • Try destructive commands in a disposable clone first.

Next: Recover work