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
- Run the appropriate
git add
andgit commit
operations to get, in some order, the changes I want to push to upstream. Typically, I do the smallest/simplest commits first. git rebase -i
and swap the order of commits if necessary.
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.
- In order to remove a deletion, simply remove the
-
character. (You will have to insert a space in the line to compensate.) Do not remove the rest of the line. - To remove an addition, remove the entire line.
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
:)
Particularly the
-m
flag ofgit 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.↩︎