Introduction: Why good commit messages matter
If you browse the log of any random Git repository, you will probably find its commit messages are more or less a mess. For example, take a look at these gems from my early days committing to Spring:
Yikes. Compare that with these more recent commits from the same repository:
Which would you rather read? The former varies wildly in length and form; the latter is concise and consistent. The former is what happens by default; the latter never happens by accident. While many repositories’ logs look like the former, there are exceptions. The Linux kernel and Git itself are great examples. Look at Spring Boot, or any repository managed by Tim Pope.
The contributors to these repositories know that a well-crafted Git commit message is the best way to communicate context about a change to fellow developers (and indeed to their future selves). A diff will tell you what changed, but only the commit message can properly tell you why. Peter Hutterer makes this point well:
If you haven’t given much thought to what makes a great Git commit message, it may be the case that you haven’t spent much time using git log and related tools. There is a vicious cycle here: because the commit history is unstructured and inconsistent, one doesn’t spend much time using or taking care of it. And because it doesn’t get used or taken care of, it remains unstructured and inconsistent.
But a well-cared for log is a beautiful and useful thing. git blame, revert, rebase, log, shortlog and other subcommands come to life. Reviewing others’ commits and pull requests becomes something worth doing, and suddenly can be done independently. Understanding why something happened months or years ago becomes not only possible but efficient.
A project’s long-term success rests (among other things) on its maintainability, and a maintainer has few tools more powerful than his project’s log. It’s worth taking the time to learn how to care for one properly. What may be a hassle at first soon becomes habit, and eventually a source of pride and productivity for all involved. In this post, I am addressing just the most basic element of keeping a healthy commit history: how to write an individual commit message. There are other important practices like commit squashing that I am not addressing here. Perhaps I’ll do that in a subsequent post.
Most programming languages have well-established conventions as to what constitutes idiomatic style, i.e. naming, formatting and so on. There are variations on these conventions, of course, but most developers agree that picking one and sticking to it is far better than the chaos that ensues when everybody does their own thing. A team’s approach to its commit log should be no different. In order to create a useful revision history, teams should first agree on a commit message convention that defines at least the following three things:
* Style.
* Content.
* Metadata.
Fortunately, there are well-established conventions as to what makes an idiomatic Git commit message. Indeed, many of them are assumed in the way certain Git commands function. There’s nothing you need to re-invent. Just follow the seven rules below and you’re on your way to committing like a pro.
The seven rules of a great Git commit message
Keep in mind: This has all been said before.
For example:
- Summarize changes in around 50 characters or less
- More detailed explanatory text, if necessary. Wrap it to about 72
- characters or so. In some contexts, the first line is treated as the
- subject of the commit and the rest of the text as the body. The
- blank line separating the summary from the body is critical (unless
- you omit the body entirely); various tools like `log`, `shortlog`
- and `rebase` can get confused if you run the two together.
- Explain the problem that this commit is solving. Focus on why you
- are making this change as opposed to how (the code explains that).
- Are there side effects or other unintuitive consequences of this
- change? Here's the place to explain them.
- Further paragraphs come after blank lines.
- - Bullet points are okay, too
- - Typically a hyphen or asterisk is used for the bullet, preceded
- by a single space, with blank lines in between, but conventions
- vary here
- If you use an issue tracker, put references to them at the bottom,
- like this:
- Resolves: #123
- See also: #456, #789
From the git commit manpage:
Firstly, not every commit requires both a subject and a body. Sometimes a single line is fine, especially when the change is so simple that no further context is necessary. For example:
- Fix typo in introduction to user guide
If you’re committing something like this at the command line, it’s easy to use the -m option to git commit:
However, when a commit merits a bit of explanation and context, you need to write a body. For example:
- Derezz the master control program
- MCP turned out to be evil and had become intent on world domination.
- This commit throws Tron's disc into MCP (causing its deresolution)
- and turns it back into a chess game.
And now git log --oneline, which prints out just the subject line:
Or, git shortlog, which groups commits by user, again showing just the subject line for concision:
There are a number of other contexts in Git where the distinction between subject line and body kicks in—but none of them work properly without the blank line in between.
2. Limit the subject line to 50 characters
50 characters is not a hard limit, just a rule of thumb. Keeping subject lines at this length ensures that they are readable, and forces the author to think for a moment about the most concise way to explain what’s going on.
Tip:
GitHub’s UI is fully aware of these conventions. It will warn you if you go past the 50 character limit:
And will truncate any subject line longer than 72 characters with an ellipsis:
So shoot for 50 characters, but consider 72 the hard limit.
3. Capitalize the subject line
This is as simple as it sounds. Begin all subject lines with a capital letter. For example:
4. Do not end the subject line with a period
Trailing punctuation is unnecessary in subject lines. Besides, space is precious when you’re trying to keep them to 50 chars or less. Example:
5. Use the imperative mood in the subject line
Imperative mood just means “spoken or written as if giving a command or instruction”. A few examples:
Each of the seven rules you’re reading about right now are written in the imperative (“Wrap the body at 72 characters”, etc.). The imperative can sound a little rude; that’s why we don’t often use it. But it’s perfect for Git commit subject lines. One reason for this is that Git itself uses the imperative whenever it creates a commit on your behalf.
For example, the default message created when using git merge reads:
- Merge branch 'myfeature'
- Revert "Add the thing with the stuff"
- This reverts commit cc87791524aedd593cff5a74532befe7ab69ce9d.
- Merge pull request #123 from someuser/somebranch
Writing this way can be a little awkward at first. We’re more used to speaking in the indicative mood, which is all about reporting facts. That’s why commit messages often end up reading like this:
And sometimes commit messages get written as a description of their contents:
To remove any confusion, here’s a simple rule to get it right every time. A properly formed Git commit subject line should always be able to complete the following sentence:
For example:
Notice how this doesn’t work for the other non-imperative forms:
Remember: Use of the imperative is important only in the subject line. You can relax this restriction when you’re writing the body.
6. Wrap the body at 72 characters
Git never wraps text automatically. When you write the body of a commit message, you must mind its right margin, and wrap text manually. The recommendation is to do this at 72 characters, so that Git has plenty of room to indent text while still keeping everything under 80 characters overall. A good text editor can help here. It’s easy to configure Vim, for example, to wrap text at 72 characters when you’re writing a Git commit. Traditionally, however, IDEs have been terrible at providing smart support for text wrapping in commit messages.
7. Use the body to explain what and why vs. how
This commit from Bitcoin Core is a great example of explaining what changed and why:
Take a look at the full diff and just think how much time the author is saving fellow and future committers by taking the time to provide this context here and now. If he didn’t, it would probably be lost forever.
In most cases, you can leave out details about how a change has been made. Code is generally self-explanatory in this regard (and if the code is so complex that it needs to be explained in prose, that’s what source comments are for). Just focus on making clear the reasons why you made the change in the first place—the way things worked before the change (and what was wrong with that), the way they work now, and why you decided to solve it the way you did.
The future maintainer that thanks you may be yourself!
Tips
Learn to love the command line. Leave the IDE behind.
For as many reasons as there are Git subcommands, it’s wise to embrace the command line. Git is insanely powerful; IDEs are too, but each in different ways. I use an IDE every day (IntelliJ IDEA) and have used others extensively (Eclipse), but I have never seen IDE integration for Git that could begin to match the ease and power of the command line (once you know it).
Certain Git-related IDE functions are invaluable, like calling git rm when you delete a file, and doing the right stuff with git when you rename one. Where everything falls apart is when you start trying to commit, merge, rebase, or do sophisticated history analysis through the IDE. When it comes to wielding the full power of Git, it’s command-line all the way.
Remember that whether you use Bash or Zsh or Powershell, there are tab completion scripts that take much of the pain out of remembering the subcommands and switches.
Read Pro Git
The Pro Git book is available online for free, and it’s fantastic. Take advantage!
沒有留言:
張貼留言