I care a lot about commit messages and the sanctity of the git log. I’ve even written on the topic before. Commits are an after thought for a lot of programmers—and I mean a lot. It’s one of those bits that many programmers just phone in after the “real work” is over or, worse, just AI it bro.

I’ve changed up the way I handle commits just a little bit as of late in response to a sort of… problem I have.

I don’t believe in any particular PR1 size. I just don’t. Some places have policies about it which, in my experience, lean on small PRs because they’re “easier to review” or “easier to revert.” To me, these concerns are entirely secondary. I do not like committing half-baked, half-assed, or half anything to a codebase.

Some features lend themselves to nice, neat little additions. That’s fine. That’s a nice easy review. These are typically iterative additions to an existing data model or at least semi-mature part of a system. One or two commits for 50-100 lines do not require a ton of process. Just follow a decent convention and remember why you commit and all will be right in the world.

The trouble comes when you have a very big PR. As a lead who is generally banging out core functionality on systems I work on, I have the luxury—and it is indeed luxurious—of building a lot of new stuff. At times I am adding some major new piece of functionality or a foundational data model (sometimes many) to the system and I honestly cannot stand piecemealing that sort of thing over multiple PRs.

A major feature should be reviewed with full context, entirely together. Moreover, when you treat all code like a liability—because it fucking is—you get into the habit of avoiding adding things that are incomplete to a codebase. There have been times where the childlike whims of an executive have caused the abortion of a feature I’ve already put a week into. Sure, I don’t get all the delicious the metrics of committing something that never worked and then reverting it2 but better to the dustbin than in and out of production.

The problem with this approach is when co-workers see a +3,000 line PR, they do not necessarily want to review it because it’s like, “Oh yeah, I have a whole day to spend on that.” (I do have an engineer on my team who is godlike in this regard. If anyone I know is reading, they know who I speak of!) Again, as a team lead I have the luxury of basically forcing people to do my bidding and, of course, my team just loves learning at the feet of the master3 but seriously… ain’t nobody got time for that! (Actually, you ain’t got time not to do it, but that’s another story entirely.)

In recent history I have modified my approach to commits specifically around this problem and, while it’s certainly nothing groundbreaking and I doubt I’m even remotely the first person to do this, I was pleased enough with the results to write about it and continue using it. This is in direct response to some soft criticism from my own team. (I know y’all pad your comments with “lol” but… I know. I know.)

I’ll talk about my general approach to putting together a PR and then the sort of philosophical shift I’ve made as of late.

Terminal git

I need to take a moment, before I go on, to address a sort of elephant in the room. If you’re not using some kind of decent GUI program for interacting with git, you’re almost certainly not very good with git and your git log is mediocre at best, short of being some kind of savant.

To really use git well, in my opinion, you need to git gud with rebasing or, at a minimum, wait until the end to commit anything. Rebasing—specifically interactive rebasing—is, as I like to say, the tits. (If you don’t know, I invite you to fire up your favorite search engine and educate yourself.)

When working on large scale PRs, it also makes a lot of sense to commit some files in chunks. If this is somehow trivial from the command line, great. You’re a savant. Have at it. Don’t pretend that most of your co-workers are also bonkers.

The PR Process

I commit at three different points, and one of them is temporary:

  1. When an entire feature is complete, tests are written, and documentation is done.
  2. When a part of a feature is complete enough to do the above.
  3. When I want to flag a bunch of code as a work in progress and push my branch to the remote repo because it’s Friday and in case I die or get fired for refusing to prompt fondle Claude, my work is preserved somewhere. This is the temporary one.

Committing is the third major review of code for me. The first and second are documentation and testing. These can be separate or go together and the order doesn’t matter. Sometimes documentation even precedes code. It’s mostly dictated by a combination of mood, feature particulars, and how much groundwork was done before the coding started.

Either way, by the time I’m committing, things appear to work and I have explained myself in the codebase so future generations can see how delusional I am. It’s working. Now… let’s start committing.

This is where the GUI is nice. It can be built into your IDE or text editor or whatever. As long as you can see diffs clearly, review them, and then commit them by chunks rather than whole files, you’re good.4 Seriously though, stop relying on your terminal for commits and actually look at your diffs!

At this point I start committing logical groups of things that I can either write a decent commit message about or that don’t require one. In many cases the documentation that’s part of the committed code tells the story and you don’t need summarize it even more in the commit message. If it’s not, or historical color is needed, have at it. I don’t do more for the sake of more.5

If you ever look over my commit log you’ll often see a metric ton of code committed in like a 20-30 minute span. This is me going through this process. I will amend and rebase and sometimes even reorder commits to make the story of the process clearer. If you’re using a decent GUI with a good interactive rebase process, this is trivial.

At the end, each commit really stands on its own and has been explained as much is needed for historical purposes. Even a monster PR is broken up into digestible chunks. It’s also given me pause to review all my own code, docs, and tests. Quite often this is where I’ll catch a number of small issues, missing docs, etc.

Does this take time? Yes.

Is the final product better? Yes.

Is it worth it? Yes.6

The Latest Tweak

The process above naturally lends itself to PRs that are relatively easy to review regardless of their size. Even the dumpster fire that is the current state of Github has a very usable commit-by-commit view for reviewing PRs.

All that has changed is that I think about three things very specifically in the context of reviewers:

  1. The commit messages are very specifically about guiding a reviewer and explaining why something was done. This is already a good practice but I’ve found that thinking very specifically in terms of a review has really improved the quality and general usefulness if what I put in there. Asking myself, “What would someone doing a review need to know that isn’t obvious or implied by the code in the commit?” leads to very natural and useful messages. (Suck that Copilot!) In a total PR they can tell a story, which is handy.
  2. This is harder to quantify specifically, but I tend to break up the PRs a little more. Sometimes I’ll just include a utility function or something in the PR where its functionality is being used, but when I really think about a review, I don’t. Again, this might already be the good practice of some of y’all out there, but I’ve found that thinking specifically about the review makes me more mindful of this sort of thing.
  3. Commit order. Sometimes I’ll get my PR ready and just look over the commits and move stuff that’s supportive or even incidental to the front or back of the PR. (I like to make little fixes to unrelated code if I notice issues as I go. Going through an entire CI process7 for a typo fix is complete madness.) This keeps the feature bits in order.

This is more than “nice to have” and it still translates well to smaller PRs. After all, the true value of the commit log once code has shipped is your ability to understand changes in as much isolation as is sensible when trying to figure out where things went wrong. Not only does this improve the review process significantly, but it lends itself very naturally to this process.

I believe most programmers would benefit from a process like this over all. Of course, pretty much any time you turn some autopilot process into something mindful, the end result is better.8

I haven’t bothered looking, but I doubt I’m the first to say any of this. Think of this as my personal observation on the process as a sort general internet +1.

  1. I didn’t realize how stupid the term “pull request” was until I worked at a place that used Gitlab and saw that it used the term “merge request” because, of course, that’s what they fucking are. Bad naming is everywhere and in everything. 

  2. Wait a minute, are you telling me that not every company values these completely useless numbers? This is my own private hell? Or, at least a hell that only some of us are subjected to? Oh goody. I swear, I did something really shitty in some past life. 

  3. This has become sort of a running joke on our team. My name is automatically associated with huge PRs. While the juniors do learn a thing or two looking over my work, it’s still a lot. 

  4. I currently use Fork. I like it a lot. I started using GUIs when a guy I worked with about 15 years ago was watching me do some stuff, saw me committing from the terminal and was like, “Bro, what are you? Some kind of cave man?” He introduced me to Sourcetree and it was almost as transformative as when I started using git in the first place. (My, my… the dark days before I used source control.) I don’t even remember why I stopped using Sourcetree. I guess I hate Atlassian on principle. First there was Jira, the world’s slowest and most convoluted project management system that no one likes except people with major head injuries (so product managers and executives). Now their front page reads, in big bold letters right at the top, “Unleash your teams and their agents.” Please fuck right off. Just imagine if all the effort that has been poured into trying to make inherently nondeterministic chatbots try to do deterministic things was spent on… just building deterministic and reliable workflows. Okay. That’s enough. (Fuck Jira. Sideways.) 

  5. That’s what Claude zombies are for and many of us are over it. Hey, by the way… fuck you guys. kbyeee! 

  6. I was going to have a more nuanced answer but I’m done pretending that sloppy or hurried work is anything but shit. It’s not acceptable. It’s not good. Something isn’t truly done if this is the standard. If only your direct executive hadn’t rotted their brain away huffing paint fumes, they’d understand that. And don’t even get me started about your product manager. Why does this department even exist!? 

  7. Maybe it isn’t like this everywhere but the CI process where I work is just… it’s like everything there. Every single process and design feels like Rube Goldberg was somehow involved. 

  8. Yeah, this is another pot shot at AI zombies. What am I supposed to do though? You people bring slop to the party. Try going to a real party with pig slop instead of pizza and beer and see what happens. No one likes you. I don’t even think you like you.