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 -pGit 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,?]? yStages 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,?]? nSkips 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,?]? qExits 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,?]? aStages 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,?]? dSkips 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.pyHunk 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 statusOutput:
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.pyThe 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,?]? sWhen 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 hunkIf 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,?]? nResult: 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,?]? nBenefit: 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,?]? nUse 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 statusThis 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.pyThe reset command also offers interactive mode:
# Interactively unstage changes
git reset -pInteractive 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 filesUnderstanding 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 * ySolution: 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 correctlyConfiguration 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 falseUseful 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 unstageIntegration 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 recommitWorking 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.pyThis 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 stagingy- Stage this hunkn- Skip this hunks- 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.