loading...

Git and the interactive patch add

etcwilde profile image Evan Wilde Updated on ・4 min read

Sometimes, while working, we see a fix that should be made to a part of a file that is near our work area, but is not directly related to what we are working on. Do we make the fix now and just throw it in with the wash when we add the file, or do we wait and hope to remember to fix it so that it can be committed separately?

The answer is, neither. We make the fix now, but instead of just adding the entire file, we can select "hunks" of code that we want to have in our commit using the git add --interactive file.txt or git add --patch file.txt. Both will result in an interactive console that shows the hunks, or grouped lines being changed, that you can either select to be staged, or ignored.

Git add patch

What does it do? It lets you interactively select chunks of code that you want staged for commit from a file, while leaving out other changes in the file.

Upon invoking git add --patch, it will show the changes in the file and give you a prompt. You can enter one of 9 different commands depending on what you want to do at the first stage, but there are other commands too; here is a list of all of the commands you can give to git add --patch

  • y - Stage this hunk
  • n - do not stage this hunk
  • q - quit; do not stage this hunk or any other hunks
  • a - stage this hunk, and all hunks after this one
  • d - do not stage this hunk, or any later hunks in the file
  • g - select a hunk to go to
  • / - search for a hunk using a 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 the list of commands

Enough talk, examples please

Okay, lets imagine a very simple setup. Let's say I'm adding documentation to functions in the following C file (call it file.c).

#include <stdio.h>

int printHelloWorld() {
  print("hello world");
  return 0;
}

int main(int argc, char *argv[]) {
  printHelloWorld();
  return 0;
}

I get to the printHelloWorld function and add the documentation.

/**
 * printHelloWorld
 * writes "Hello world" to the console
 *
 * params: none
 * return: 0 if the function executed correctly
 */
int printHelloWorld() {
  print("hello world");
  return 0;
}

... but I also notice that someone wrote print instead of printf. That's a bug (which admittedly, if they had the right pre-commit hooks or test in place, would be caught long before it ever reached a public repository, but hang with me).

I fix it and end up with

/**
 * printHelloWorld
 * writes "Hello world" to the console
 *
 * params: none
 * return: 0 if the function executed correctly
 */
int printHelloWorld() {
  printf("hello world");
  return 0;
}

I continue adding the rest of the documentation as I should and end up with the final file

#include <stdio.h>

/**
 * printHelloWorld
 * writes "Hello world" to the console
 *
 * params: none
 * return: 0 if the function executed correctly
 */
int printHelloWorld() {
  printf("hello world");
  return 0;
}

/**
 * main
 * runs the program
 *
 * params: none
 * return: 0 if program executed without error
 */
int main(int argc, char *argv[]) {
  printHelloWorld();
  return 0;
}

Documentation is done, but I made that one bug fix as well, and it should not be included in the changes made.

We run git add --patch file.c

diff --git a/file.c b/file.c
index 7afa2a7..4293372 100644
--- a/test.c
+++ b/test.c
@@ -1,10 +1,24 @@
 #include <stdio.h>

+/**
+ * printHelloWorld
+ * writes "Hello world" to the console
+ *
+ * params: none
+ * return: 0 if the function executed correctly
+ */
 int printHelloWorld() {
-  print("hello world");
+  printf("hello world");
   return 0;
 }

+/**
+ * main
+ * runs the program
+ *
+ * params: none
+ * return: 0 if program executed without error
+ */
 int main(int argc, char *argv[]) {
   printHelloWorld();
   return 0;
Stage this hunk [y,n,q,a,d,/,s,e,?]? 

We choose to break the hunk into smaller pieces, choosing s.

Split into 3 hunks.
@@ -1,3 +1,10 @@
 #include <stdio.h>

+/**
+ * printHelloWorld
+ * writes "Hello world" to the console
+ *
+ * params: none
+ * return: 0 if the function executed correctly
+ */
 int printHelloWorld() {
Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]? 

If we want to commit the documentation first, we would press y, staging this hunk and going to the next hunk. If we want to commit the bugfix first, we would press n, and not stage this hunk, but still go to the next one. I choose to do the bugfix first (note that we can always re-order commits with a simple rebase after the matter). We choose n.

@@ -3,5 +10,5 @@
 int printHelloWorld() {
-  print("hello world");
+  printf("hello world");
   return 0;
 }

Stage this hunk [y,n,q,a,d,/,K,j,J,g,e,?]? 

This is the only one we want, so we choose y. which will take us to the last hunk, which again is documentation, so we just choose q to quit.

If we run git status now, we'll see that file.c is both staged and not staged. Running git diff --cached, shows us that our bugfix is ready for staging, and git diff shows us that our documentation has not been staged. Just run a quick git commit to commit the bugfix. Then, since the rest is just documentation, we can do a normal git add file.c followed by the standard git commit.

We'll end up with a repo with the following commits;

* aa7b7ec (HEAD -> master) adding documentation to functions
* ecc0aa7 bugfix: fixing print to printf
* e106991 Writing the program

Closing up

With git add --patch we can interactively select only the changes from a file that we want. So we can fix the changes that we see, as we see them, but we don't need to include them in the commit, which allows us to logically separate changes nicely, while being able to make bug fixes and typo-corrections as we work.

Discussion

pic
Editor guide