2025-02-19 Productivity, Programming

Undoing Changes & Recovering Lost Work in Git

By O. Wolfson

Working with Git can sometimes lead to mistakes like unwanted changes or lost commits. Fortunately, Git offers robust tools to revert changes and recover lost work. This article will guide you through key commands for undoing changes and reverting to previous states.

Three commands are often used to undo changes and recover lost work:

Each command has different purposes and behaviors.

CommandPurposeAffects Working Directory?Affects Staging Area (Index)?Affects Commit History?Common Use Case
git restoreRestore file(s) to a previous stateYes (optional)Yes (optional)NoDiscard changes in the working directory or unstage changes
git resetMove HEAD and optionally update index and working directoryOptionalYesYes (in some modes)Unstage changes, discard commits, or move HEAD
git revertCreate a new commit that undoes a previous commitNoNoYesSafely undo a commit in public history

1. git restore

  • Introduced in Git 2.23 to improve usability.
  • Used to discard changes in the working directory or unstage changes from the index (staging area).
  • Does not affect commit history.

Examples:

bash
git restore file.txt                    # Discard changes in working directory (reverts to last committed state)
git restore --staged file.txt            # Unstage file but keep changes in working directory
git restore --source=HEAD~1 file.txt     # Restore file to state from one commit ago

2. git reset

  • Moves the HEAD pointer to a different commit.
  • Optionally modifies the staging area and working directory depending on the mode:
    • --soft: Moves HEAD, keeps changes staged.
    • --mixed (default): Moves HEAD, unstages changes, keeps working directory.
    • --hard: Moves HEAD, unstages and discards changes in working directory.

Examples:

bash
git reset --soft HEAD~1   # Undo last commit, keep changes staged
git reset --mixed HEAD~1  # Undo last commit, unstage changes, keep working directory
git reset --hard HEAD~1   # Undo last commit, discard changes from working directory

3. git revert

  • Creates a new commit that undoes the changes from a specific previous commit.
  • Safe for public/shared branches because it preserves history.
  • Does not modify past commits or move HEAD back.

Examples:

bash
git revert HEAD             # Revert last commit (create a new commit that undoes it)
git revert <commit-hash>    # Revert a specific commit

Key Differences Summary:

Featuregit restoregit resetgit revert
Undo Working Directory Changes✔️✔️ (with --hard)
Unstage Files✔️✔️
Undo Commits✔️ (moves HEAD)✔️ (creates new commit)
Preserves History✔️❌ (except --soft)✔️
Safe for Public History✔️✔️

When to Use:

SituationRecommended Command
Discard changes in working directorygit restore file.txt
Unstage a filegit restore --staged file.txt
Undo the last commit, keep changes stagedgit reset --soft HEAD~1
Undo the last commit, keep changes in working directorygit reset --mixed HEAD~1
Undo the last commit, discard changes entirelygit reset --hard HEAD~1
Undo a specific commit safely in shared historygit revert <commit>

Building on the project we created earlier called my_git_project. Let’s continue using this project to simulate common scenarios, like accidental deletions, bad commits, and recovery, so you can learn how to get back on track when things go wrong.


Recap: Setting Up my_git_project

If you haven’t already, follow these steps to create a simple project:

bash
mkdir my_git_project
cd my_git_project
git init

Create and commit a file:

bash
echo "Hello, Git!" > hello.txt
git add hello.txt
git commit -m "Added hello.txt with a greeting message"

Make a second change:

bash
echo "Welcome to version control." >> hello.txt
git add hello.txt
git commit -m "Updated hello.txt with an additional message"

Now, we have a small history to work with.


🔄 Reverting to Previous Versions

Let’s say you realize that the second commit was a mistake. You want to go back to the first version.

Temporary Switch to an Earlier Commit (Checkout)

You can view the state of the project at the initial commit without changing your branch:

  1. Get the commit hash for the initial commit:

    bash
    git log --oneline
    

    Example output:

    text
    9e2b4d2 Updated hello.txt with an additional message
    4f6b3c2 Added hello.txt with a greeting message
    
  2. Temporarily switch to the first commit:

    bash
    git checkout 4f6b3c2
    

You are now in "detached HEAD" state, viewing the project as it was at the initial commit. To return to the latest state:

bash
git checkout main

Permanently Go Back (Reset)

If you want to undo the second commit and erase it from history:

bash
git reset --hard 4f6b3c2

This will delete the second commit and any changes after it. Use this with caution—it’s permanent!

Safe Rollback Without Losing History (Revert)

Instead of erasing history, you can undo the effects of a specific commit by creating a new commit:

bash
git revert 9e2b4d2

This will create a new commit that undoes the changes made in the second commit. The history remains intact, which is safer for shared projects.


🛠️ Undoing Commits & Changes

Sometimes, you need to undo recent work without deleting your progress.

Undo the Last Commit but Keep Changes (Soft Reset)

Let’s say you committed changes, but realized you forgot to add something. You can undo the last commit while keeping your changes staged:

bash
git reset --soft HEAD~1

This moves the commit back, but your changes stay in the staging area. You can modify files and commit again.

Undo the Last Commit and Discard Changes (Hard Reset)

If you want to completely remove the last commit and all changes:

bash
git reset --hard HEAD~1

This is irreversible—use with care.

Discard Uncommitted Changes (Restore)

If you made changes to hello.txt but want to discard them:

bash
git restore hello.txt

This reverts the file to the last committed state. To discard all files:

bash
git restore .

🕵️ Recovering Lost Commits with git reflog

What if you made a hard reset and realize you lost important work?

  1. View the history of HEAD movements:
    bash
    git reflog
    

Example output:

text
3f9d2a1 HEAD@{0}: reset: moving to HEAD~1
9e2b4d2 HEAD@{1}: commit: Updated hello.txt with an additional message
4f6b3c2 HEAD@{2}: commit: Added hello.txt with a greeting message
  1. Find the lost commit (e.g., 9e2b4d2), and recover it:

    bash
    git checkout 9e2b4d2
    
  2. Save it by creating a new branch:

    bash
    git branch recovered-work
    
  3. Switch to this branch:

    bash
    git checkout recovered-work
    

This brings your lost work back into the project.


💻 Hands-on Exercise: Simulating Accidental Deletions and Recovery

  1. Open my_git_project.
  2. Add another line to hello.txt:
    bash
    echo "Oops, this might be a mistake." >> hello.txt
    git add hello.txt
    git commit -m "Added a potentially bad line"
    
  3. Realize it’s a mistake and remove the last commit:
    bash
    git reset --hard HEAD~1
    
  4. Panic—you needed that line!
  5. Use git reflog to find the commit hash.
  6. Recover it:
    bash
    git checkout <commit_hash>
    
  7. Create a branch to save it:
    bash
    git branch recovered-work
    git checkout recovered-work
    

This exercise teaches you that even when things seem lost, git reflog is your safety net.


✨ Key Takeaways

CommandPurpose
git checkout <commit>Temporarily view an old commit.
git reset --hard <commit>Permanently move branch to a previous commit (destructive).
git revert <commit>Create a new commit to undo a previous commit (safe).
git reset --soft HEAD~1Undo last commit but keep changes staged.
git reset --hard HEAD~1Undo last commit and discard changes.
git restore <file>Discard changes in a file.
git reflogView the history of HEAD movements to recover lost commits.

Final Thought

Mistakes are part of development. Knowing how to undo changes and recover lost work will boost your confidence in using Git. Experiment with these commands in my_git_project to get comfortable before working on larger projects.