Commit discipline with git add -p

A lot of people have god-awful commit discipline, like using git commit -a for every commit and writing nonsense because a bunch of changes were jumbled up. One of the biggest reasons for this is because they don’t know there’s a way to only commit part of a file at a time, and no, it’s not deleting the excess and then adding it back. (At least, not directly and not always — you do have the option with the e option of git add -p.)

Well, there is. And it’s git add -p.

Commit Discipline

The way you are supposed to write a commit is by staging what you want to commit with git add, and then actually writing the commit with git commit. In some sense, the fact that git commit has any more arguments (like filenames, but especially -a and -m) is really dangerous. But dangerous in the sense that an idiot or a lazy person will use them improperly — they have reasons for existing.1

Sometimes you know that all you changed was refactoring some module by renaming it, in which case git commit -a is justified. Or you just changed one file, initialized your project, etc. But in these cases you knew from the start exactly what you were going to change.

More often, when you’re writing the most important or complicated features, you’ll have a bunch of semi-related changes uncommitted. The wrong thing to do is git commit -a. The right thing to do is ask, “What’s the smallest set of related changes I can commit?” And then commit those.

So my workflow looks like

How does git add -p help?

What the workflow I described above requires is being able to separate changes in one file into separate commits conveniently. Running git add -p FILENAME lets you pick which edits you want to stage. It gives you an interactive prompt, and you can choose options like y,n,q,e,s (these tend to be the only ones that matter). They stand for yes, no, quit (aka answering no for every remaining prompt), edit (more on that in a second), and split, which splits a hunk into multiple sub-hunks. Split only works if the current hunk is not all in one chunk of consecutive lines.

Here’s an example of what pops up when running git add -p. Notice there is no s because you can’t split up these changes, since they’re on consecutive lines.

diff --git a/api/src/accounts/verify_email.rs b/api/src/accounts/verify_email.rs
   index abf1a7d..d870c98 100644
   --- a/api/src/accounts/verify_email.rs
   +++ b/api/src/accounts/verify_email.rs
   @@ -2,7 +2,7 @@

    use lib::{
        models::{
   -        table::Table,
   +        sql::Table,
            token::{RedisPool, RedisToken},
            user::User,
        },
   (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?

Now let’s talk about e, which is edit. Here you can directly choose which lines in a hunk you want to stage and which you want to ignore. When you open up the edit menu, you’ll be met with all the changes in the hunk, and what you want to do is remove the changes you do not want to keep.

That’s pretty much for git add -p. It’s a pretty simple tool, but it is very powerful. It makes it that much easier to keep your commit history clean, and that is why you should use it more often :)


  1. Particularly the -m flag of git commit. It makes it much easier for a shell script (like musiclist or GNU pass) to automatically generate commits. But these are situations where Git is not used as a development tool, and all changes are predictable to the point of being machine-generatable: add, delete, edit, or move a password.↩︎