582 words, ~3 min read

git add -p won't split

It is amazing how many developers I run into that don't seem to either know about git add -p (a.k.a. git add patch), and ever fewer that know what to do when git add -p won't let you split your hunks up any further. Given how much I use this command in my workflow to facilitate creating logically chunked, buildable commits I figured I might as well make a post just touching on it.

Yes, this is something that less keyboard driven developers often ignore as they use a GUI (ex: Sublime Merge) to stage things at a smaller granularity. To be completely honest I use Sublime Merge to stage changes myself sometimes. However, there are a large number of times where I am already working on the command line with Git and therefore it is quicker and easier just to stay on the keyboard and use git add -p.

So lets break things down a bit.

git add

git add is a command that allows you to take local changes and move them to the staging area in preparation for making a commit. The idea is that you move all the changes you want in your commit into the staging area and then turn the changes in the staging area into your commit.

git add -p

When git add is executed with the -p option it instructs git add to allow you to select a portion of the changes in a local file for staging rather than the entire set of changes in that local file.

Lets look at an example to see exactly how this works.

For sake of discussion lets say we have the following local, unstaged changes.

❯ git diff
diff --git a/README b/README
index d325bcb..f8f8d46 100644
--- a/README
+++ b/README
@@ -5,3 +5,9 @@ Subtitle of README
 Description of the README

 First paragraph of the README
+
+Second paragraph
+
+Third paragraph
+
+Fourth paragraph

Given that we are following best practices because we want all the awesome Git commands to be able to work on our repo and we want our Git history to be useful we decide we want to create three separate commits, one to add each of the paragraphs in the diff.

To do this we first start out by running the following:

git add -p README

This prompts us with:

❯ git add -p README
diff --git a/README b/README
index d325bcb..f8f8d46 100644
--- a/README
+++ b/README
@@ -5,3 +5,9 @@ Subtitle of README
 Description of the README

 First paragraph of the README
+
+Second paragraph
+
+Third paragraph
+
+Fourth paragraph
(1/1) Stage this hunk [y,n,q,a,d,e,?]?

Oh No! Where is s?

Most of the time when you run git add -p it will present s as one of the choices in the output. s stands for split and tells git add -p to split the currently presented hunk into smaller hunks and then ask you if you want to stage the smaller hunks.

But in this case and generally when you get your hunks small enough it won't split them any further. At this point you have use one of the other options it provides.

Woot woot, e here we come!

The e option stands for edit and it puts us into manual hunk edit mode in our editor. This looks like the following.

# Manual hunk edit mode -- see bottom for a quick guide.
@@ -5,3 +5,9 @@ Subtitle of README
 Description of the README
 
 First paragraph of the README
+
+Second paragraph
+
+Third paragraph
+
+Fourth paragraph
# ---
# To remove '-' lines, make them ' ' lines (context).
# To remove '+' lines, delete them.
# Lines starting with # will be removed.
# 
# If the patch applies cleanly, the edited hunk will immediately be
# marked for staging.
# If it does not apply cleanly, you will be given an opportunity to
# edit again.  If all lines of the hunk are removed, then the edit is
# aborted and the hunk is left unchanged.

As we can see from the comments we can simply delete the '+' lines we don't want in this hunk. So in this case I edit the hunk to look as follows and save and quit the editor.

@@ -5,3 +5,9 @@ Subtitle of README
 Description of the README
 
 First paragraph of the README
+
+Second paragraph

If we run git status we can see it resulted in part of the changes be staged and the other part not. Lets verify the parts are as we expect.

❯ git diff --staged
diff --git a/README b/README
index d325bcb..8f6f97e 100644
--- a/README
+++ b/README
@@ -5,3 +5,5 @@ Subtitle of README
 Description of the README

 First paragraph of the README
+
+Second paragraph

The staged changes are correct.

❯ git diff
diff --git a/README b/README
index 8f6f97e..f8f8d46 100644
--- a/README
+++ b/README
@@ -7,3 +7,7 @@ Description of the README
 First paragraph of the README

 Second paragraph
+
+Third paragraph
+
+Fourth paragraph

And the unstaged changes are correct.

So now we can make our first commit, git commit -m "first commit".

From here we can simply rinse and repeat the process to end up with isolated logically chunked commits.