“Commit Patch”: Managing your mess

I consider myself to be chaotically organized. If you look at my workbench you’ll see stacks of papers and half finished projects. However, when I finally clean up I end up filing things away meticulously.

The same chaos applies to my development work. If I’m working on a bug or a new feature I end up making other random enhancements or fixes that aren’t at all related (except for the fact that they exist in the same file). Judging from other people I’ve worked with, I’m hardly alone in this behavior.

The problem comes when I’m ready to become organized and check in my work (the software equivalent of “file things away meticulously”). Version control systems don’t cater to the messy development model. Take CVS and Mercurial: They only let you commit a whole file at a time, not just pieces of it. Darcs is better, as it allows you to commit pieces of a file, but even it doesn’t let you commit just one part of a single line.

Why would anyone want to do this? Look at this made up example:

 void player_died()
 {
     game_over(score);
     char x[50];
     get_name(x);
     add_high_score(x, score);
 }

Say I need to change the way get_name() works–I need to make sure I don’t overrun my buffer. While I’m doing that I decide that “x” is a really stupid name for the variable, so I fix that too. The code now looks like:

 void player_died()
 {
     game_over(score);
     char name[50];
     get_name(name, sizeof(name));
     add_high_score(name, score);
 }

Fine. But now I have 2 unrelated changes that touch the same source line. In the past I’d just check them both in with a comment like this:

 - Pass in length with high score string storage.
 - x -> name for clarity.

Enter commit-patch With commit-patch I now can precisely specify the patch I want to commit and leave the rest behind as uncommitted changes in my working directory.

Ok, so how does it work?

First I use emacs to get a diff (I make sure I have an emacs library for whatever version control system I’m using). Here’s an example of what darcs might show me:

 diff -rN -u old-test/game.c new-test/game.c
 --- old-test/game.c     2006-08-22 23:09:44.000000000 -0700
 +++ new-test/game.c     2006-08-22 23:09:44.000000000 -0700
 @@ -1,8 +1,8 @@
  void player_died()
  {
      game_over(score);
 -    char x[50];
 -    get_name(x);
 -    add_high_score(x, score);
 +    char name[50];
 +    get_name(name, sizeof(name));
 +    add_high_score(name, score);
  }

Emacs has diff-mode which makes editing patches trivial. I now edit it down to this:

 --- old-test/game.c 2006-08-22 23:11:52.000000000 -0700
 +++ new-test/game.c 2006-08-22 23:11:52.000000000 -0700
 @@ -1,8 +1,8 @@
  void player_died()
  {
      game_over(score);
      char x[50];
 -    get_name(x);
 +    get_name(x, sizeof(x));
      add_high_score(x, score);
  }

Now I can check this in with commit-patch (along with the corresponding change in the get_name() function’s file). Since I’m using emacs I can just use the “C-c C-c” key sequence in any diff-mode buffer which runs commit-patch for me. Or I could save the patch out to a file and run commit-patch from the command line. Either way, it commits only what was in the the patch and leaves the working directory effectively untouched. To demonstrate, my diff after I’ve committed the above patch looks like this:

 diff -rN -u old-test/game.c new-test/game.c
 --- old-test/game.c     2006-08-23 14:58:54.000000000 -0700
 +++ new-test/game.c     2006-08-23 14:58:54.000000000 -0700
 @@ -1,8 +1,8 @@
  player_died()
  {
      game_over(score);
 -    char x[50];
 -    get_name(x, sizeof(x));
 -    add_high_score(x, score);
 +    char name[50];
 +    get_name(name, sizeof(name));
 +    add_high_score(name, score);
  }

Now I can check in the file again, this time for just the variable name change.

commit-patch allows you to keep your commits short and to the point instead of forcing you to check in a bunch of unrelated changes just because they happen to be in the same file (or the same line). It relies on you editing patches which I admit can be scary at first, but after a few times you really start to get the hang of it. I now always look over the diff of my uncommitted changes and weed out stupid changes in my code–debug prints, commented out dead code, and all the other junk I shouldn’t check in.

Ok, sounds great, what are the downsides?

Glad I asked. Checking in patches effectively means checking in untested code. Despite your best intentions, you can screw up the code when editing a patch. Generally the projects I work on where this really matters all have internal infrastructure to make sure builds work. One project I am involved with that uses CVS has an auto-builder to check for broken code. Another large darcs project I deal with requires ‘make’ to run successfully before it accepts a patch. Both of these provide good enough insulation for bad patches. The truth is, I’ve been using commit-patch for a few years now and I rarely have accidental errors checked in that were caused by bad patch editing. I would say I’m more likely to forget to check in a file completely than I am to check in a bad patch.

The only other downside is that it requires you to be able to edit patches. I use emacs which makes it pretty trivial. If you are not an emacs user then you’d have to look around and see what other patch editing options are out there. I honestly don’t know what other tools exists. So if you consider needing to use emacs a downside, then there you go. If you don’t, well then I salute you. :-)

Conclusion

commit-patch fits my development style like a glove. I frankly cannot imagine working on a project without it any more. That is an interesting statement because before we wrote it, Jim and I felt that it would be a script we would use only occasionally. But once I started using it I found more and more situations where it would be useful.

I think commit-patch is one of those tools that you don’t realize you need until it suddenly becomes indispensable. So do yourself a favor and try it out for a week or two.

Get commit-patch here

This entry was posted in Software. Bookmark the permalink.

2 Responses to “Commit Patch”: Managing your mess

  1. Fabrice says:

    I’m using “git record” for that, with record defined as follows in your Git configuration file:

    [alias]
    record = ! sh -c ‘(git add -p — $@ && git commit) || git reset’ –

  2. david says:

    Looks like we have a Darcs veteran here. :-).

    So this was written before Git was pervasive. And I agree that Git has lots of nice built-in stuff for just this sort of thing. Commit patch was originally written to give this great functionality to older VCSes like CVS and SVN. I would say the killer feature of commit-patch is its Emacs integration. The seamless editing and committing of patches right from within Emacs is my main use case for commit-patch with Git.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>