Module 2: Understanding Git's Three Areas: Working, Staging, and Repository

Master the fundamental workflow of Git by understanding the three core areas. Learn how test files move through Working Directory, Staging Area, and Repository. Practice selective staging to commit only relevant test changes, and understand how to inspect and manipulate files at each stage.

Selective Staging: Committing Only What Matters

Why This Matters

The Real-World Problem

You’ve just finished a productive testing session. You’ve:

  • Fixed three failing integration tests
  • Added debug logging to troubleshoot a flaky test (but it’s messy)
  • Started experimenting with a new test framework configuration
  • Updated test data files for multiple test suites

Now you need to commit your work. Do you commit everything at once? That would create a massive, confusing commit mixing bug fixes, experiments, and temporary debug code. Your team reviewer will struggle to understand what actually changed and why.

This is where selective staging becomes critical.

When You’ll Use This Skill

Daily scenarios for test engineers:

  • Separating concerns: Commit your passing test fixes separately from experimental work
  • Excluding temporary changes: Keep debug print statements and test data dumps out of commits
  • Creating reviewable commits: Group related test changes so reviewers can understand each commit’s purpose
  • Atomic test updates: Commit test code and corresponding test data together, but separately from unrelated changes
  • Maintaining clean history: Build a commit history that tells the story of your test suite’s evolution

Common Pain Points Addressed

“I accidentally committed debug code to main”

  • Learn to review what you’re staging before committing
  • Use techniques to exclude temporary debugging changes

“My commits are too large and confusing”

  • Break work into logical, reviewable chunks
  • Create commits that represent single, complete ideas

“I changed multiple tests but only want to commit one”

  • Stage specific files while leaving others untouched
  • Even stage specific lines within a file

“I staged the wrong file and already committed”

  • Understand how to inspect and manipulate the staging area
  • Learn recovery techniques for staging mistakes (before pushing)

“Code reviews take forever because my commits are unclear”

  • Structure commits to make reviewer’s job easier
  • Each commit becomes a clear, testable unit of change

Learning Objectives

By the end of this lesson, you will be able to:

Core Competencies

  1. Navigate Git’s three-tree architecture with confidence, understanding how test files transition between working directory, staging area, and repository

  2. Selectively stage changes using multiple techniques:

    • Stage entire files with git add
    • Stage portions of files with git add -p
    • Stage changes interactively with git add -i
  3. Create atomic commits that group related test changes logically:

    • One bug fix per commit
    • Test code and test data committed together
    • Feature tests separated from refactoring
  4. Inspect and verify changes before committing:

    • Use git status to understand current state
    • Use git diff to compare working directory, staging area, and repository
    • Preview exactly what will be committed
  5. Correct staging mistakes safely:

    • Unstage files without losing changes
    • Modify staged content before committing
    • Recover from common staging errors

Practical Outcomes

You’ll complete hands-on exercises where you:

  • Fix multiple test files but commit them separately based on purpose
  • Stage bug fixes while excluding debug logging
  • Use interactive staging to commit only specific test assertions
  • Build a clean, professional commit history that tells a clear story
  • Prepare commits that will pass code review on the first try

The Result: You’ll develop the discipline and skills to create meaningful, reviewable commits—a hallmark of professional test engineers who make their teams more effective.


Core Content

Core Content: Selective Staging - Committing Only What Matters

Core Concepts Explained

Understanding the Staging Area

The staging area (also called the “index”) is Git’s middle ground between your working directory and the repository. It allows you to selectively choose which changes to include in your next commit, giving you precise control over your version history.

Why Selective Staging Matters in Test Automation:

  • Separate test code changes from configuration updates
  • Commit related test modifications together
  • Keep debugging code out of your commits
  • Maintain clean, logical commit history

Step-by-Step Workflow

  1. Check Current Status

    • View modified files
    • See what’s staged vs. unstaged
  2. Stage Specific Changes

    • Add entire files
    • Stage parts of files (hunks)
    • Stage specific lines
  3. Review Before Committing

    • Verify staged changes
    • Ensure only relevant modifications are included
  4. Commit Staged Changes

    • Write meaningful commit messages
    • Leave other changes for future commits

Git Commands for Selective Staging

# View all changes
git status

# View detailed changes
git diff                    # Unstaged changes
git diff --staged          # Staged changes

# Stage specific files
git add path/to/file.py

# Stage all files of a type
git add tests/*.py

# Stage interactively (choose what to stage)
git add -p filename        # Patch mode

# Unstage files
git restore --staged filename

Practical Code Examples

Example 1: Staging Specific Test Files

Let’s say you’ve made changes to multiple test files and a configuration file, but only want to commit the login tests:

# Check what files have changed
git status

# Output might show:
# Modified:   tests/test_login.py
# Modified:   tests/test_checkout.py
# Modified:   config/test_config.py
# Modified:   README.md

# Stage only the login test
git add tests/test_login.py

# Verify what's staged
git status

# Output:
# Changes to be committed:
#   modified:   tests/test_login.py
#
# Changes not staged for commit:
#   modified:   tests/test_checkout.py
#   modified:   config/test_config.py
#   modified:   README.md

# Commit only the staged changes
git commit -m "Add password visibility toggle test for login page"

Example 2: Interactive Staging with Patch Mode

Suppose you’ve added both a new test and debug print statements in the same file:

# tests/test_form_submission.py
from selenium import webdriver
from selenium.webdriver.common.by import By
import pytest

class TestContactForm:
    
    def test_form_submission_success(self):
        """Test successful form submission on practiceautomatedtesting.com"""
        driver = webdriver.Chrome()
        driver.get("https://practiceautomatedtesting.com/contact")
        
        # Fill form fields
        driver.find_element(By.ID, "name").send_keys("John Doe")
        driver.find_element(By.ID, "email").send_keys("john@example.com")
        driver.find_element(By.ID, "message").send_keys("Test message")
        
        print("DEBUG: Form filled")  # Debug statement - don't commit
        
        # Submit form
        driver.find_element(By.ID, "submit").click()
        
        # Verify success message
        success_msg = driver.find_element(By.CLASS_NAME, "success")
        assert "Thank you" in success_msg.text
        
        driver.quit()
    
    def test_form_validation_empty_email(self):
        """Test form validation for empty email field"""
        driver = webdriver.Chrome()
        driver.get("https://practiceautomatedtesting.com/contact")
        
        # Fill only name and message
        driver.find_element(By.ID, "name").send_keys("John Doe")
        driver.find_element(By.ID, "message").send_keys("Test message")
        
        print("DEBUG: Checking validation")  # Debug statement - don't commit
        
        # Try to submit
        driver.find_element(By.ID, "submit").click()
        
        # Verify error appears
        error_msg = driver.find_element(By.CLASS_NAME, "error")
        assert "Email is required" in error_msg.text
        
        driver.quit()

Using patch mode to exclude debug statements:

# Start interactive staging
git add -p tests/test_form_submission.py

# Git will show hunks (chunks of changes) one at a time:
# Stage this hunk [y,n,q,a,d,s,e,?]?
# 
# Options:
# y - stage this hunk
# n - do not stage this hunk
# q - quit; do not stage this hunk or any remaining ones
# a - stage this hunk and all later hunks in the file
# s - split the current hunk into smaller hunks
# e - manually edit the hunk
# ? - print help

# When you see the hunk with print statements, press 'n'
# For the actual test code, press 'y'

Example 3: Staging Parts of Page Object Model Updates

# pages/login_page.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

class LoginPage:
    # URLs
    URL = "https://practiceautomatedtesting.com/login"
    
    # Locators
    USERNAME_INPUT = (By.ID, "username")
    PASSWORD_INPUT = (By.ID, "password")
    LOGIN_BUTTON = (By.ID, "login-button")
    ERROR_MESSAGE = (By.CLASS_NAME, "error-message")
    SUCCESS_MESSAGE = (By.CLASS_NAME, "success-message")  # New locator
    
    def __init__(self, driver):
        self.driver = driver
        self.wait = WebDriverWait(driver, 10)
    
    def navigate(self):
        """Navigate to login page"""
        self.driver.get(self.URL)
        print(f"DEBUG: Navigated to {self.URL}")  # Debug - don't commit
    
    def enter_username(self, username):
        """Enter username in the login form"""
        element = self.wait.until(
            EC.presence_of_element_located(self.USERNAME_INPUT)
        )
        element.clear()
        element.send_keys(username)
    
    def enter_password(self, password):
        """Enter password in the login form"""
        element = self.driver.find_element(*self.PASSWORD_INPUT)
        element.clear()
        element.send_keys(password)
    
    def click_login(self):
        """Click the login button"""
        button = self.driver.find_element(*self.LOGIN_BUTTON)
        button.click()
        print("DEBUG: Login button clicked")  # Debug - don't commit
    
    def get_error_message(self):
        """Get error message text"""
        element = self.wait.until(
            EC.visibility_of_element_located(self.ERROR_MESSAGE)
        )
        return element.text
    
    def is_login_successful(self):  # New method
        """Check if login was successful"""
        try:
            self.wait.until(
                EC.visibility_of_element_located(self.SUCCESS_MESSAGE)
            )
            return True
        except:
            return False

Selective staging workflow:

# View the changes in detail
git diff pages/login_page.py

# Use patch mode to selectively stage
git add -p pages/login_page.py

# When the new SUCCESS_MESSAGE locator appears: press 'y'
# When the new is_login_successful() method appears: press 'y'
# When debug print statements appear: press 'n'

# Verify what's staged
git diff --staged pages/login_page.py

# Commit only the production-ready code
git commit -m "Add success message verification to LoginPage POM"

Example 4: Using .gitignore and Selective Staging Together

# .gitignore file (commit this!)
# Ignore test outputs and temporary files
screenshots/
reports/
*.log
*.pyc
__pycache__/
.pytest_cache/
venv/

# config/local_settings.py (don't commit local overrides)
config/local_settings.py

Workflow:

# Modified multiple files including config
git status

# Stage only test files, not config
git add tests/

# Add the .gitignore if it's new
git add .gitignore

# Verify
git status

# Shows:
# Changes to be committed:
#   new file:   .gitignore
#   modified:   tests/test_login.py
#
# Untracked files:
#   config/local_settings.py  (ignored, won't be committed)

git commit -m "Add login test suite and update gitignore"

Common Mistakes Section

Mistake 1: Using git add . Too Liberally

Problem:

# Stages EVERYTHING, including files you don't want
git add .

Why it’s wrong: You might accidentally commit:

  • Debug code
  • Local configuration files
  • Temporary test files
  • Credentials or API keys

Solution:

# Be explicit about what you stage
git add tests/test_login.py tests/test_checkout.py

# Or use gitignore for files that should never be committed
echo "local_config.py" >> .gitignore

Mistake 2: Not Reviewing Staged Changes

Problem:

git add tests/
git commit -m "Updated tests"  # What exactly changed?

Why it’s wrong: You don’t know what you’re committing.

Solution:

# Always review before committing
git add tests/
git diff --staged  # Review all staged changes
git status         # See which files are staged

# Then commit with confidence
git commit -m "Add validation tests for email field format"

Mistake 3: Mixing Unrelated Changes in One Commit

Problem:

# In one commit: fixed bug + added new feature + updated config
git add tests/ config/ pages/
git commit -m "Various updates"

Why it’s wrong: Makes history unclear, harder to review, difficult to revert.

Solution:

# Commit 1: Bug fix
git add tests/test_login.py
git commit -m "Fix: Correct timeout in login wait condition"

# Commit 2: New feature
git add tests/test_two_factor.py pages/two_factor_page.py
git commit -m "Add two-factor authentication test suite"

# Commit 3: Config update
git add config/test_config.py
git commit -m "Update test config for staging environment"

Mistake 4: Forgetting to Unstage Files

Problem:

git add config/secrets.py  # Oops! This has API keys
# How do I undo this?

Solution:

# Unstage the file before committing
git restore --staged config/secrets.py

# Or use the older syntax
git reset HEAD config/secrets.py

# Verify it's unstaged
git status

Debugging Tip: Use git status Frequently

# Check status at each step
git status                    # What's changed?
git add specific_file.py
git status                    # What's staged?
git diff --staged            # What exactly am I committing?
git commit -m "message"
git status                    # Clean working directory?

Pro Tip: Create Logical Commits

Good commit structure:

  • One logical change per commit
  • Related files committed together
  • Clear, descriptive commit messages
  • Easy to review and revert if needed

Example workflow:

# You've updated login tests, added screenshot capability, and fixed a typo
# Make 3 separate commits:

git add tests/test_login.py
git commit -m "Add remember-me checkbox test for login"

git add utils/screenshot_helper.py tests/conftest.py
git commit -m "Add screenshot capture on test failure"

git add README.md
git commit -m "Fix typo in installation instructions"

This approach creates a clean, maintainable Git history that your team will thank you for!


Hands-On Practice

Hands-On Exercise

Task: Clean Up a Messy Repository

You’ve been working on a test automation project and your working directory is cluttered with various files. Your task is to selectively stage and commit only the files that belong together, while ignoring temporary and generated files.

Scenario Setup

Create a new Git repository with the following files:

mkdir selective-staging-practice
cd selective-staging-practice
git init

# Create test files
mkdir -p tests/api tests/ui
echo "def test_login(): pass" > tests/api/test_auth.py
echo "def test_dashboard(): pass" > tests/ui/test_dashboard.py

# Create configuration files
echo "BASE_URL=http://localhost:8080" > .env
echo "test-report.html" > .gitignore

# Create documentation
echo "# Test Suite Documentation" > README.md

# Create temporary/generated files
echo "<html>Test Report</html>" > test-report.html
echo "compiled_code" > tests/__pycache__/cache.pyc
touch .DS_Store

Instructions

Step 1: Check Repository Status

git status

Expected outcome: You should see multiple untracked files.

Step 2: Stage Only Test Files

Use selective staging to add only the Python test files:

git add tests/*.py tests/**/*.py
# or
git add tests/api/test_auth.py tests/ui/test_dashboard.py

Check your staging area:

git status

Step 3: Commit Test Files

git commit -m "Add initial API and UI test files"

Step 4: Stage Configuration Files

Now stage only the configuration-related files:

git add .gitignore README.md
git status

Step 5: Review Staged Changes

Before committing, review what you’re about to commit:

git diff --staged

Step 6: Commit Configuration

git commit -m "Add project documentation and gitignore"

Step 7: Verify Clean Separation

Check your commit history:

git log --oneline
git show HEAD~1  # View first commit
git show HEAD    # View second commit

Step 8: Handle Remaining Files

Check what’s left:

git status

Expected outcome: You should still see .env, test-report.html, .DS_Store, and cache files as untracked. These should NOT be committed.

Update your .gitignore:

echo ".env" >> .gitignore
echo "*.pyc" >> .gitignore
echo "__pycache__/" >> .gitignore
echo ".DS_Store" >> .gitignore

Stage and commit the updated .gitignore:

git add .gitignore
git commit -m "Update gitignore for environment and temp files"

Challenge Exercise

Make modifications to multiple files and practice selective staging:

# Modify multiple files
echo "def test_logout(): pass" >> tests/api/test_auth.py
echo "def test_profile(): pass" >> tests/ui/test_dashboard.py
echo "Updated documentation" >> README.md

# Use interactive staging to commit only test changes
git add -p tests/

# Review and commit
git commit -m "Add logout and profile tests"

# Then stage and commit documentation separately
git add README.md
git commit -m "Update documentation"

Expected Final Outcome

Your repository should have:

  • 4 commits with clear, focused purposes
  • Clean working directory (or only untracked files that should be ignored)
  • Logical separation between test code, configuration, and documentation
  • Proper .gitignore preventing sensitive/temporary files from being tracked

Verify with:

git log --oneline --graph

Key Takeaways

  • Selective staging enables focused commits – By staging only related changes together, each commit tells a clear story and serves a single purpose, making code reviews and debugging much easier.

  • Use pattern matching and interactive mode strategicallygit add with wildcards (e.g., *.py), directory paths, or -p flag for interactive staging gives you fine-grained control over what goes into each commit.

  • Always review before committing – Use git status and git diff --staged to verify exactly what you’re about to commit, preventing accidental inclusion of sensitive data, debug code, or unrelated changes.

  • Proper .gitignore is your first line of defense – Setting up .gitignore early prevents you from accidentally staging environment files, build artifacts, or IDE-specific files that don’t belong in version control.

  • Small, logical commits are easier to manage – When you need to revert changes, cherry-pick features, or understand project history, atomic commits that change one thing at a time are invaluable.


Next Steps

What to Practice

  1. Daily workflow integration – Make selective staging your default approach. Before each commit, consciously decide what belongs together.

  2. Interactive staging mastery – Practice using git add -p on files with multiple unrelated changes to split them into separate commits.

  3. Refine your .gitignore – Build a comprehensive .gitignore for your test automation stack (Python/Java/JavaScript specific files, test reports, screenshots, logs).

  4. Review staging regularly – Make git diff --staged a habit before every commit to catch mistakes early.

  • Partial file commits with git add -p – Learn to stage only specific hunks within a file
  • Unstaging mistakes – Master git reset HEAD <file> and git restore --staged <file>
  • Branch strategies – Combine selective staging with feature branches for cleaner PRs
  • Commit message conventions – Pair your focused commits with conventional commit formats
  • Pre-commit hooks – Automate checks to prevent committing unwanted files or code patterns
  • Git stash workflows – Temporarily save unrelated changes while you commit focused work
  • Amending and interactive rebase – Clean up commit history when you didn’t stage selectively enough

Pro Tip: Create a pre-commit checklist for your team: “Have you reviewed git diff --staged? Are all changes in this commit related? Is the commit message descriptive?” This builds a culture of quality commits.