Interactive Staging Basics

Interactive staging enables selective commit composition by allowing you to stage specific portions of modified files rather than entire files at once. This capability proves essential when a single file contains multiple logical changes that should be committed separately—enabling cleaner commit history and more effective code review.

Want to understand staging in depth? Our Understanding the Staging Area guide explains Git’s three-state model and why precise staging control matters.

Understanding the Problem Interactive Staging Solves

During development, files often accumulate multiple distinct changes:

Example scenario:

# payment.py - contains three separate changes:
def process_payment(amount, user):
    # Change 1: Input validation (ready to commit)
    if amount <= 0:
        raise ValueError("Amount must be positive")

    # Change 2: Fee calculation update (needs review)
    fee = calculate_fee(amount)
    fee = max(fee, 0.30)  # New minimum fee

    # Change 3: Logging improvement (ready to commit)
    logger.info(f"Processing ${amount} for {user.email}")

    total = amount + fee
    return charge_card(user, total)

Traditional staging treats the entire file as a single unit—you either stage all three changes or none. Interactive staging lets you stage changes 1 and 3 while leaving change 2 unstaged for further work.


Initiating Interactive Staging

The patch mode (-p or --patch) launches Git’s interactive staging interface:

# Interactive staging for specific file
git add -p payment.py

# Interactive staging for all modified files
git add -p

Git divides the file’s modifications into “hunks”—contiguous blocks of changed lines. For each hunk, Git presents a prompt asking how you want to handle it.


Understanding Hunks

Git automatically segments your changes into logical units called hunks. Each hunk represents a section of modified code that Git considers related:

Example hunk presentation:

@@ -15,5 +15,7 @@ def process_payment(amount, user):
 def process_payment(amount, user):
+    # Validate amount
+    if amount <= 0:
+        raise ValueError("Amount must be positive")
+
Stage this hunk [y,n,q,a,d,s,e,?]?

The hunk shows:

  • Context lines: Unchanged lines surrounding the modification (helps identify location)
  • Added lines: Prefixed with + (new code)
  • Removed lines: Prefixed with - (deleted code, if any)
  • Line numbers: The @@ header indicates where changes occur

Core Interactive Commands

When Git presents a hunk, you respond with single-letter commands:

Essential Commands

y - Yes, stage this hunk

Stage this hunk [y,n,q,a,d,s,e,?]? y

Stages the current hunk and moves to the next one. This is your primary tool for selective staging.

n - No, don’t stage this hunk

Stage this hunk [y,n,q,a,d,s,e,?]? n

Skips the current hunk without staging, leaving it in your working tree. Move to the next hunk.

q - Quit

Stage this hunk [y,n,q,a,d,s,e,?]? q

Exits interactive staging immediately. Hunks you’ve already processed remain staged; remaining hunks stay unstaged.

a - Stage this and all remaining hunks

Stage this hunk [y,n,q,a,d,s,e,?]? a

Stages the current hunk and all subsequent hunks in the file. Useful when you’ve carefully reviewed initial hunks and are confident about staging the rest.

d - Don’t stage this hunk or any remaining

Stage this hunk [y,n,q,a,d,s,e,?]? d

Skips the current hunk and all remaining hunks in the file. Opposite of a.

? - Show help

Stage this hunk [y,n,q,a,d,s,e,?]? ?

Displays all available commands with brief descriptions.


Practical Workflow Example

Here’s a complete interactive staging session:

Starting state: Modified payment.py with three distinct changes

git add -p payment.py

Hunk 1: Input validation

@@ -15,4 +15,7 @@ def process_payment(amount, user):
 def process_payment(amount, user):
+    if amount <= 0:
+        raise ValueError("Amount must be positive")
+
Stage this hunk [y,n,q,a,d,s,e,?]? y

Decision: Stage it—validation is complete and tested.


Hunk 2: Fee calculation change

@@ -20,1 +23,2 @@ def process_payment(amount, user):
     fee = calculate_fee(amount)
+    fee = max(fee, 0.30)  # Minimum fee
Stage this hunk [y,n,q,a,d,s,e,?]? n

Decision: Skip it—this change needs more testing before committing.


Hunk 3: Logging improvement

@@ -25,1 +29,1 @@ def process_payment(amount, user):
-    logger.debug(f"Processing payment")
+    logger.info(f"Processing ${amount} for {user.email}")
Stage this hunk [y,n,q,a,d,s,e,?]? y

Decision: Stage it—logging enhancement is ready.


Result:

git status

Output:

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   payment.py

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working tree)
	modified:   payment.py

The same file appears in both sections—partially staged. You can now commit the staged portions:

git commit -m "Add payment validation and improve logging"

The fee calculation change remains in your working tree for continued development.


Splitting Hunks

Sometimes Git groups unrelated changes into a single hunk. The s command attempts to split it:

Stage this hunk [y,n,q,a,d,s,e,?]? s

When splitting works: Git can split hunks that have unchanged lines between modifications:

def authenticate(user):
    # Change A: Add validation
    if not user:
        raise ValueError("User required")

    # Unchanged lines here...

    # Change B: Add logging
    logger.info(f"Auth attempt: {user.email}")

Git splits this into two hunks at the unchanged lines.

When splitting fails:

Sorry, cannot split this hunk

If changes are too close together (adjacent or interleaved), Git cannot split them. In this case, you’ll need to:

  • Stage the entire hunk and make a smaller commit, or
  • Use manual editing (covered in the advanced guide), or
  • Edit the file to separate changes and try again

Common Staging Patterns

Pattern 1: Separating Bug Fixes from Features

You’re implementing a feature when you discover and fix a bug in existing code:

git add -p

# Bug fix hunk appears first
Stage this hunk [y,n,q,a,d,s,e,?]? y

# Feature implementation hunks appear next
Stage this hunk [y,n,q,a,d,s,e,?]? n

Result: Commit the bug fix separately, then continue feature development with the unstaged changes.


Pattern 2: Refactoring vs. Logic Changes

Your file contains both refactoring (renames, reorganization) and functional changes:

git add -p

# Refactoring hunk (rename variable)
Stage this hunk [y,n,q,a,d,s,e,?]? y

# Logic change hunk (new algorithm)
Stage this hunk [y,n,q,a,d,s,e,?]? n

Benefit: Reviewers can see pure refactoring in one commit and logic changes in another—easier to verify correctness.


Pattern 3: Documentation vs. Implementation

Code changes mixed with documentation updates:

git add -p

# Documentation comment additions
Stage this hunk [y,n,q,a,d,s,e,?]? y

# Implementation changes needing more work
Stage this hunk [y,n,q,a,d,s,e,?]? n

Use case: Commit documentation improvements immediately while continuing to refine implementation details.


Verifying Staged Changes

After interactive staging, always verify what you’ve staged:

# View staged changes
git diff --staged

# View unstaged changes
git diff

# See both in status
git status

This verification step prevents accidental commits of incomplete changes or important omissions.


Unstaging Interactively Staged Changes

If you stage something incorrectly during interactive mode:

# Unstage specific file portions interactively
git restore --staged -p payment.py

# Or unstage the entire file and restart
git restore --staged payment.py
git add -p payment.py

The reset command also offers interactive mode:

# Interactively unstage changes
git reset -p

Interactive Staging Across Multiple Files

When multiple files are modified, Git processes them sequentially:

git add -p

# Git processes first modified file
# After completing first file, automatically moves to second file
# Continue until all files processed or you quit with 'q'

Tip: To stage specific files only:

git add -p payment.py auth.py
# Processes only these two files

Understanding What Gets Committed

After interactive staging, your repository exists in three states simultaneously:

1. Working Tree: Contains all your changes (staged and unstaged)

2. Staging Area (Index): Contains only the hunks you staged

3. Repository (HEAD): Contains the last committed state

When you commit, only the staging area contents are saved. Your working directory retains the unstaged changes for continued work.

Visualization:

Working Tree:     change A + change B + change C
Staging Area:          change A + change C
After commit:
Repository (HEAD):     change A + change C
Working Tree:     change B (unstaged)

When to Use Interactive Staging

Use interactive staging when:

  • A single file contains multiple logical changes
  • You want to separate bug fixes from feature additions
  • Refactoring and logic changes are mixed
  • You’re preparing a clean commit history for code review
  • Testing revealed issues in part of your changes

Consider alternatives when:

  • All changes in a file belong together logically
  • You’re making only a single, cohesive change
  • Time is critical and commit granularity isn’t important
  • Changes are simple and already well-separated into files

Common Pitfalls and Solutions

Pitfall 1: Staging Incomplete Logic

Problem: You stage part of a multi-step change, creating a broken intermediate state.

Example:

# Staged:
result = new_calculate_method(x, y)

# Unstaged:
def new_calculate_method(x, y):
    return x * y

Solution: Commit should include both the function definition and its usage. Use git diff --staged to verify completeness before committing.


Pitfall 2: Forgetting Unstaged Changes

Problem: After committing staged hunks, you forget about remaining unstaged changes.

Solution: Always check git status after committing. Git will remind you about unstaged modifications.


Pitfall 3: Accidentally Staging Debug Code

Problem: Debug logging or test code accidentally included in a hunk you staged.

Solution: Carefully read each hunk before typing y. If you catch it after staging:

git restore --staged -p file.py
# Then restage correctly

Configuration and Aliases

Setting Interactive Pager

Git uses a pager for large diffs. Configure your preferred pager:

# Use less with specific options
git config --global core.pager "less -FRX"

# Disable pager for add (immediate output)
git config --global pager.add false

Useful Aliases

# Add to ~/.gitconfig
[alias]
    # Interactive add shorthand
    ap = add -p

    # Interactive unstage
    up = restore --staged -p

    # Show what's staged
    staged = diff --staged

    # Interactive add with status preview
    stage = "!git add -p && git status"

Usage:

git ap              # Quick interactive staging
git staged          # Review what you've staged
git up              # Interactively unstage

Integration with Your Workflow

Pre-Commit Hook Integration

Interactive staging works seamlessly with pre-commit hooks:

# Stage changes interactively
git add -p

# Commit (triggers pre-commit hooks)
git commit

# Hooks run only against staged changes
# If hooks fail, you can fix issues and recommit

Working with Code Formatters

When using tools like Black or Prettier, format before interactive staging:

# Make your changes
vim payment.py

# Format the file
black payment.py

# Then stage interactively
git add -p payment.py

This prevents formatting changes from cluttering your hunks.


When to Move to Advanced Interactive Staging

The basic interactive staging covered here handles most scenarios. Consider moving to advanced techniques when you need to:

  • Edit hunks manually for line-level control
  • Stage parts of hunks that Git won’t split
  • Handle complex merge scenarios
  • Integrate with external diff tools

These advanced scenarios are covered in the “Advanced Interactive Staging” section.


Summary

Interactive staging transforms Git’s commit composition from file-level to change-level granularity. This capability enables:

Core benefits:

  • Logically separated commits from mixed-change files
  • Cleaner git history for code review and debugging
  • Flexibility to commit completed work while continuing development
  • Better commit messages that describe focused changes

Key commands:

  • git add -p - Launch interactive staging
  • y - Stage this hunk
  • n - Skip this hunk
  • s - Split hunk (when possible)
  • q - Quit interactive mode

Best practices:

  • Always preview hunks carefully before staging
  • Verify staged changes with git diff --staged
  • Ensure each commit represents a complete logical unit
  • Use descriptive commit messages that match the staged changes

Interactive staging represents one of Git’s most powerful features for maintaining clean, reviewable commit history. By enabling selective commit composition, it allows developers to separate concerns even after changes are made—bringing structure to the inherently messy process of software development.