Get insightful engineering articles delivered directly to your inbox.
By

— 5 minute read

Tidier Commits using Git --patch

Git is one of the most powerful tools in a software workflow, and knowing tricks to bend it to your will can help you to be more efficient and keep your repository better organized. It has an overwhelming amount of tools under the hood, and today we’ll be looking at the enormously useful --patch mode to see how it’s often a better option than a plain old git add.

A Simple Example

Let’s start by creating a simple test repository containing a few text files.

mkdir ./patch-mode && cd ./patch-mode
git init .

echo "Superman" >> heroes
echo "Wonder Woman" >> heroes
echo "Spider Man" >> heroes 
echo "Iron Man" >> heroes

echo "R2D2" >> robots
echo "Johnny #5" >> robots
echo "Kitt" >> robots 

Because these are brand new files, we’ll go ahead and run an initial git add with the special -N flag to let git know that we intend to add these files later, but for now, to act as if these files were already staged.

git add -N heroes robots

Let’s stop and have a look at the diff to see what our changes look like

git diff
diff --git a/heroes b/heroes
index e69de29..81533d0 100644
--- a/heroes
+++ b/heroes
@@ -0,0 +1,3 @@
+Superman
+Wonder Woman
+Spider Man
+Iron Man
diff --git a/robots b/robots
index e69de29..3572eed 100644
--- a/robots
+++ b/robots
@@ -0,0 +1,3 @@
+R2D2
+Johnny #5
+Kitt

Exactly what we’d expect. We see the addition of four heroes and three robots. At this point we could git add -a, git add ., or git add heroes robots to stage all of the changes at one time. Sometimes, though, you’ve made changes elsewhere that you might not want to commit, and any of those commands would cause you to unintentionally stage those changes. Let’s see if we can avoid that.

The –patch flag

git add --patch
diff --git a/heroes b/heroes
index e69de29..cf1fcb8 100644
--- a/heroes
+++ b/heroes
@@ -0,0 +1,4 @@
+Superman
+Wonder Woman
+Spider Man
+Iron Man
Stage this hunk [y,n,q,a,d,/,e,?]? 

Running git add --patch is showing us the first hunk of changes, our heroes file with four additions. It’s also giving us a list of options for staging the hunk; [y,n,q,a,d,/,e,?]. The list shows the options available to us given our current hunk, but you can type ? and hit [Enter] to get a better understanding of all of the options. Our available options are:

  • 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
  • / - search for a hunk matching the given regex
  • e - manually edit the current hunk
  • ? - print help

This time, it’s a good thing we opted for --patch! We haven’t decided if Iron Man is a hero. I mean, he doesn’t have any true super powers after all. Let’s not include him in our list, at least for this commit. To do that, we’ll choose e to manually edit this hunk. Keep in mind we’re not editing the actual file, just the diff to be applied to this hunk.

e [Enter]
# Manual hunk edit mode -- see bottom for a quick guide
@@ -0,0 +1,4 @@
+Superman
+Wonder Woman
+Spider Man
+Iron Man
# ---
# 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.

We now have the ability to move about freely in the hunk and modify it according to the instructions in the comment at the bottom. In our case, we’d like to remove a ‘+’ line from the hunk. Namely, the Iron Man addition. Removing that line should result in the following hunk

# Manual hunk edit mode -- see bottom for a quick guide
@@ -0,0 +1,4 @@
+Superman
+Wonder Woman
+Spider Man
# ---
# To remove '-' lines, make ...

Again, we’re not telling git that we want to delete “Iron Man” from the heroes file, just that we don’t want to commit that specific piece of the hunk right now. Save the hunk with :wq and git will move on to the next hunk of changes.

diff --git a/robots b/robots
index e69de29..3572eed 100644
--- a/robots
+++ b/robots
@@ -0,0 +1,3 @@
+R2D2
+Johnny #5
+Kitt
Stage this hunk [y,n,q,a,d,/,e,?]?

These changes look good! Let’s choose y to let git know that we’d like to stage this hunk of changes as is.

y [Enter]

At this point, there are no more changes to be added and git will exit the interactive --patch mode. If we take a look at git status, we’ll see that new heroes file is staged for commit and that it has modifications that are not staged for commit:

On branch master

Initial commit

Changes to be committed:
        new file:   heroes
        new file:   robots

Changes not staged for commit:
        modified:   heroes

Hopfully it’s clear that this is happening because the heroes file still contains our “Iron Man” line, and that line was not staged for commit thanks to us using the interactive --patch mode. Running git diff will confirm that that is indeed the change in question:

git diff
diff --git a/heroes b/heroes
index 81533d0..cf1fcb8 100644
--- a/heroes
+++ b/heroes
@@ -1,3 +1,4 @@
 Superman
 Wonder Woman
 Spider Man
+Iron Man

That’s It!

Using --patch mode effectively is a great way to keep your commits logical and narrow. I’ve found that it also helps to force me to take a closer look at all of my changes to ensure I’m only commiting code that should be commited. Take a moment to get comfortable using it in your workflow and it will pay great dividends in the long run with regard to your commit history.

By
Darrell Banks is a Sr. Full-Stack Engineer at InVision.

Like what you've been reading? Join us and help create the next generation of prototyping and collaboration tools for product design teams around the world. Check out our open positions.