Undo safely
- Lesson 11
- 45 min
- Module 4: Resolve and recover
Lesson 11 of 15 · Git and Team Development EssentialsUndo safely
1. Understand a repository
2. Work with branches and remotes
3. Collaborate through reviews
4. Resolve and recover
5. Release and hotfix practice
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 ready
What you need to know
- How to resolve a conflict and complete a merge, from Conflicts step by step: Resolve, test and commit.
- In this module, you commit, merge and revert on
main, so that the commands stay short. On a team, you make each change on a branch and merge it through a pull request, as in Pull-request sequence.
What you need on your computer
- The lab
~/projects/c14-labin the statem04, 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, rungit pull. The output must sayAlready up to date.orFast-forward. - If
git pullsaysNot 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 --resetin~/projects. Then do the worked example in Conflicts step by step again, up to the lastgit 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.
Three ways to undo
Section titled “Three ways to undo”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.
Text description of the diagram
- Is the change committed? If no, go to question 2. If yes, go to question 3.
- Is the change staged? If yes, run
git restore --staged <file>: the change leaves the staging area and stays in your file. If no, rungit restore <file>: the edit is discarded, with no undo. - 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. - 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, rungit reset --hard HEAD~1: the commit and its changes go.
Undo a change that you did not commit
Section titled “Undo a change that you did not commit”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:
On branch mainYour branch is up to date with 'origin/main'.
Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: tickets.txtgit restore --staged tickets.txtgit status --short M tickets.txtThe 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.
git restore tickets.txtgit status --shortThe 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”Find the commit
Section titled “Find the commit”Amira looks at contacts.txt. The Network team is missing:
Accounts team: ext. 4300Help desk: ext. 4100She lists the commits that changed this file. A -- before a file name means “only commits that changed this file”:
git log --oneline -- contacts.txtf3bc1c5 Sort contacts by name55b7943 Add contactsThe message says “sort”, but a message is not the change. She checks what the commit really did:
git show f3bc1c5commit f3bc1c576e109cc78d291f676c393381d5177526Author: 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.txtindex 0ce0116..209c541 100644--- a/contacts.txt+++ b/contacts.txt@@ -1,3 +1,2 @@Help desk: ext. 4100Network team: ext. 4410 Accounts team: ext. 4300Help desk: ext. 4100Sam 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.
Revert it
Section titled “Revert it”git revert makes the opposite change as a new commit. --no-edit keeps the message that Git writes:
git revert --no-edit f3bc1c5[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:
git log -1commit 6643efd8cee1fce74247da2fd0441d3117d7b7bcAuthor: 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.
Check that later work is kept
Section titled “Check that later work is kept”A revert undoes one commit only. Amira checks both files:
cat contacts.txtgrep "Ticket 104" tickets.txtsh check.shHelp desk: ext. 4100Network team: ext. 4410Accounts team: ext. 4300Ticket 104: laptop will not charge. Replaced the power adapter.PASS: 4 ticket lines checkedgrep 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:
git pushTo /Users/amira/projects/c14-lab/team-remote.git b850b9e..6643efd main -> mainThe 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: only for commits that nobody has
Section titled “Reset: only for commits that nobody has”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:
cd ~/projects/c14-labgit clone team-remote.git scratchcd scratchCloning 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:
5dd5cac (HEAD -> main) Add ticket 1056643efd (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.
git reset --soft HEAD~1git status --shortM tickets.txtShe commits again with the same message, then tries the default mode.
Mixed: the commit goes, and its change stays in the file, not staged.
git commit -m "Add ticket 105"git reset HEAD~1Unstaged changes after reset:M tickets.txtHard: she commits once more, then removes the commit and its change.
git commit -am "Add ticket 105"git reset --hard HEAD~1HEAD 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”-
Go to your clone, and check that it is up to date:
Terminal window cd ~/projects/c14-lab/ticket-notesgit pull -
Add the line
Test line. Delete me.to the end oftickets.txt, and save. Then stage it:Terminal window git add tickets.txt -
Unstage it, and check:
Terminal window git restore --staged tickets.txtgit status --shortThe output is
M tickets.txt: the change is in the file, but not staged. -
Run
git diff tickets.txt. Check that the only change is the test line. Then discard it:Terminal window git restore tickets.txtgit status --shortgit status --shortprints nothing. -
Find the commit that removed the Network team:
Terminal window git log --oneline -- contacts.txtgit show f3bc1c5The diff shows
-Network team: ext. 4410. -
Revert it:
Terminal window git revert --no-edit f3bc1c5The output starts with
[mainand showsRevert "Sort contacts by name". -
Check the result:
Terminal window cat contacts.txtgrep "Ticket 104" tickets.txtsh check.shcontacts.txthas three teams. The ticket 104 line is still there. The test printsPASS. -
Run
git log -1. The message containsThis reverts commit f3bc1c576e109cc78d291f676c393381d5177526. -
Push:
Terminal window git pushThe output shows
main -> main. -
Make a disposable clone:
Terminal window cd ~/projects/c14-labgit clone team-remote.git scratchcd scratch -
In
scratch, add a line for ticket 105 totickets.txt. Save, then commit:Terminal window git commit -am "Add ticket 105" -
Run
git reset --soft HEAD~1, thengit status --short. The output isM tickets.txt: the change is staged. -
Commit again with
git commit -m "Add ticket 105". Then rungit reset HEAD~1, thengit status --short. The output isM tickets.txt: the change is not staged. -
Check that you are in
scratchwithpwd. Then commit again and reset with--hard:Terminal window git commit -am "Add ticket 105"git reset --hard HEAD~1git status --shortgit status --shortprints nothing. The ticket 105 line is gone. -
Delete the disposable clone. The next lesson makes a new one.
rm -rfdeletes a folder with no undo, so check the name before you press Enter:Terminal window cd ~/projects/c14-labrm -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.
- In
sam-notes, rungit pull. Incontacts.txt, changeAccounts team: ext. 4300toAccounts team: ext. 4030. Commit with the messageUpdate the accounts team extension. - Still as Sam, add the line
Ticket 105: new starter has no email account. Created the account.totickets.txt. Commit with the messageAdd ticket 105. Push both commits. - 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
cd ~/projects/c14-lab/ticket-notesgit pullgit log --oneline -3f30d4c7 Add ticket 105815c329 Update the accounts team extension6643efd 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:
git revert --no-edit 815c329cat contacts.txttail -1 tickets.txtsh check.shgit push[main 2bf80ca] Revert "Update the accounts team extension"Help desk: ext. 4100Network team: ext. 4410Accounts team: ext. 4300Ticket 105: new starter has no email account. Created the account.PASS: 5 ticket lines checkedtail -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:
HEAD is now at e058b3f Add ticket 103grep "Ticket 104" tickets.txt prints nothing. Sam’s later commit and the merge left main too. Then git status and git push show:
Your branch is behind 'origin/main' by 5 commits, and can be fast-forwarded. (use "git pull" to update your local branch) ! [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 behindhint: 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.
-
Run
git pull. It is a fast-forward:mainmoves forward to the same commit asorigin/main.Output (first lines) Updating e058b3f..dbad696Fast-forward -
Run
git log --oneline -5, and look for aRevertcommit. In this lab,mainalready has your revert from the worked example. So stop here, and delete the disposable clone. -
In a real repository, run
git revert --no-edit <hash>in your own clone, not in a disposable clone. Then runsh check.shandgit 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.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
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>. Readgit 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.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
Recap
- Restore works on files that you did not commit:
--stagedunstages, 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.
--softand the default keep your changes.--harddiscards 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