Crafting Commits in Git

Posted on by in Process

A commit, like a well-designed function or class, should represent a single concept. A distinct, cohesive commit is easy to understand, review, and, if necessary, revert.

Git’s powerful staging area allows you to finely craft your commits. You decide which files and even which changes in a file, down to the individual line level, to commit.

Let’s take a look at how to use Git’s staging area to divide a large set of un-related changes into several smaller, well-defined changes.

Staging Parts of a File

Here’s a repo with some unstaged changes.

In this file, two new methods were added to a class. Instead of adding both methods in a single commit, let’s create a separate commit for each method.

git add‘s -p or --patch option allows you to stage parts of a single file.

In a partial staging session, Git divides the changes in a file into hunks and then moves from hunk to hunk, asking what to do with each hunk. “?” explains each of the possible choices.

In this example, we’ll choose “s” to split the current hunk into two smaller hunks.

Git asks us what to do with the first of the two new hunks. Choose “y” to stage it.

Then, Git asks about the second hunk. Enter “d” to not stage this hunk and finish the staging session.

git status shows that we have staged and unstaged changes.

Commit the staged changes.

Now, all the remaining changes are related.

These changes can be committed as a whole.

Finer-Grained Commits with Hunk Editing

When a hunk is too big for a single commit, Git allows you to edit it and stage as little as a single line.

Continuing with our Foo example, let’s change the letter-casing in the output of some of the public methods, add some logging, and remove a method.

Each change is distinct, and should be committed in a separate commit. Choose the “e” option during a partial staging session to edit the hunk.

Git will then open the hunk in your editor (the single “.” character at the start of each blank line is how my editor, in this case vim, is configured to display a blank space character).

Our first commit will be the change to the output letter-casing, i.e., from lowercase to uppercase. We don’t want to include the logging in this commit, so we’ll delete those two lines. This is only changing the hunk to be staged and eventually committed; the file still contains the two logging lines. We also don’t want to include the removal of the private Foo#qux method, so we’ll replace the “-” character at the beginning of the those lines with a single blank space character. This tells Git to not delete those lines in this hunk.

Save and quit the editor to stage the hunk. git diff --cached shows that we’ve only staged the change to the output letter-casing.

git diff displays the unstaged logging changes and Foo#qux removal.

Let’s commit our staged changes before staging our next set of changes.

We can use the same process to stage and commit the logging changes.

When editing the hunk this time, leave the two logging lines but again replace the beginning “-” character with a single blank space character in each of the lines removing Foo#qux, this tells Git to not remove these lines in this hunk.

git diff --cached shows that we’ve only staged the logging changes.

Commit the staged changes.

The removal of Foo#qux is now our only local change.

So we can commit these final changes as a whole.

Git’s Powerful Staging Area

Become a better committer by learning how to use Git’s staging area. Partial staging and hunk editing bring an incredible level of precision to committing. Distinct, well-designed commits will make your code easier to review and result in a clearer, more understandable development history.