Recently I had a coworker introduce the mental model of one pull request containing a single commit. At first I immediately rejected it as heretical. Such an idea seemed to be in opposition to many "best practices" we're taught in learning git. Nonetheless, I heard him out and there are some interesting advantages.
Address future maintainers while fresh
It's worth asking: what is the purpose of a commit? Here are some real life commit messages from a PR of mine:
- Introduce tooltip for Change Lead Time
- Apply text changes proposed by product
- Include small typo fix in admin
While these commits may communicate something to an immediate observer of a PR, they may be useless if not confusing months down the road when someone looks at a particular line's commit message with GitLens.
Compare that to the following (shortened significantly for brevity)
Force a browser hard navigation if the app is stale
Since we introduced code splitting via splitVendorChunkPlugin()
with Vite (see PR 7420 19c7355), now Vue SFCs are lazily loaded.
Each module is hashed `[name]-[hash].js` according...
Writing a good commit message answers two important questions:
- What is the overall objective of this PR?
- How should a developer months/years from now interpret the 'why' of these code changes?
Arguably, these might often be the same question. Nonetheless for me both are easiest to articulate after the initial major code change (i.e., first commit). Waiting until approval to introduce a comprehensive squash commit message often means I may have to re-orient to context and recall the motivations for changes. Context switching is a heavy cost.
Switching to a model of one commit per PR forces one to think long term earlier and to provide helpful context in commit messages that will be available to developers even if version control cloud provider, issue tracker, etc. are changed.
Encourage better collaboration
One compelling argument for smaller commits is to be able to walk my peers through my process of code changes. This can be very useful for learning no doubt. But arguably it can be accomplished much better with other strategies:
- Smaller pull requests
- Comments (PR or in code)
- Pair programming
Enabling your reviewer with the knowledge to comprehend and adequately review your code changes is important. Enabling your reviewer and your future maintainers is even better. Pairing, github commenting, and smaller PRs help in that they ideally improve your code coherence. But at the end of the day your maintainers (including you) are left with only one resource. The code commit. Why not treat the code commit as a first class equal partner to the code changes it describes?
If developers rely on the suggested tools rather than commit walkthroughs, they will become better collaborators and enablers on their teams.
My current git workflow
# zshrc
alias gcf='git commit --amend --reuse-message HEAD'
alias gpf='git push --force-with-lease'
- branch from master and make a significant change
git commit -m 'long commit message'
- make a change
-
gcf
gpf
- make a change that merits explanation to future maintainers:
-
git commit --amend
gpf
Here is an example of how I am trying to write commits for better code maintenance. You can even see some of the earlier versions of that same commit in the timeline.
Potential Exceptions
Configuration migration
You're moving an entire configuration from one file to another but you also want to introduce some changes. This happened for my team when we wanted to move eslint config from package.json. Putting these in the same file first in one commit then noting the changes in another is preferable. See also git blame --ignore-rev
Branch off of non-trunk branch
Sometimes it makes sense to branch off of a non-trunk branch. For example, I work with a distributed team that sometimes doesn't get to my PRs for 12+ hours. Sometimes I want to introduce multiple PRs that will depend on changes in a previous. In this case, a tool like GitHub will intelligently watch and once a target branch is merged into main, it will update target of PR to main. Rewriting git history with something like git commit --amend can break this and will likely require some conflict resolution and manually updating the merge target.
Top comments (0)