Free Ebook cover Git for Programmers: Version Control for Small Projects

Git for Programmers: Version Control for Small Projects

New course

7 pages

Clean History with Git: Undo, Amend, and Safe Recovery for Small Projects

Capítulo 2

Estimated reading time: 9 minutes

+ Exercise

Core idea: fix mistakes without losing work

In small projects, most Git “oops” moments fall into two categories: (1) you changed the wrong thing (staged the wrong file, edited the wrong lines, deleted something), or (2) you recorded the wrong history (bad commit message, forgot a file, committed too early). Git gives you tools that are safe by default when you choose the right command for the situation. The key is to decide what you want to change: the staging area, the working tree, or the commit history.

Quick decision rules (use these first)

  • Undo what is staged (but keep your edits): git restore --staged
  • Discard local edits in a file (restore from last commit): git restore
  • Bring back a deleted file: git restore (or git checkout -- in older Git)
  • Undo a commit safely on a shared branch: git revert
  • Rewrite local history (not shared): git reset --soft/--mixed/--hard
  • Fix the last commit (message or content) before pushing: git commit --amend
  • Recover “lost” commits after a reset: git reflog

Scenario 1: You staged the wrong file (unstage without losing edits)

Problem: you ran git add and now the staging area includes changes you don’t want in the next commit. You want to keep the edits in your working tree, just remove them from the staging area.

Step-by-step: unstage a specific file

git status
git restore --staged path/to/file
git status

After this, the file’s changes remain in your working directory, but they are no longer staged.

Unstage everything

git restore --staged .

Use this when you want to rebuild the staging area carefully (for example, staging only certain files or hunks).

Scenario 2: You want to discard local edits safely (restore from last commit)

Problem: you edited a file and want to throw away those local modifications and return the file to the last committed state.

Continue in our app.

You can listen to the audiobook with the screen off, receive a free certificate for this course, and also have access to 5,000 other free online courses.

Or continue reading below...
Download App

Download the app

Step-by-step: discard edits in one file

git status
git restore path/to/file
git status

This replaces the working-tree version of the file with the version from HEAD (your last commit on the current branch). It does not affect other files.

Discard edits in multiple files

git restore .

Be careful: this discards all unstaged changes in the current directory tree. If you might want the work later, consider stashing or committing to a temporary branch (not covered here) before restoring.

Scenario 3: You deleted a file and want it back

Problem: you removed a tracked file (accidentally or intentionally) and now you want to restore it.

Case A: file deleted in working tree, not committed

git status
git restore path/to/deleted-file

This restores the file from HEAD.

Case B: file deletion is staged (you ran git rm or staged the deletion)

If the deletion is staged, first unstage it, then restore the file content:

git restore --staged path/to/deleted-file
git restore path/to/deleted-file

Think of it as: first fix the staging area, then fix the working tree.

Scenario 4: You need to undo a commit: revert vs reset

Problem: a commit is wrong and you want to undo its effect. There are two main approaches, and choosing the right one prevents headaches.

Option 1 (safe for shared history): git revert

git revert creates a new commit that applies the inverse of an earlier commit. This keeps history linear and is the default choice when the commit may already be on a branch others pull from (or anything you already pushed and want to avoid rewriting).

Step-by-step: revert a commit

git log --oneline
git revert <commit-sha>
git log --oneline

Result: history shows both the original commit and the revert commit. The project content reflects the undo.

Option 2 (rewrite local history): git reset

git reset moves the current branch pointer (and optionally changes the staging area and working tree). This rewrites history, so it is appropriate when the commits are local (not pushed) or when you are certain rewriting is acceptable.

Understand the three reset modes

  • git reset --soft <target>: moves HEAD to <target>, keeps changes staged. Use when you want to “uncommit” but keep everything ready to recommit (e.g., split into multiple commits).
  • git reset --mixed <target> (default): moves HEAD, unstages changes, keeps edits in working tree. Use when you want to redo staging/commit selection.
  • git reset --hard <target>: moves HEAD and resets staging + working tree to match. Use only when you truly want to discard local changes (and you have no uncommitted work you care about).

Practical targets: “go back one commit”

To move back one commit from the current HEAD:

git reset --soft HEAD~1
git reset --mixed HEAD~1
git reset --hard HEAD~1

Pick exactly one based on whether you want changes staged, unstaged, or discarded.

Rules of thumb: when each is appropriate

  • Use git revert when the commit is already shared (pushed) or you want an audit-friendly “undo” in history.
  • Use git reset when you are cleaning up local commits before sharing, or when you want to reorganize commits (message/content) without leaving “revert noise.”
  • Avoid --hard unless you are sure you do not need the current working-tree changes. If unsure, use --mixed first, inspect with git status, then decide.

Scenario 5: Fix the last commit with git commit --amend

Problem: your last commit is almost right, but you need to fix the message, add a forgotten file, or adjust a small change.

Amend the message only

git commit --amend

Your editor opens with the previous message; edit it and save. This creates a new commit object that replaces the previous last commit.

Amend content (add forgotten changes)

1) Make the additional edits or add the missing file. 2) Stage them. 3) Amend.

git add path/to/forgotten-file
git commit --amend

Important: amending rewrites the last commit. If you already pushed that commit, amending will diverge from the remote history. In small projects, a safe habit is: amend freely before pushing; after pushing, prefer git revert or create a new commit.

Safe recovery: use git reflog to find “lost” commits

Problem: you used git reset (or amended) and now a commit seems gone. Often it is not truly lost: Git records where HEAD and branch tips have been in the reflog. You can use it to locate the previous commit SHA and restore it.

Step-by-step: find prior states

git reflog

You will see entries like HEAD@{0}, HEAD@{1}, each with a commit SHA and an action (reset, commit, amend).

Restore by resetting back to a reflog entry

If you want to return your branch to a previous state:

git reset --hard HEAD@{1}

Or use the SHA shown in the reflog:

git reset --hard <sha-from-reflog>

If you are unsure about discarding current work, prefer --mixed first, inspect with git status, then decide whether to go --hard.

Restore a lost commit without moving your current branch (create a rescue branch)

A cautious approach is to create a new branch pointing at the lost commit:

git branch rescue <sha-from-reflog>

This preserves access to the commit while you decide how to integrate it.

Exercises: intentional mistakes, two fixes, compare history

Exercise 1: Stage the wrong file, then fix it two ways

Goal: learn to correct staging mistakes without losing edits.

  • Create two files and edit both.
  • Accidentally stage both, but you only want one staged.
# Create files (example names; use any files you like) git create-file notes.txt git create-file todo.txt
# Edit both files (use your editor) # Then stage everything by mistake git add . git status

Method A (recommended): unstage the unwanted file only.

git restore --staged todo.txt git status

Method B (broader): unstage everything, then stage only what you want.

git restore --staged . git add notes.txt git status

Compare: both methods keep your edits. Method A is faster when only one file is wrong; Method B is useful when you want to rebuild the staging set from scratch.

Exercise 2: Discard a bad edit vs recover a deleted file

Goal: practice restoring content from HEAD.

  • Make a bad edit in a tracked file and discard it.
  • Delete a tracked file and restore it.
# Bad edit (in a tracked file) # Edit README.md and save unwanted changes git status git restore README.md git status
# Delete and restore rm config.json git status git restore config.json git status

Variation: stage the deletion first, then recover using the two-step unstage + restore.

rm config.json git add config.json git status git restore --staged config.json git restore config.json git status

Exercise 3: Undo a commit two ways and compare history

Goal: see the difference between “undo by new commit” (revert) and “rewrite local history” (reset).

Setup: create a commit that introduces an obvious mistake (e.g., add a line “DEBUG=true” to a config file).

# Make a mistake and commit it echo "DEBUG=true" >> app.conf git add app.conf git commit -m "Enable debug" git log --oneline --decorate -5

Method A: revert (keeps history, adds an undo commit).

git revert HEAD git log --oneline --decorate -5

Observe: you now have two commits: the mistake and the revert. The file content should have the debug line removed.

Method B: reset (rewrite history). To try this, first recreate the mistake commit again (or do this exercise in a fresh branch). Then reset back one commit using different modes and observe git status.

# Recreate mistake commit (if needed) echo "DEBUG=true" >> app.conf git add app.conf git commit -m "Enable debug (again)"
# Option 1: keep changes staged git reset --soft HEAD~1 git status
# Option 2: keep changes but unstaged git reset --mixed HEAD~1 git status
# Option 3: discard changes completely (only if you truly want to) git reset --hard HEAD~1 git status

Compare history: revert adds a new commit and preserves the original commit in the log; reset removes the commit from the branch history (but it is typically recoverable via reflog).

Exercise 4: Amend the last commit, then recover with reflog

Goal: practice rewriting the last commit and recovering prior versions.

1) Make a commit with a bad message. 2) Amend it. 3) Use reflog to find the pre-amend commit.

# Make a commit with a bad message echo "typo" >> notes.txt git add notes.txt git commit -m "WIP" git log --oneline -3
# Amend message (and optionally add more changes) git commit --amend
# Inspect reflog to find the old commit git reflog

Restore the old commit into a rescue branch so you can compare:

git branch pre-amend <sha-from-reflog> git log --oneline --decorate -5

Observe: the amended commit has a different SHA than the original. The reflog and rescue branch demonstrate the “recoverable by default” habit: even after rewriting, you can usually get back to earlier states.

Now answer the exercise about the content:

A commit has already been pushed to a branch others may pull from, but you need to undo its effect. Which approach is the safest choice and why?

You are right! Congratulations, now go to the next page

You missed! Try again.

git revert is preferred for commits that are already shared because it undoes changes by adding a new commit, keeping history linear and avoiding rewriting a branch others may have pulled.

Next chapter

Ignoring the Right Things: .gitignore Rules and Tracking Decisions in Git

Arrow Right Icon
Download the app to earn free Certification and listen to the courses in the background, even with the screen off.