Manually Edit Git Hunks to Stage


One of my favorite features of git is the ability to commit partial changes to a file. Sometimes this is super easy as the changes I want to commit may not be near other changes I don’t want staged. Other times if the changes are too close to each other git has no idea what to do and it fails. Fortunately there is a manual option for only staging lines that you wish.


To start a partial git commit on a file simply run the following git command:

git add -p <file_name>

This will bring you to the first hunk of code to commit

@@ -1,13 +1,13 @@
 package net.jamesbaca.example;
 
 import org.junit.Test;
+import org.robolectric.annotation.Config;
 
 import net.jamesbaca.example.helpers.ReadSplitter;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.Is.is;
 
-
 public class ReadSplitterTest {
 
     @Test
Stage this hunk [y,n,q,a,d,/,s,e,?]?

The important part with respect to options we have available are in the last line. That is the brief menu of options. The question mark option tells us what each one does:

y - stage this hunk
n - do not stage this hunk
q - quit; do not stage this hunk or any of the remaining ones
a - stage this hunk and all later hunks in the file
d - do not stage this hunk or any of the later 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

In this particular case I can use the s command to split the hunk into two. After which, I see this:

Split into 2 hunks.
@@ -1,9 +1,10 @@
 package net.jamesbaca.example;
 
 import org.junit.Test;
+import org.robolectric.annotation.Config;
 
 import net.jamesbaca.example.helpers.ReadSplitter;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.Is.is;

I can now decide individually which hunk I want to commit. But what happens if my initial file had this instead?

@@ -1,13 +1,14 @@
 package net.jamesbaca.example;
 
 import org.junit.Test;
+import org.robolectric.annotation.Config;
+import org.other.Package;
 
 import net.jamesbaca.example.helpers.ReadSplitter;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.Is.is;
 
-
 public class ReadSplitterTest {
 
     @Test
Stage this hunk [y,n,q,a,d,/,s,e,?]? s

If I wanted to stage only one of the imports how would I do it? Split? Split only works once to separate the bottom hunk from the top. Leaving us with the following:

Stage this hunk [y,n,q,a,d,/,s,e,?]? s
Split into 2 hunks.
@@ -1,9 +1,11 @@
 package net.jamesbaca.example;
 
 import org.junit.Test;
+import org.robolectric.annotation.Config;
+import org.other.Package;
 
 import net.jamesbaca.example.helpers.ReadSplitter;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.Is.is;
 
Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]?

Unfortunately subsequent hunks are not able to be split anymore. Leaving us with the task for figuring out how to just commit a single line. This is where option e comes into play. We want to manually edit the hunk. In order to do so we need to edit 2 things
the first is the @@ -1,9 +1,11 @@ line. This line specifies the boundaries of the hunk to change. The -1,9 means the original portion of code started at line 1 in the code below and went for 9 more lines. The +1,11 means that we want to replace the original portion with the following code starting at line 1 and spanning the next 11 lines. In our case we don’t mind the starting lines staying consistent, we just don’t want the second line. So let’s go ahead and remove the second line:

# Manual hunk edit mode -- see bottom for a quick guide
@@ -1,9 +1,10 @@
 package net.jamesbaca.example;

 import org.junit.Test;
+import org.robolectric.annotation.Config;

 import net.jamesbaca.example.helpers.ReadSplitter;

 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.Is.is;

# ---
# 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.

If you do this correctly after saving you should see the next hunk. In our case that is the following:

@@ -4,10 +6,9 @@
 
 import net.jamesbaca.example.helpers.ReadSplitter;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.Is.is;
 
-
 public class ReadSplitterTest {
 
     @Test

If you were to check the status(git status) you would see that changes have been staged:

Changes to be committed:
  (use "git reset HEAD ..." to unstage)

	modified:   ReadSplitterTest.java

But what about the second line we didn’t want to stage, where did it go? No where. It is still in the un-staged area. We can see that it is available if we were to run the partial stage command.
git add -p ReadSplitterTest.java

@@ -2,13 +2,13 @@ package net.jamesbaca.example;
 
 import org.junit.Test;
 import org.robolectric.annotation.Config;
+import org.other.Package;
 
 import net.jamesbaca.example.helpers.ReadSplitter;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.Is.is;
 
-
 public class ReadSplitterTest {
 
     @Test
Stage this hunk [y,n,q,a,d,/,s,e,?]?

Leave a Reply

Your email address will not be published. Required fields are marked *