Skip to content

Git Interactive Staging: Crafting Commits With Surgical Precision

How git add -p transformed my commits from "dump everything" to carefully curated changesets that tell a story.


The Problem With git add .

I had a habit. I'd work for hours, changing multiple things — fixing a bug, refactoring a function, adding a feature, updating documentation. Then I'd git add . && git commit -m "various changes". Every commit was a grab bag. Code review was painful. Reverting one change meant reverting everything. Bisecting was imprecise because each commit mixed unrelated changes.

The solution was always there: interactive staging. I just needed to slow down and use it.

The Basics of git add -p

The -p flag (short for --patch) shows each change in your working directory as a "hunk" and asks what to do with it:

git add -p

Git presents each hunk with this prompt:

@@ -45,7 +45,8 @@ def validate_user(user_data):
     if not user_data.get("email"):
-        return False
+        raise ValidationError("email is required")
+    if not is_valid_email(user_data["email"]):
+        raise ValidationError("email format is invalid")
     return True

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

The options: - y — stage this hunk - n — skip this hunk - s — split into smaller hunks (if possible) - e — manually edit the hunk - q — quit (don't stage remaining hunks) - a — stage this and all remaining hunks in this file - d — don't stage this or any remaining hunks in this file

Splitting Hunks: When Changes Are Adjacent

Sometimes two unrelated changes are close together in a file, and git groups them as one hunk. The s (split) command breaks them apart:

@@ -10,8 +10,9 @@
-DEFAULT_TIMEOUT = 5
+DEFAULT_TIMEOUT = 30
+MAX_RETRIES = 3

-logger = logging.getLogger("app")
+logger = logging.getLogger(__name__)

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

After splitting, git shows each change separately. The timeout change goes in one commit ("config: increase default timeout"), the logger fix goes in another ("fix: use module-specific logger name").

Editing Hunks: The Ultimate Precision Tool

When s can't split finely enough — two changes on adjacent lines — I use e (edit). This opens the hunk in my editor where I can manually choose which lines to stage:

# In the editor, lines starting with:
# +  → added lines (remove the line to NOT stage it)
# -  → removed lines (change - to space to NOT stage the removal)
#    → context lines (leave unchanged)

This is how I stage a single line from a multi-line change. It requires understanding diff format, but once mastered, it gives complete control.

My Interactive Staging Workflow

When I finish a work session, I never commit directly. I review my changes interactively:

# First, see the full picture
git diff --stat

# Then stage methodically, hunk by hunk
git add -p

# Review what's staged vs. what's left
git diff --cached   # staged changes (next commit)
git diff            # unstaged changes (not yet committed)

# Commit the curated changeset
git commit -m "feat(auth): add email format validation"

# Repeat for the next logical group
git add -p
git commit -m "config: increase default timeout to 30s"

Staging by File: A Middle Ground

Sometimes I don't need hunk-level precision — whole files belong together:

# Stage specific files
git add src/auth/validator.py src/auth/tests/test_validator.py

# Stage all files in a directory
git add src/auth/

# Interactive mode at file level (not hunk level)
git add -i

The -i flag gives a menu-driven interface for selecting files, which I find useful when dealing with many changed files across multiple directories.

Unstaging With Precision

Made a mistake staging? Interactive unstaging works the same way:

# Unstage hunks interactively
git reset -p

# This shows each staged hunk and asks: "Unstage this hunk?"
# Same y/n/s/e interface as git add -p

Stashing Selectively

The patch interface also works with stash:

# Stash only specific hunks
git stash push -p -m "WIP: experimental cache layer"

# Keep some changes in the working directory, stash others

This is powerful when I need to isolate one concern for a quick commit while keeping other in-progress work available.

The Mindset Shift

Interactive staging changed how I think about work. Instead of "I'm done, let me commit everything," I think "what are the distinct logical changes in my working directory?" Each one becomes its own commit.

The benefits compound: - Code review is easier because each commit is focused - Bisect is more precise because commits don't mix concerns - Revert is safe because reverting one commit only undoes one thing - Cherry-pick works because changes are self-contained - History tells a story that future developers can follow

Practical Tips

  1. Commit small, commit often. I aim for commits that a reviewer can understand in 30 seconds.

  2. Read every hunk. The review during add -p often catches bugs — "wait, that's not right." It's a free code review by yourself.

  3. Don't be afraid of many small commits. You can always squash later during rebase. You can never split a large commit after it's pushed.

  4. Use the --stat preview. Before starting interactive staging, git diff --stat gives you the map of what changed where.

Key Takeaway

git add -p is the difference between using git as a backup tool and using it as a communication tool. Every commit becomes an intentional message to future readers — including future you. The two extra minutes spent curating each commit saves hours of confusion later. Precision in staging creates precision in history.


Tags: git, staging, workflow, patch, commit-hygiene

Reference