為什麼好的 Commit Message 這麼重要?

每次看到亂七八糟的 git commit message,我都會覺得渾身不對勁。所謂的「亂七八糟」,我指的是這幾種情況:

  • 格式毫無章法,東一筆西一筆
  • 寫得太含糊,讓人看了也不知道改了什麼
  • 組織混亂,難以理解

好的 commit message 應該要簡潔、有條理、格式一致。寫好它當然要花點心力,但有幾個理由值得我們這樣做:

  • 就像程式碼一樣,commit message 寫一次但會被讀很多次。花時間寫好它是值得的。
  • 如果容許爛 commit message 存在,等於在傳達一個訊息:你不在乎程式碼的可維護性。這對建立良好的工程文化很不利。

Chris Beams 寫的 How to Write a Git Commit Message 是我讀過關於這個主題最好的文章。Chris 很清楚地解釋了為什麼好的 commit message 很重要,以及該怎麼寫。你可以從這個範例看看好的 commit message 長什麼樣子:

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 consequenses 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

這個範本是基於著名的 Tim Pope’s Note About Git Commit Messages

大家都怎麼寫 Commit Message?

我不打算重複別人已經說過的東西。我想做的是蒐集一些真實世界的範例,看看可以從中學到什麼。

Linux

Subsurface

Subsurface 是 Linus Torvalds 開發的專案,他當然也是 Git 的創造者。以下是 Subsurface 開發者使用的格式:

Header line: explain the commit in one line (use the imperative)

Body of commit message is a few lines of text, explaining things
in more detail, possibly giving some background about the issue
being fixed, etc etc.

The body of the commit message can be several paragraphs, and
please do proper word-wrap and keep columns shorter than about
74 characters or so. That way "git log" will show things
nicely even when it's indented.

Make sure you explain your solution and why you're doing what you're
doing, as opposed to describing what you're doing. Reviewers and your
future self can read the patch, but might not understand why a
particular solution was implemented.

Reported-by: whoever-reported-it
Signed-off-by: Your Name <[email protected]>

如果改動本身很簡單,好的 commit message 可以非常簡潔:

Set proper mode for file open dialog

Fixes #913

如果有需要,也可以寫得很詳細:

Planner: Count the minimum stop time towards o2time

When doing backgas breaks, count the minimum stop time towards o2time.
Previously, the initial minimum stop wasn't counted, so the time of the first
segment on oxygen was min_switch_duration + 12 minutes.

E.g. with 1 minute minimum switch duration.

Previously:
depth   duration        runtime gas
40m     1min    1min    air
40m     34min   35min
21m     2min    37min
21m     1min    38min   EAN50
18m     1min    39min
15m     3min    42min
12m     4min    46min
9m      5min    51min
6m      13min   64min   oxygen      <--13 minutes on O2
6m      6min    70min   air
6m      2min    72min   oxygen
0m      1min    73min

Now:
depth       duration        runtime gas
40m 1min    1min    air
40m 34min   35min
21m 2min    37min
21m 1min    38min   EAN50
18m 1min    39min
15m 3min    42min
12m 4min    46min
9m  5min    51min
6m  12min   63min   oxygen
6m  6min    69min   air
6m  2min    71min   oxygen
0m  1min    72min

Signed-off-by: Rick Walsh <[email protected]>
Signed-off-by: Dirk Hohndel <[email protected]>

Go

貢獻指南中給出了以下範例:

math: improved Sin, Cos and Tan precision for very large arguments

The existing implementation has poor numerical properties for
large arguments, so use the McGillicutty algorithm to improve
accuracy above 1e10.

The algorithm is described at http://wikipedia.org/wiki/McGillicutty_Algorithm

Fixes #159

我其實很喜歡 Go 開發者使用的簡潔風格:

fmt: don't unread eof scanning %x

When scanning a hex byte at EOF, the code was ungetting the eof,
which backed up the input and caused double-scanning of a byte.

Delete the call to UnreadRune.

This line appeared in 1.5 for some reason; it was not in 1.4 and
should be removed again for 1.5

Fixes #12090.

Change-Id: Iad1ce8e7db8ec26615c5271310f4b0228cca7d78
Reviewed-on: https://go-review.googlesource.com/13461
Reviewed-by: Andrew Gerrand <[email protected]>

這是另一個寫得很好的 commit message

runtime: make sure heapBitsBulkBarrier cannot be preempted

Changes the torture test in #12068 from failing about 1/10 times
to not failing in almost 2,000 runs.

This was only happening in -race mode because functions are
bigger in -race mode, so a few of the helpers for heapBitsBulkBarrier
were not being inlined, and they were not marked nosplit,
so (only in -race mode) the write barrier was being preempted by GC,
causing missed pointer updates.

Filed issue #12069 for diagnosis of any other similar errors.

Fixes #12068.

Change-Id: Ic174d9b050ba278b18b08ab0d85a73c33bd5b175
Reviewed-on: https://go-review.googlesource.com/13364
Reviewed-by: Austin Clements <[email protected]>

LLVM

LLVM 開發者並不強制要求 commit message 的格式,但有提供一些指引。顯然他們沒有遵循 Git commit message 的風格,因為 LLVM 的主要版本庫還在用 Subversion。不過你還是可以在那裡找到很多寫得很好的 commit message。

x86: Emit LAHF/SAHF instead of PUSHF/POPF
NaCl's sandbox doesn't allow PUSHF/POPF out of security concerns (priviledged emulators have forgotten to mask system bits in the past, and EFLAGS's DF bit is a constant source of hilarity). Commit r220529 fixed PR20376 by saving cmpxchg's flags result using EFLAGS, this commit now generated LAHF/SAHF instead, for all of x86 (not just NaCl) because it leads to an overall performance gain over PUSHF/POPF.

As with the previous patch this code generation is pretty bad because it occurs very later, after register allocation, and in many cases it rematerializes flags which were already available (e.g. already in a register through SETE). Fortunately it's somewhat rare that this code needs to fire.

I did [[ https://github.com/jfbastien/benchmark-x86-flags | a bit of benchmarking ]], the results on an Intel Haswell E5-2690 CPU at 2.9GHz are:

| Time per call (ms)  | Runtime (ms) | Benchmark                      |
| 0.000012514         |      6257    | sete.i386                      |
| 0.000012810         |      6405    | sete.i386-fast                 |
| 0.000010456         |      5228    | sete.x86-64                    |
| 0.000010496         |      5248    | sete.x86-64-fast               |
| 0.000012906         |      6453    | lahf-sahf.i386                 |
| 0.000013236         |      6618    | lahf-sahf.i386-fast            |
| 0.000010580         |      5290    | lahf-sahf.x86-64               |
| 0.000010304         |      5152    | lahf-sahf.x86-64-fast          |
| 0.000028056         |     14028    | pushf-popf.i386                |
| 0.000027160         |     13580    | pushf-popf.i386-fast           |
| 0.000023810         |     11905    | pushf-popf.x86-64              |
| 0.000026468         |     13234    | pushf-popf.x86-64-fast         |

Clearly `PUSHF`/`POPF` are suboptimal. It doesn't really seems to be worth teaching LLVM about individual flags, at least not for this purpose.

Reviewers: rnk, jvoung, t.p.northover

Subscribers: llvm-commits

Differential revision: http://reviews.llvm.org/D6629

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@244503 91177308-0d34-0410-b5e6-96231b3b80d8

更多參考資料

以下是一些有提供 commit message 指引的專案列表: