Module 8: Fixing Mistakes and Resolving Merge Conflicts
Learn to handle the inevitable: mistakes and conflicts in test code. Practice undoing commits, recovering deleted test files, and resolving merge conflicts in test scripts, configuration files, and test data. Build confidence in recovering from common Git problems without losing work.
Undoing Changes: Working Directory to Committed History
Why This Matters
Every test automation engineer faces this moment: You’ve just committed broken test code. Or deleted a critical configuration file. Or realized your last three commits should have been one. The panic sets in—Have I just lost hours of work?
The reality: Mistakes in test code are inevitable. You’ll accidentally stage environment-specific credentials, commit failing tests, or delete data files that other team members need. The difference between a junior and confident test engineer isn’t avoiding mistakes—it’s recovering from them quickly and safely.
Real-World Scenarios You’ll Face
- Monday morning: You committed API test credentials to the repository and need to remove them from history before anyone pulls
- During test development: Your Selenium tests are failing after recent changes, and you need to revert to the last working version
- Code review feedback: Your teammate asks you to fix a typo in your test description—but you’ve already committed it
- Merge conflicts: After resolving conflicts in your test data files, you realize you kept the wrong version
- Accidental deletions: You deleted a test utilities file, committed the change, and now realize you need it back
The Pain Points This Lesson Solves
Without proper undo skills, you might:
- Create messy commit histories with “oops” and “fix previous commit” messages
- Lose test data or configuration files permanently
- Force your team to work around your mistakes
- Waste hours manually recreating deleted test code
- Fear making experimental changes because you can’t undo them
This lesson gives you confidence. You’ll learn the Git safety net exists at every stage, and you’ll know exactly which command to use when things go wrong.
Learning Objectives Overview
This lesson takes you through Git’s undo capabilities from the working directory all the way to committed history, teaching you the right tool for each situation.
What You’ll Accomplish
Understanding Git’s Three States (Foundation)
Before you can undo changes effectively, you need to understand where your changes live. We’ll visualize Git’s architecture—working directory, staging area, and repository—so you always know which undo command to use.
Discarding Unstaged Changes (First Line of Defense)
You’ll learn to quickly discard modifications in test files before they’re staged, using both traditional git checkout
and modern git restore
commands. Perfect for those “never mind, this approach won’t work” moments.
Unstaging Files (Fixing Premature Adds)
Discover how to remove files from the staging area while preserving your work. Essential when you accidentally stage files you’re not ready to commit, like work-in-progress test data or debug configurations.
Amending Recent Commits (Quick Fixes)
Master the art of fixing your most recent commit—whether it’s correcting a typo in test code, adding a forgotten file, or improving the commit message. This keeps your history clean without creating “fix” commits.
Undoing Commits Safely (Preserving Your Work)
Learn different git reset
modes to undo commits while keeping your changes available. You’ll understand when to use --soft
(undo commit only), --mixed
(undo commit and staging), and when to approach --hard
with caution.
Removing Commits Completely (The Nuclear Option)
Understand when and how to completely remove commits from history—and the serious implications of doing so. We’ll cover safe scenarios (local-only commits) and dangerous ones (shared history).
Recovery with Reflog (Your Ultimate Safety Net)
Even when commits seem “lost,” Git remembers. You’ll learn to use git reflog
to recover deleted commits, restore branches, and undo undos. This is your emergency recovery toolkit.
Choosing the Right Strategy (Decision Framework)
Finally, you’ll develop decision-making skills to quickly choose the appropriate undo approach based on whether changes are uncommitted, in the last commit, or in history—and whether you’re working alone or with a team.
By the end of this lesson, you’ll handle Git mistakes with confidence, knowing you have the tools to recover from virtually any situation without losing your valuable test automation work.
Core Content
Core Content: Undoing Changes in Git
Core Concepts Explained
Understanding Git’s Three States
Git manages files in three distinct states, and understanding these is crucial for undoing changes effectively:
graph LR
A[Working Directory] -->|git add| B[Staging Area]
B -->|git commit| C[Repository]
C -->|git checkout| A
B -->|git restore --staged| A
- Working Directory: Where you actively edit files
- Staging Area (Index): Where you prepare changes for commit
- Repository: Where committed changes are permanently stored
Undoing Changes in the Working Directory
When you’ve modified files but haven’t staged them yet, you can discard these changes.
Checking Current Status:
$ git status
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: test_login.py
modified: config.json
Discarding Changes (Git 2.23+):
# Restore a single file
git restore test_login.py
# Restore multiple specific files
git restore test_login.py config.json
# Restore all modified files
git restore .
Legacy Method (Git < 2.23):
# Discard changes to a specific file
git checkout -- test_login.py
# Discard all changes
git checkout -- .
Undoing Changes in the Staging Area
When you’ve used git add
but haven’t committed yet, you can unstage files.
Current State Example:
$ git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: test_login.py
new file: test_signup.py
Unstaging Files (Git 2.23+):
# Unstage a single file
git restore --staged test_login.py
# Unstage all files
git restore --staged .
Legacy Method:
# Unstage a specific file
git reset HEAD test_login.py
# Unstage all files
git reset HEAD .
Note: Unstaging keeps your changes in the working directory; it only removes them from the staging area.
Undoing Committed Changes
Once changes are committed, you have several options depending on whether you’ve pushed to a remote repository.
Option 1: Amend the Last Commit
Use when you need to modify the most recent commit (not yet pushed):
# Make your corrections
echo "# Test Configuration" > config.json
git add config.json
# Amend the previous commit
git commit --amend -m "Add test configuration file"
Amending without changing the message:
git commit --amend --no-edit
Option 2: Revert a Commit
Creates a new commit that undoes changes (safe for pushed commits):
$ git log --oneline
a1b2c3d (HEAD -> main) Add broken test
d4e5f6g Fix login validation
h7i8j9k Initial commit
# Revert the most recent commit
git revert HEAD
# Revert a specific commit
git revert a1b2c3d
# Output after revert
$ git log --oneline
k9l0m1n (HEAD -> main) Revert "Add broken test"
a1b2c3d Add broken test
d4e5f6g Fix login validation
Option 3: Reset to a Previous Commit
⚠️ Warning: Use with caution, especially if you’ve already pushed!
# View commit history
$ git log --oneline
a1b2c3d (HEAD -> main) Add broken test
d4e5f6g Fix login validation
h7i8j9k Initial commit
Three Reset Modes:
- Soft Reset (keeps changes staged):
git reset --soft d4e5f6g
# Changes from a1b2c3d are now staged
- Mixed Reset (default, keeps changes unstaged):
git reset d4e5f6g
# or
git reset --mixed d4e5f6g
# Changes from a1b2c3d are in working directory
- Hard Reset (⚠️ discards all changes):
git reset --hard d4e5f6g
# All changes from a1b2c3d are permanently lost
Practical Code Examples
Scenario 1: Fixing an Accidental Edit
You accidentally modified a test file while debugging:
# test_login.py (accidentally modified)
def test_login_success():
print("DEBUG: testing login") # Oops, added debug code
driver.get("https://practiceautomatedtesting.com/login")
assert True # Changed assertion by mistake
Restore the original:
$ git status
modified: test_login.py
$ git restore test_login.py
$ git status
nothing to commit, working tree clean
Scenario 2: Unstaging Multiple Files
You staged files but realized you want to commit them separately:
# Current situation
$ git status
Changes to be committed:
modified: test_login.py
modified: test_cart.py
new file: test_checkout.py
# Unstage test_cart.py to commit it separately
$ git restore --staged test_cart.py
# Now stage and commit login and checkout together
$ git commit -m "Add login and checkout tests"
# Then commit cart separately
$ git add test_cart.py
$ git commit -m "Add cart functionality tests"
Scenario 3: Fixing the Last Commit
You committed with a typo in the message or forgot to add a file:
# Forgot to include config file in commit
$ git log --oneline -1
a1b2c3d Add automated login tests
# Add the forgotten file
$ git add pytest.ini
$ git commit --amend -m "Add automated login tests with configuration"
# Result: The commit a1b2c3d now includes pytest.ini
Scenario 4: Undoing a Pushed Commit Safely
You pushed a commit that breaks tests:
# test_registration.py (introduced a bug)
def test_email_validation():
driver.get("https://practiceautomatedtesting.com/register")
email_field = driver.find_element(By.ID, "wrong_id") # Bug!
# ... rest of test
Safe approach using revert:
# Don't use reset --hard if already pushed!
# Instead, create a revert commit:
$ git revert HEAD
# Edit the revert commit message if needed
# This creates a new commit that undoes the changes
$ git push origin main
Scenario 5: Recovering from Multiple Bad Commits (Not Pushed)
You made several local commits that you want to undo:
$ git log --oneline
e1f2g3h (HEAD -> main) Experiment 3
d4e5f6g Experiment 2
c7d8e9f Experiment 1
b0c1d2e Last good commit
# Reset to the last good commit, keeping changes
$ git reset b0c1d2e
$ git status
Changes not staged for commit:
modified: test_experiments.py
# Start fresh with a clean approach
Common Mistakes Section
❌ Mistake 1: Using reset --hard
on Pushed Commits
Problem:
git reset --hard HEAD~3 # After pushing
git push origin main # This will fail!
Error:
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs
Solution: Use git revert
instead for pushed commits.
❌ Mistake 2: Confusing restore
and reset
Wrong:
# Trying to unstage with restore (without --staged)
git restore test_file.py # This discards changes, doesn't unstage!
Correct:
# To unstage
git restore --staged test_file.py
# To discard changes
git restore test_file.py
❌ Mistake 3: Forgetting to Check Status First
Always check what will be affected:
# Good practice: Always check first
git status
# Then act based on what you see
git restore . # or other commands
❌ Mistake 4: Not Creating a Backup Branch Before Risky Operations
Before potentially destructive operations:
# Create a backup
git branch backup-before-reset
# Now safe to experiment
git reset --hard HEAD~5
# If something goes wrong
git checkout backup-before-reset
🐛 Debugging: Recovering “Lost” Commits
If you accidentally reset and need to recover:
# View all operations history
git reflog
# Output shows:
# a1b2c3d HEAD@{0}: reset: moving to d4e5f6g
# k9l0m1n HEAD@{1}: commit: The commit you "lost"
# Recover the lost commit
git reset --hard k9l0m1n
⚠️ Important Reminders
- Before
reset --hard
: Always create a backup branch - After pushing: Use
revert
instead ofreset
- When unsure: Use
git status
andgit log
to understand current state - Test environment: Practice these commands in a test repository first
- Reflog is your friend: You can usually recover from mistakes using
git reflog
Quick Reference:
State | Undo Command | Effect |
---|---|---|
Working Directory (unstaged) | git restore <file> |
Discard changes |
Staging Area | git restore --staged <file> |
Unstage, keep changes |
Last Commit (not pushed) | git commit --amend |
Modify commit |
Any Commit (pushed) | git revert <commit> |
Create undo commit |
Any Commit (not pushed) | git reset <commit> |
Move branch pointer |
Hands-On Practice
EXERCISE
Hands-On Exercise: Mastering Git Undo Operations
Scenario
You’re working on a Python project and need to practice undoing changes at different stages of the Git workflow. You’ll simulate common mistakes and learn how to recover from them.
Setup
- Create a new directory and initialize a Git repository:
mkdir git-undo-practice
cd git-undo-practice
git init
- Create initial files:
echo "# My Project" > README.md
echo "print('Hello World')" > app.py
git add .
git commit -m "Initial commit"
Task 1: Undo Working Directory Changes
Scenario: You accidentally modified app.py
but haven’t staged the changes yet.
Steps:
- Modify the file incorrectly:
echo "print('This is wrong!')" >> app.py
- Check the status:
git status
- View the changes:
git diff app.py
- Undo the changes in the working directory:
git checkout -- app.py
# OR (Git 2.23+)
git restore app.py
- Verify the file is restored:
cat app.py
git status
Expected Outcome: The file should return to its last committed state with no modifications shown.
Task 2: Unstage Changes (Undo git add
)
Scenario: You staged changes but want to unstage them without losing your work.
Steps:
- Make changes and stage them:
echo "def greet():" >> app.py
echo " print('Hello!')" >> app.py
git add app.py
- Check status (file should be in staging area):
git status
- Unstage the file:
git reset HEAD app.py
# OR (Git 2.23+)
git restore --staged app.py
- Verify the changes are unstaged but still in working directory:
git status
cat app.py
Expected Outcome: Changes remain in the file, but it’s no longer staged for commit.
Task 3: Undo the Last Commit (Keep Changes)
Scenario: You committed too early and want to modify the commit.
Steps:
- Stage and commit your changes:
git add app.py
git commit -m "Add greet function"
- Realize you forgot to add documentation:
echo "# Documentation" > docs.md
- Undo the last commit but keep changes:
git reset --soft HEAD~1
- Check status:
git status
- Add the documentation and commit everything together:
git add docs.md
git commit -m "Add greet function and documentation"
Expected Outcome: Previous commit is undone, but all changes remain staged.
Task 4: Completely Undo the Last Commit
Scenario: The last commit was completely wrong and needs to be removed.
Steps:
- Create a bad commit:
echo "TEMPORARY FILE" > temp.txt
git add temp.txt
git commit -m "Bad commit - temporary file"
- Check commit history:
git log --oneline
- Completely remove the last commit:
git reset --hard HEAD~1
- Verify the commit and file are gone:
git log --oneline
ls
Expected Outcome: The commit is removed from history, and temp.txt
no longer exists.
Task 5: Revert a Commit (Safe Public Undo)
Scenario: You’ve pushed a commit and need to undo it safely without rewriting history.
Steps:
- Make and commit a change:
echo "buggy_code = True" >> app.py
git add app.py
git commit -m "Add buggy code"
- Create a revert commit:
git revert HEAD
-
Git will open an editor for the revert message. Save and close it.
-
Check the history:
git log --oneline
cat app.py
Expected Outcome: A new commit that undoes the previous change is created, keeping history intact.
Solution Approach Summary
Situation | Command | Effect |
---|---|---|
Undo working directory changes | git restore <file> or git checkout -- <file> |
Discards uncommitted changes |
Unstage files | git restore --staged <file> or git reset HEAD <file> |
Keeps changes in working directory |
Undo commit (keep changes staged) | git reset --soft HEAD~1 |
Removes commit, keeps changes staged |
Undo commit (keep changes unstaged) | git reset --mixed HEAD~1 |
Removes commit, keeps changes unstaged |
Undo commit (discard changes) | git reset --hard HEAD~1 |
Removes commit and all changes |
Safely undo pushed commit | git revert <commit> |
Creates new commit that reverses changes |
KEY TAKEAWAYS
✅ Different stages require different undo commands:
- Working directory changes:
git restore <file>
orgit checkout -- <file>
- Staged changes:
git restore --staged <file>
orgit reset HEAD <file>
- Committed changes:
git reset
(local) orgit revert
(shared/pushed)
✅ Git reset has three modes with different effects:
--soft
: Only moves HEAD, keeps changes staged--mixed
(default): Unstages changes, keeps them in working directory--hard
: Completely discards changes (use with caution!)
✅ Choose the right undo strategy based on whether code is shared:
- Use
git reset
for local commits that haven’t been pushed - Use
git revert
for pushed commits to preserve history and avoid conflicts with collaborators
✅ Always check status before and after undo operations:
git status
shows your current stategit diff
shows uncommitted changesgit log
shows commit history
✅ Safety first: Before using destructive commands like git reset --hard
, ensure you won’t lose important work. When in doubt, create a backup branch.
NEXT STEPS
What to Practice
- Create deliberate mistakes in a test repository and practice recovering from them
- Practice the decision tree: Given a scenario, choose the appropriate undo command
- Experiment with different
git reset
modes to understand their differences - Simulate collaborative scenarios where you need to use
git revert
instead ofgit reset
Related Topics to Explore
- Git stash: Temporarily save changes without committing
- Git reflog: Recover from mistakes when you’ve lost commits
- Interactive rebase: Rewrite commit history cleanly (for local branches)
- Git cherry-pick: Apply specific commits from one branch to another
- Amending commits: Modify the most recent commit message or contents
- Branch management: Use branches to experiment safely before committing to main
Helpful Resources
- Try the interactive Git tutorial:
git help tutorial
- Practice in a safe environment using: Learn Git Branching
- Keep a Git command cheat sheet handy for quick reference