Introduction

If you’re not already using git add -p to stage your commits then you’re missing out. It allows you to interactively stage a file or just part of it giving you greater control over your git commit process.

Why should you want to do this?

Here are some of the reasons why I prefer using git add -p in my workflow. Primarily, because it allows me to review my changes as I stage them and I often find mistakes this way. By reviewing changes during staging, I catch bugs, typos, and other issues that might have slipped through during initial coding or content creation.

There is an additional benefit though; it allows me to stage only part of file. Git, rather oddly, refers to these parts as hunks so I will use that term going forward.

This feature is really useful when you have a number changes, but you want to group them up into different commits. Imagine you’ve made several related changes across different parts of a file. With git add -p, you can selectively stage these changes together, ensuring cleaner and more organized commits.

Staging part of a file

Here is an example of the interface showing you the diff and then prompting you to “Stage this hunk?”.

diff --git a/main.mts b/main.mts
index e1132f2..8f7c279 100644
--- a/main.mts
+++ b/main.mts
@@ -1,2 +1,4 @@
 export const add = (a, b) => a + b
+export const div = (a, b) => a / b
 export const sum = (xs) => xs.reduce((acc, x) => sum(acc, x))
+export const avg = (xs) => div(sum(xs), xs.length)
(1/1) Stage this hunk [y,n,q,a,d,s,e,?]?

In its simplest form we can enter y to stage that diff ready for commit or n not to. For this example I am not ready to commit the avg function, but I want to get div pushed up so I choose to enter s to split the hunk into smaller hunks. Git then asks me this.

Split into 2 hunks.
@@ -1,2 +1,3 @@
 export const add = (a, b) => a + b
+export const div = (a, b) => a / b
 export const sum = (xs) => xs.reduce((acc, x) => sum(acc, x))
(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?

So I enter y to stage that hunk for commit and git responds with the next hunk.

@@ -2 +3,2 @@
 export const sum = (xs) => xs.reduce((acc, x) => sum(acc, x))
+export const avg = (xs) => div(sum(xs), xs.length)
(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]?

Remembering that I only want to commit the div function I then enter q to quit the interactive hunk staging.

After interacting with the hunk staging, I return to the command prompt. From there, I can proceed with git commit or any other necessary commands.

Checking it worked

If I were to run a git status to check the staged files I would see that the fil (main.mts) appears in both sections; to be committed and not staged for commit. This is because we only staged part of the file and it is what we wanted!

On branch main
Your branch is ahead of 'origin/main' by 1 commit.
  (use "git push" to publish your local commits)

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

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   main.mts

By selectively staging only part of the file, we’ve successfully prepared a single hunk—a patch—for our upcoming commit.

Other options

The list ([y,n,q,a,d,s,e,?]) of potential responses is shortened, but you can get extended information by entering ? to get the help documentation.

Here are some response options you can use during interactive hunk staging taken from the git documentation. You’ll notice that there are a lot more options than in the list we saw earlier.

  • y: stage this hunk
  • n: do not stage this hunk
  • a: stage this and all the remaining hunks in the file
  • d: do not stage this hunk nor any of the remaining hunks in the file
  • g: select a hunk to go to
  • /: search for a hunk matching the given regex
  • j: leave this hunk undecided, see next undecided hunk
  • J: leave this hunk undecided, see next hunk
  • k: leave this hunk undecided, see previous undecided hunk
  • K: leave this hunk undecided, see previous hunk
  • s: split the current hunk into smaller hunks
  • e: manually edit the current hunk
  • ?: print help

When not to use it

I use git add -p nearly every time I commit every working day. There are two occasions where I don’t:

  1. There is a newly created file to commit for the first time - when a file is newly created there is no previous version to diff against of course so git add -p cannot present a diff for you to approve for staging.
  2. In rare cases, when I want to commit an entire directory and am confident about its content, I usually opt for the standard approach. However, even in such edge cases, I often find myself using git add -p for finer control.

Conclusion

By incorporating git add -p into your workflow, you’ll streamline your git and commit process.. I use this technique, without exaggeration, nearly every single time I need to commit a changeset to git. It allows me to easily review my code as I stage it for commit and control exactly what goes into each of my commits.