How to Not Merge Work-in-Progress Commits in Git

When using Git on a project I’ll create branches that have work-in-progress commits. That is, commits I intend to later go back and modify by fleshing out their commit message, combining or separating them with other commits, and so forth. I recently screwed up and merged a branch with such a commit onto a project, and other developers have already added work on top of it so there’s no changing that part of the project’s history now.

So I came up with a simple way to stop myself from merging work-in-progress commits in the future, and in this post I’ll explain how.

Prerequisites

What I’m about to describe is based on two conditions:

  1. Commit messages begin with WIP if they are work-in-progress. This is a personal habit. I’ll start the message with those three letters and then type some brief notes about what I intend to come back and clean-up or change later.

  2. When merging I am creating a merge commit. Or to put it another way, I am not performing a fast-forward merge, e.g. git merge --no-ff.

Introducing Hooks

Git provides a system of hooks you can use to perform arbitrary actions at certain times. You will find samples of each hook in any project using Git if you look in the directory .git/hooks/. For the sake of this article we’re interested in prepare-commit-msg. As the link above says:

The prepare-commit-msg hook is run before the commit message editor is fired up but after the default message is created. It lets you edit the default message before the commit author sees it. This hook takes a few options: the path to the file that holds the commit message so far, the type of commit, and the commit SHA-1 if this is an amended commit. This hook generally isn’t useful for normal commits; rather, it’s good for commits where the default message is auto-generated, such as templated commit messages, merge commits, squashed commits, and amended commits.

When I perform a merge Git automatically creates a default commit message that contains the first line of each commit I’m merging. I take advantage of this to prevent myself from merging anything that has one of my work-in-progress commits by searching the commit message for any line beginning with WIP. You can write hooks in any language. Git only cares that they’re executable and have the correct name, e.g. prepare-commit-msg in my case is a Perl script that looks like this:

exit 0 unless $ARGV[1] == "merge";

open(my $commit_message_file, "<", $ARGV[0]) or exit 1;

while (<$commit_message_file>) {
    if (/\s+WIP/) {
        print "Not merging because of 'WIP' commit.\n";
        exit 1;
    }
}

exit 0;

The script does the following:

  1. Checks that the second argument to the hook is merge. Git will give different values depending on the nature of the commit. So if it’s not merge the script immediately ends with an exit code of zero, which indicates success to Git.

  2. Opens the file named by the first argument. For a merge this is going to be .git/MERGE_MSG, although that is not actually relevant.

  3. Searches the file for any line containing whitespace followed by WIP. Because of the way Git pre-formats commit messages for merges this will find any commit of mine that starts with those three letters.

  4. If it finds such a line, the hook exits with anything non-zero, which tells Git to stop the commit. Otherwise it returns zero if everything looks good.

That should be easy to write in your language of choice. Unless you’re using—I don’t know—J. (I love that language, by the way.)

Conclusion

Hopefully this post has demonstrated a simple and practical use-case for Git hooks. In my experience hooks are not often necessary. But I still recommend reading about them because inevitably you will find yourself with a problem that a hook can solve most easily.

Advertisements

Add Your Thoughts

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s