Git’s Patch Mode All the Way

If you have been using Git long enough, you have probably heard about git add -p/--patch, which allows you to selectively stage parts of files. However, did you know that many other Git commands support this argument as well? Among them are commit, reset, checkout, stash, and log, and they represent the main topic of the present post.

git add -p

Let’s start with the classic. Suppose you have added a feature and fixed a bug, and both of these changes required you to modify the same files.

$ git status -s
M CHANGELOG.rst
M retdec/decompiler.py
M tests/decompiler_tests.py

As a practitioner of good source control, you want to make your commits atomic, i.e. commit the feature and the bug fix separately. As we have made changes to the same files, we need to split these modifications. This can be done by using the so-called patch mode of git add:

$ git add -p
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -8,8 +8,10 @@ dev
 * Added support for selecting the optimizations to be used when compiling C
   source files.
+* Added support for stripping the compiled binary file.
 * Added support for printing script versions via the ``-V``/``--version``
   parameter.
+* Fixed the list of supported target high-level languages.

Stage this hunk [y,n,q,a,d,/,s,e,?]?

Git will ask us about every change we have made. For each block of changes (a hunk in Git parlance), we can either stage it (y), skip it (n), split it into smaller hunks (s), or manually edit what will be staged (e). Since we want to commit only e.g. the feature, we need to press s so Git splits the current hunk into two smaller hunks. Then, we stage the first hunk (* Added support...) and skip the second one (* Fixed...). We make a similar decision for the remaining two files.

After we have staged all the changes we wanted, we can commit them:

$ git commit

Tip: Enable colors in Git to make the hunks more readable.

A screenshot of a colored output from git add -p.

git commit -p

When all we want is to stage changes and then immediately commit them, we can run just

$ git commit -p

which is a shortcut for

$ git add -p
$ git commit

As with git add -p, Git will ask us which changes we want to stage, and then automatically commit them.

git reset -p

Suppose we have staged changes to be committed via git add. Then, we find out that we have accidentally staged something we did not want to include in the next commit. The situation is easy when we want to unstage all changes in a file:

$ git reset FILE

However, what if we want to unstage only some of the changes done to a file? Git’s patch mode to the rescue! Simply run

$ git reset -p FILE

Git will ask you about each staged hunk whether you want to keep it or unstage it.

Of course, if you want to unstage changes from multiple files, you can run just

$ git reset -p

This will make Git ask us about staged hunks in all files.

Tip: If you want to partially reset a commit, git reset -p 6b141d7 allows you to do that.

git checkout -p

Sometimes, you may want to throw away some of the changes you have done. The easiest thing to do is to run

$ git checkout -p

which will cause Git to selectively ask you about each hunk whether you want to keep it or discard it.

Beware: When you discard a hunk, it will be lost forever. Think twice before throwing away a hunk!

Another, less-known use of git checkout -p is when you want to apply changes from one branch to another branch. Suppose there is a branch bug-572-fix-attempt. As its name suggests, it contains an experimental fix of bug #572. The attempt turned out to lead to a dead end, but you still would like to apply some of the changes to your current branch. To do this, simply run

$ git checkout -p bug-572-fix-attempt

Git will selectively ask you about each changed block of code in bug-572-fix-attempt whether you want to apply it to the current branch. Say y to all the changes you want to include.

git stash -p

Stashing is a nice Git feature that allows you to save your unfinished changes, do some other work, and then re-apply the stored changes. However, as you quickly find out, running

$ git stash

will stash all the changes you have made. What if you want to stash only some of the changes? Yes, your guess is correct! We use the patch mode:

$ git stash -p

It will make Git selectively ask us about each change whether we want to stash it or keep it.

git log -p

Last, but certainly not least, you can use the patch mode in tandem with git log. Lets start with a motivation. During a code review, it is often useful to see a list of changes that were done in the commits under review. When you run git log, Git only includes basic information about each commit, e.g. its hash, author, and commit message. However, when you run

$ git log -p

Git will also include the changes that were done in each commit:

A screenshot with a sample output from git log -p.

There you go. As you can see, Git’s patch mode is very powerful. If you know other useful scenarios, be sure to leave a comment!

Discussion

Apart from comments below, you can also discuss this post at r/programming.

17 Comments

      • If terminal is your thing, you may try tig, a curses-based git client.

        Press s to stage, then enter the file to see diff and then u to stage a chunk of file, 1 to stage a single line, \ to split the chunk into smaller ones.

        Reply
  1. With all due respect, this seems like the worst idea in the world for programming applications!

    If you stage a partial change – how do you know all your automatic testing works? Heck, how do you even know if it compiles?

    I can see doing this occasionally when I’m on an experimental branch, or conceivably if I’m preparing a very complex series of commits, but the idea of creating a commit with no idea whether it even compiles is at best building up work for me later, and at worst an accident waiting to happen.

    Reply
    • All tools, techniques, etc. have their pros and cons. I would not dismiss a feature just because it may be used improperly. For me, Git’s patch mode represents a handy helper that I use regularly in my workflow to e.g. create atomic commits. Without it, I would waste more time than getting around problems that it may cause. However, I agree with you that blind use may get you into troubles later.

      Reply
    • I use Mercurial and use partial commits extensively. The GUI I use (TortoiseHg) makes it easy. To ensure the partial changes compile (you are absolutely right, it can bite from time to time), I do it the opposite way. That is, I shelve (stash in git parlance) the changes I do NOT want to commit, check everything compiles and works, then commit and unshelve.

      Reply
  2. git log -p seems like an odd one out in this listing. All the other -p flags are for choosing among bits of code changed below commit granularity. On git log, the -p flag chooses between showing the contents of a commit or not.

    Reply

Leave a Comment.