Gitのコミットメッセージの書き方

(訳注:2015/10/31、いただいた翻訳フィードバックを元に記事を修正いたしました。)
(訳注:2015/11/1、いただいた翻訳フィードバックを元に記事を再修正いたしました。)


訳: プロジェクトが長引くほど、私のGitのコミットメッセージは情報が薄くなっていく。

イントロダクション | 7つのルール | ヒント

イントロダクション:なぜ良いコミットメッセージを書くことが重要か

Gitのリボジトリのログをランダムに閲覧すると、ひどいコミットメッセージを目にすることがあります。例として、私が昔書いたSpringにコミットしたこれらのgemを見てみましょう。

$ git log --oneline -5 --author cbeams --before "Fri Mar 26 2009"

e5f4b49 Re-adding ConfigurationPostProcessorTests after its brief removal in r814. @Ignore-ing the testCglibClassesAreLoadedJustInTimeForEnhancement() method as it turns out this was one of the culprits in the recent build breakage. The classloader hacking causes subtle downstream effects, breaking unrelated tests. The test method is still useful, but should only be run on a manual basis to ensure CGLIB is not prematurely classloaded, and should not be run as part of the automated build.
2db0f12 fixed two build-breaking issues: + reverted ClassMetadataReadingVisitor to revision 794 + eliminated ConfigurationPostProcessorTests until further investigation determines why it causes downstream tests to fail (such as the seemingly unrelated ClassPathXmlApplicationContextTests)
147709f Tweaks to package-info.java files
22b25e0 Consolidated Util and MutableAnnotationUtils classes into existing AsmUtils
7f96f57 polishing

訳:
e5f4b49 r814内で一旦削除したConfigurationPostProcessorTestsを再度追加。最近のビルドの失敗の原因だと分かったため、testCglibClassesAreLoadedJustInTimeForEnhancement()メソッドを@Ignore。ClassLoaderのハッキングがわずかなダウンストリームエフェクトの原因となり、関係のないテストを破壊。テストメソッドはそれでも使えるが、CGLIB が早まってclassloadをしないように手動で実行するべきで、自動ビルドの一部とし実行すべきではない。
2db0f12 ビルド失敗原因の2つのissueを修正+ClassMetadataReadingVisitorをrevision 794に変更+ダウンストリームテストが失敗する理由を突き止めるまでConfigurationPostProcessorTestsを消去(関係なさそうなClassPathXmlApplicationContextTestsなど)
147709f package-info.javaファイルを変更
22b25e0  UtilとutableAnnotationUtilsクラスを存在するAsmUtilsと統合
7f96f57 修正中

おえっ。これを、同じリボジトリから書いたより最近ののコミットメッセージと比べてみましょう。

$ git log --oneline -5 --author pwebb --before "Sat Aug 30 2014"

5ba3db6 Fix failing CompositePropertySourceTests
84564a0 Rework @PropertySource early parsing logic
e142fd1 Add tests for ImportSelector meta-data
887815f Update docbook dependency and generate epub
ac8326d Polish mockito usage

注釈:
$ git log –oneline -5 –author pwebb –before “Sat Aug 30 2014″
5ba3db6  CompositePropertySourceTestsの不具合を修正
84564a0 先に解析したロジックで@PropertySourceを再作業
e142fd1  ImportSelectorのメタデータのテストを追加
887815f  docbookの付属をアップデートしてepubを生成
ac8326d  mockitoの用法を修正

あなただったらどっちを読みたいですか?

前者は文章も長いし見た目も幅広くなっています。後者は簡潔で一貫性があります。前者はデフォルトで書かれたものです。後者は偶発的に書かれたものではありません。

前者のようなリポジトリのログを多く見かけますが、例外もあります。Linux kernelGitそれ自体がそのいい例です。Spring BootなどTim Popeによって管理されているリポジトリを見てみてください。

これらのリポジトリの作成者は、上手に記述されたGitのコミットメッセージが変更内容を仲間の開発者に伝える一番良い方法だと知っています(もちろん将来、自分で見た時のためにも)。diffで何が変更されたかが分かりますが、なぜ変更されたのか正しく理由を教えてくれるのはコミットメッセージです。Peter Huttere氏はこれをうまく説明しています

 コードの一部の内容のコンテクストを再度構築するのは時間の無駄です。しかし、それは完全には避けられないので、可能な限り減らす努力をすべきです。コミットメッセージはまさにその仕事をしてくれるし、その結果、コミットメッセージを見れば、開発者が素晴らしい共同制作者であるかどうかが分かります。

優れたGitのコミットメッセージを書くににはどうしたら良いか、まだ理解してないならば、今までにgit logや関連ツールをあまり使ってこなかったということかもしれません。それは悪循環を引き起こします。コミット履歴は統制が取れておらず矛盾が生じているので、使ったり手入れをしたりする時間を費やさなくなります。そして、コミット履歴は使われなかったり手入れをされない状態だと、統制が取れず矛盾が生じたままです。

しかし、ログをしっかり手入れすれば、美しくて使い勝手の良いものとなります。そして、git blamerevertrebaselogshortlogや他のサブコマンドが活かせます。他の人が書いたコミットメッセージを見れば、プルリクエストが役に立つはずです。すると、突然自分でも書けるようになるはずです。数カ月、数年前になぜそのことが発生したのかを理解することが可能になるだけでなく、効果的にできるようになります。

プロジェクトの長期的な成功の鍵は(とりわけ)メンテナンス性にあります。そして、メンテナンス担当者にとってプロジェクトのログ以上に強力なツールはありません。時間をかけても使い方を学ぶ価値はあります。最初は大変かもしれませんが、すぐに慣れます。そしてそれが自信となり、プロジェクトにかかわる全ての生産性を高めるでしょう。

この投稿では、健全なコミット履歴を維持する最もベーシックなやり方をご紹介します。つまり、どうやってコミットメッセージを書くのか、ということです。コミットに関してはもう1つ、commit squashing という大事なものがありますが、今回は省略します。もしかしたら次回の投稿でご説明することになるかもしれませんが。

プログラミング言語の多くは、どうすれば自然なスタイルになるかということに関して、うまく確立されたルールがあります。例えば、ネーミングやフォーマットなどです。これらのルールにバリエーションがあるのは当然ですが、1つのルールのみを使って、そのやり方に従うほうが、それぞれの開発者が別々の方法を使ってカオスになるよりマシであると多くの開発者は賛同してくれるでしょう。

チームでコミットログに取り組む時も違いはありません。使えるリビジョン履歴をつくるには、チームは初めにコミットメッセージのルールを決めておくべきです。少なくも下記の3点に関しては合意を取っておきましょう。

スタイル:マークアップ構文、空白、文法、大文字、句読点。これらのことに関して議論し、なんとなくで作業するのをやめ、可能な限り単純なスタイルにしましょう。これを実践すれば、一貫性のあるログになり、ログを読むことが習慣になってしまうくらい楽になるでしょう。

内容:コミットメッセージの本文(もしあれば)が伝えたい情報はどんな内容ですか? また、含まれていない情報は何ですか?

メタデータ:Issue Tracking IDやプルリクエストの番号などはどうやって参照しますか?

幸運にも、自然なGitのコミットメッセージを書くにはどうしたらいいのかという、確立されたルールが存在します。実際に、いくつかのルールはGitコマンドの機能に則って定められています。あなたが自分で再発明する必要はないのです。以下の7つのルールにただ従えばいいのです。そうすればプロと同じようにコミットできるようになります。

素晴らしいコミットメッセージを書くための7つのルール

訳注 : このルールは英語での書き方に準拠するものであり、日本語でコミットメッセージを書く場合は異なる場合があります。

心に留めておいてください:これは 今までも ずっと 言われて きた ことです

  1. タイトルの後は1行空けて本文を書く
  2. タイトルを50字以内におさめる
  3. タイトルの文頭を大文字にする
  4. タイトルの文末にピリオドを付けない
  5. タイトルは命令形で記述する
  6. 本文は1行あたり72字以内におさめる
  7. 本文ではどのようにではなく何をなぜを説明する

例を挙げてみます。

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

(訳:
だいたい50字以内で変更を要約

必要であればより詳細な説明を書く。約72字以内で改行する。コンテキストによっては、初めの1文をコミットのタイトル、それ以下を本文とすることも。タイトルと本文の間は必ず1行空ける(本文を全く書かない場合を除く)。タイトルと本文を繋げて書くと、 logshortlogrebase などのさまざまなツールが混乱するため。

このコミットが解決している問題を説明する。どのように、ではなく、なぜこの変更をしたのかに重きを置く(どのように変更したかはコードを見れば分かる)。この変更で生じる副作用や直感的には分からない他の結果は生じないか?何かあればここで説明する。

さらに文を追加する場合は、1行空ける。

  • 箇条書きでもいい

  • 箇条書きの頭にはハイフンやアスタリスクを使うのが一般的。記号の後に1文字分の空白を入れ、項目ごとには1行空ける。ただし、このあたりのルールは厳密ではない

Issue Tackerを使用する場合は、以下のようにリファレンスをつけておく。

Resolves: #123
See also: #456, #789

1. タイトルの後は1行空けて本文を書く

以下はgit commitmanpageからの引用です。

必須ではないですが、コミットメッセージ書くときはまず、変更を要約した短い1文(50字以下)を作って、1行空けて、次により詳細な記述をするのが良いでしょう。コミットメッセージの冒頭の1文は、コミットタイトルとして扱います。そして、そのタイトルはGitで使われます。例えば、git-format-patch(1)でコミットをEメールに出力とすると、タイトルはメールの題名になり、残りの記述はメールの本文になります。

しかし、コミットは必ずしもタイトルと本文が必要ではありません。特に変更がとてもシンプルで、それ以上の説明が必要ない場合などは、1文のみで問題ない場合もしばしばあります。例えば以下のような形です。

Fix typo in introduction to user guide

(訳:ユーザガイドの序文で誤字を修正)

これ以上のことは言う必要はありません。もし、これを読んだ人が、誤字とは何だったのか知りたければ、git showgit diffgit log -pを使うなどして変更そのものを確認すればいいだけのことです。

コマンドラインでこのように何かをコミットするなら、-mスイッチからgit commitを使ったほうが簡単です。

$ git commit -m"Fix typo in introduction to user guide"

注釈: $ git commit -m “ユーザガイドの序文で誤字を修正”

しかし、コミットが説明や内容を必要としていれば、本文を書いたほうがいいでしょう。以下が例です。

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.

注釈:
マスタコントロールプログラムを消去

MCPは悪役で、世界征服を企んでいることが分かった。
このコミットは、トロンのディスクをMCPに投げて(これで、姿を消せます)、ディスクをチェスゲームに戻す。

この場合は、-mスイッチでコミットするのは難しいです。適切なエディタが必要です。コマンドラインでGitを使うのにエディタをまだ設定してなければ、Pro Gitの項目を読んでください。

どんな場合もタイトルと本文を分けることは、ログを閲覧する際に効果的です。ここにログエントリの全文があります。

$ git log
commit 42e769bdf4894310333942ffc5a15151222a87be
Author: Kevin Flynn <kevin@flynnsarcade.com>
Date:   Fri Jan 01 00:00:00 1982 -0200

 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.

注釈:
マスタコントロールプログラムを消去

MCPは悪で、世界征服を企んでいることが分かった。
このコミットは、トロンのディスクをMCPに投げて(これで、姿を消せます)、ディスクをチェスゲームに戻す。

ここで、タイトルだけを表示するgit log –onelineを使用すると、

$ git log --oneline
42e769 Derezz the master control program

注釈:
42e769 マスタコントロールプログラムを消去

となります。もしくは、ユーザごとにコミットをグループ分けするgit shortlogを使うと、上記と同様にタイトルだけが簡潔に表示されます。

$ git shortlog
Kevin Flynn (1):
      Derezz the master control program

Alan Bradley (1):
      Introduce security program "Tron"

Ed Dillinger (3):
      Rename chess program to "MCP"
      Modify chess program
      Upgrade chess program

Walter Gibbs (1):
      Introduce protoype chess program

注釈:
Kevin Flynn (1):
マスタコントロールプログラムを消去

Alan Bradley (1):
セキュリティプログラム “Tron”の導入

Ed Dillinger (3):
チェスプログラムを”MCP”にリネーム
チェスプログラムの変更
チェスプログラムのアップグレード

Walter Gibbs (1):
チェスプログラムのプロトタイプの導入

Gitでは、他にもタイトルと本文を区別する箇所が多くありますが、タイトルと本文の間に空行が無ければ機能しません。

2. タイトルを50字以内におさめる

50字というのは絶対的な制限ではなく、要約する際の単なるルールです。タイトルをこの長さに維持することで読みやすさが保証される上に、何が起きているかを最も簡潔に説明するにはどうしたらいいか、記述者が考えるように促すことになります。

ヒント:もし要約が難しいと感じているとしたら、一度にコミットする変更が多すぎるのかもしれません。アトミックコミット(別の記事のトピック)を目標に努力してみてください。

GitHubのユーザインターフェースはこうしたルールを完全に認識しています。50字の制限を超えてしまった場合、次のような警告が表示されます。

gh1
注釈:
プロのヒント:良いコミットの要約は、50字かそれ以内です。追加情報は説明欄に記述してください。

そしてタイトルが69字を超えると、省略記号を付けて切り捨てられます。

gh2

というわけですので、50字を目標としますが、上限値は69字です。

3. タイトルの文頭を大文字にする

これは言葉通りの単純なことです。タイトルは全て大文字で書き始めます。

例えば、

  • accelerate to 88 miles per hour

ではなく、

  • Accelerate to 88 miles per hour

と記述します。

4. タイトルの文末にピリオドを付けない

タイトルにピリオドを付ける必要はありません。また、タイトルを50字かそれ以下にしようと思ったら、スペースは貴重です。

例えば、

  • Open the pod bay doors.

ではなく、

  • Open the pod bay doors

と記述します。

5. タイトルは命令法で記述する

命令法とは要するに”命令や指示をするような話し言葉または書き言葉”を意味します。いくつか例を挙げてみましょう。

  • Clean your room(部屋を掃除せよ)
  • Close the door(ドアを閉めよ)
  • Take out the trash(ゴミを片付けよ)

今皆さんが読んでいる7つのルールはそれぞれ、命令法で書かれています(”Wrap the body at 72 characters(本文を72字以内におさめよ)”など)。

命令法は少し乱暴な言い方に聞こえるかもしれません。ですから私たちは普段あまり使いませんね。しかし、Gitコミットのタイトルにはまさにこれがうってつけなのです。あなたがコミットを作成する際に、Gitそれ自体が命令法で記述されているというのが理由の1つです。

例えば、git merge読み込みの際に作成される初期メッセージは以下のようになります。

Merge branch 'myfeature'

注釈:ブランチ”myfeature”をマージせよ

そして、git revertを使う際には以下のようになります。

Revert "Add the thing with the stuff"

This reverts commit cc87791524aedd593cff5a74532befe7ab69ce9d.

注釈:
” Add the thing with the stuff “をrevertせよ

このrevertのコミットはcc87791524aedd593cff5a74532befe7ab69ce9dである。

または、GitHubのプルリクエストの”Merge”ボタンをクリックする時は以下のようになります。

Merge pull request #123 from someuser/somebranch

注釈:
ユーザ/ブランチからの#123プルリクエストをマージせよ

ですので、命令法でコミットメッセージを記述する時には、Gitそれ自体がビルトインしているルールに従っていることになるのです。例えば、

  • Refactor subsystem X for readability(サブシステムXを読みやすくするためにリファクタせよ)
  • Update getting started documentation(スタートドキュメンテーションをアップデートせよ)
  • Remove deprecated methods(非推奨のメソッドを削除せよ)
  • Release version 1.0.0(バージョン1.0.0をリリースせよ)

このような書き方は、最初は少しぎこちなく感じるかもしれません。私たちは、事実を伝える際に使う直接法で話すことに慣れています。そのため、以下のような文章のコミットメッセージをよく見かけるということになってしまうのです。

  • Fixed bug with Y(Yでバグを修正した)
  • Changing behavior of X(Xの動きを変更している)

さらにコミットメッセージは、内容の説明として以下のように記述されることもあります。

  • More fixes for broken stuff(壊れている箇所に対する追加の修正)
  • Sweet new API methods(簡単な新しいAPIメソッド)

混乱を避けるために、いつでも正しく記述できるようなシンプルなルールを紹介します。

適切に記述されたGitコミットのタイトルは常に、以下の文章を完成させるようなものになるべきです。

  • If applied, this commit will ここにタイトルを記述

例を挙げます。

  • If applied, this commit will refactor subsystem X for readability(適用することにより、このコミットは、読みやすくなるようにサブシステムXをリファクタするだろう)
  • If applied, this commit will update getting started documentation(適用することにより、このコミットは、スタートドキュメンテーションをアップデートするだろう)
  • If applied, this commit will remove deprecated methods(適用することにより、このコミットは、非推奨のメソッドを削除するだろう)
  • If applied, this commit will release version 1.0.0(適用することにより、このコミットは、バージョン1.0.0をリリースするだろう)
  • If applied, this commit will merge pull request #123 from user/branch(適用することにより、このコミットは、ユーザ/ブランチからの#123プルリクエストをマージするだろう)

命令法以外の記述方法で書いた場合は、文章が正しく機能しないことに注意してください。

  • If applied, this commit will fixed bug with Y(適用することにより、このコミットは、Yでバグを修正しただろう)
  • If applied, this commit will changing behavior of X(適用することにより、このコミットは、Xの動きを変更しているだろう)
  • If applied, this commit will more fixes for broken stuff(適用することにより、このコミットは、壊れている箇所に対する追加の修正するだろう)
  • If applied, this commit will sweet new API methods(適用することにより、このコミットは、簡単な新しいAPIメソッドだろう)

覚えておいていただきたいのですが、命令法を使うのは、タイトルに対してだけです。本文を記述する際にはこの制限に縛られることはありません。

6. 本文を72字以内におさめる

Gitでは文字は自動的に切り詰められません。コミットメッセージの本文を記述する際には、右の余白と文字数制限に気を付けて手作業で行う必要があります。

推奨する文字数は72字です。Gitにはテキストをインデントするために余白がたくさんありますが、全て含めて80字以内におさめます。

優れたテキストエディタはこの作業を補助してくれます。例えば、Gitコミットを記述する際に、テキストを72字で制限するようにVimをコンフィギュレーションするのは簡単です。しかしこれまで、IDEでコミットメッセージのテキストを制限字数内におさめる際にスマートなサーポートを期待しても、提供されている方法はお粗末なものでした(この点に関しては、最近のバージョンであるintelliJ IDEAが、ついに改善しましたが)。

7. 本文では「どのように」ではなく「何を」と「なぜ」を説明する

このBitcoin Coreのコミットは、「何」と「なぜ」を説明している大変良い例です。

commit eb0b56b19017ab5c16c745e6da39c53126924ed6
Author: Pieter Wuille <pieter.wuille@gmail.com>
Date:   Fri Aug 1 22:57:55 2014 +0200

Simplify serialize.h's exception handling

   Remove the 'state' and 'exceptmask' from serialize.h's stream
   implementations, as well as related methods.

   As exceptmask always included 'failbit', and setstate was always
   called with bits = failbit, all it did was immediately raise an
   exception. Get rid of those variables, and replace the setstate
   with direct exception throwing (which also removes some dead
   code).

   As a result, good() is never reached after a failure (there are
   only 2 calls, one of which is in tests), and can just be replaced
   by !eof().

   fail(), clear(n) and exceptions() are just never called. Delete
   them.

注釈:
serialize.hの例外処理を簡素化する

関連メソッドのように、”state”と” exceptmask “をserialize.hの streamインプリメンテーションから削除する。

exceptmaskは常に”failbit “を含み、setstateは常にbits = failbitで呼び出されるので、実行するとただちに例外処理となる。これらの変数を取り除き、setstateをダイレクトな例外スローに置き換える(これによりデッドコードも取り除かれる)。

結果として、エラー後にgood()は実行されず(callは2つだけで、1つはテスト内にある)、単にby !eof()によって置き換えることができる。

fail()、 clear(n)、exceptions()は呼び出されない。それらを削除する。

全てのdiffを見てみましょう。そして、コメントの記述者が変更時に時間をかけて内容を提供してくれたことで、同僚や将来このコードを手掛ける人が、どんなに時間を節約できるか考えてみてください。もし彼が手間をかけてくれなければ、変更内容は永遠に分からなくなっていたことでしょう。

大抵の場合、どのように変更が行われたかという詳細が書き残されます。この点についてですが、コードとは通常それ自体がコードの説明となっています(もしコードが非常に複雑で説明文が必要な場合には、ソースコメントという手があります)。まずは、変更した理由を明らかにすることに焦点を絞りましょう。変更前にはどのように機能していたのか(そして何が問題だったのか)、今はどのように機能しているのか、そしてなぜその方法で解決することに決めたのか、ということです。

あなたに感謝する将来のメンテナンス担当者は、あなた自身かもしれませんよ。

ヒント

コマンドラインを愛用することを覚えて、IDEを手放そう

Gitのサブコマンドがあるのと同じくらい多くの理由から、コマンドラインを採用することは賢明なことです。Gitは非常に強力です。IDEも強力ですが、どのように強力であるかは異なります。私は毎日IDEを使用しており(IntelliJ IDEA)、他のものも幅広く使ってきました(Eclipse)。しかし(一度知ってしまうと)、コマンドラインの簡便さと強力さに匹敵するようなGit用のIDEインテグレーションは無いことが分かります。

Git関連のIDEの関数の中には大変役に立つものもあります。例えばファイルを削除する際のgit rmの呼び出しなどです。また、リネームする際にgitで正しく処理できます。ただ、コミット、マージ、リベース、精密な履歴の分析などをしようと思った時に、IDEでは全てがバラバラになってしまいます。

Gitのパワーをフル活用するならば、最適といえるのがコマンドラインなのです。

使っているのがBashであれZ shellであれ、サブコマンドやスイッチを覚えておく労力を軽減してくれるタブ補完スクリプトがあることを覚えておいてください。

Pro Gitを読みましょう

Pro Gitの本はオンライン上で無料で入手できる素晴らしい本です。ぜひ利用してください。