Conventional Commitsでコミットメッセージを分かりやすくする

コミットの目的と真実はいつもひとつ

Soudai Sasada

Soudai Sasada

ここ最近オープンソースにコントリビュートしようとしていると、特にモノレポ構成を取っているリポジトリにおいてコミットメッセージが一定の法則を持っている場合が多いことに気が付きました。例えば docs: modify dead link のような形式です。コントリビューションガイドにはその辺りへの言及がたまたまなかったか(または筆者自身が見落としていたか)その法則に関する言及には気が付けなかったのですが、調べてみるとどうやら Conventional Commits という仕様があったみたいでした。当時のコミットは一応フォーマットを合わせていたので無事取り込まれていますが今思うとこの辺りを知らずにコミットしていたのはあまり良くなかったなと反省しています。

今回は自分自身が Conventional Commits についてなにができてどういった便益があるのか理解したくせっかくなので記事にしてまとめてみようと思います。

Conventional Commitsとは

さて、ではまず Conventional Commits とは一体なんでしょう?みんな大好き(?)DeepL翻訳では「慣例的なコミットメント」となりました。慣例的、分かった気にさせるような表現が出てきました。このまま「完全に理解した」としても良いのですが、それだといつまでもチョット分かるとは言えないので公式ドキュメント(v1.0.0版)を見てみます。するとそこには

人間と機械が読みやすく、意味のあるコミットメッセージにするための仕様

とあります。続けて以下、概要の引用となります。

Conventional Commits の仕様はコミットメッセージのための軽量の規約です。 明示的なコミット履歴を作成するための簡単なルールを提供します。この規則に従うことで自動化ツールの導入を簡単にします。 コミットメッセージで機能追加・修正・破壊的変更などを説明することで、この規約は SemVer と協調動作します。

とても分かりやすい説明ですね。翻訳ツールの出番はなさそうです。

それにしても公式で日本語翻訳があるサイトは珍しい。実は最初それに気が付かず、この記事を書く過程でパスに言語の slug が含まれていることに気が付き公式サイトのプルダウンメニューに辿り着きました。
さすが公式が用意した翻訳だけあってとても分かりやすいです。翻訳をされた方には本当に感謝です。

このまま仕様についてこの記事で紹介しても公式ドキュメント以上のものにはなりません。なのでここからは、 Conventional Commits の仕様に沿っているかをチェックできるツールであるcommitlintや入力補助ツールであるgit-czを紹介します。

commitlint の導入

commitlint はコミットメッセージ用のリンタです。これを使うことでコミットメッセージが Conventional Commits の仕様に則っているかをチェックすることができます。便利なツールですが自動化しないと忘れてしまうので husky を使いGitのフックに登録しておくと良さそうです。ということでこの記事では husky を使い自動化するところまで紹介していきます。

では早速セットアップしていきましょう。なお事前に husky のセットアップが終わっている前提になりますのでもしまだセットアップをしていない場合はぜひこちらの記事をご参照いただきセットアップしてみてください。

以下、パッケージマネージャに yarn を使う場合です。

yarn
$ yarn add -D @commitlint/{config-conventional,cli}
$ echo "{\n  \"extends\": [\"@commitlint/config-conventional\"]\n}" > .commitlintrc.json
$ yarn husky add .husky/commit-msg 'yarn commitlint --edit $1'
$ git add package.json yarn.lock .commitlintrc.json
$ git commit -m "add commitlint"
# npmの場合。
# npm install --save-dev @commitlint/{config-conventional,cli}
# echo "{\n  \"extends\": [\"@commitlint/config-conventional\"]\n}" > .commitlintrc.json
# npx husky add .husky/commit-msg 'yarn commitlint --edit $1'
# git add package.json yarn.lock .commitlintrc.json
# git commit -m "add commitlint"

ではこれらの処理内容を順に見ていきます。
以下、 yarn によるセットアップ方法の抜粋となります。

$ yarn add -D @commitlint/{config-conventional,cli}
$ echo -e "{\n  \"extends\": [\"@commitlint/config-conventional\"]\n}" > .commitlintrc.json
$ yarn husky add .husky/commit-msg 'yarn commitlint --edit $1'

一行目では依存の追加をしています。 commitlint を使うためにはこの二つが必要となります。二行目では設定ファイルを生成しています。中身は次のとおりです。

.commitlintrc.json
{
  "extends": ["@commitlint/config-conventional"]
}

シンプルですね。設定ファイルで検査内容を変更することが可能です。なお設定ファイルの形式やルールは公式ドキュメントから辿れますので詳しく知りたい場合はそちらをご参照ください。

三行目は husky 上で commit-msg フックを作成しており、これによってコミットメッセージの入力完了後に commitlint が呼び出されるようになります。中身は次のとおりでいつもの husky のシェルスクリプトですね。

.husky/commit-msg
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

yarn commitlint --edit $1

では残りの二行を見ていきます。

$ git add package.json yarn.lock .commitlintrc.json
$ git commit -m "add commitlint"

一行目で作成したファイルをステージングに上げ二行目でコミットしているだけですね。また二行目ではコミットメッセージをあえて Conventional Commits の規約に違反した文言としています。実行結果は以下のようになります。

$ git commit -m "add commitlint"
yarn run v1.22.10
$ /Users/sou/products/mono/node_modules/.bin/lint-staged
→ No staged files match any configured task.
✨  Done in 0.28s.
yarn run v1.22.10
$ /Users/sou/products/mono/node_modules/.bin/commitlint --edit .git/COMMIT_EDITMSG
⧗   input: add commitlint
✖   subject may not be empty [subject-empty]
type may not be empty [type-empty]

✖   found 2 problems, 0 warnings
ⓘ   Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint

このように規約に違反したことでコミットは失敗し違反内容が出力されます。次に Conventional Commits に則ったコミットメッセージでコミットしてみます。

git commit -m "feat: add commitlint"
arn run v1.22.10
$ /Users/sou/products/mono/node_modules/.bin/lint-staged
→ No staged files match any configured task.
✨  Done in 0.33s.
yarn run v1.22.10
$ /Users/sou/products/mono/node_modules/.bin/commitlint --edit .git/COMMIT_EDITMSG
✨  Done in 0.27s.
[main 5cfc7db] feat: add commitlint
 4 files changed, 1098 insertions(+), 5 deletions(-)
 create mode 100755 .husky/commit-msg
 create mode 100644 commitlint.config.js

無事検査にとおりコミットすることができました :)

コミットメッセージの型について

Conventional Commits では型、スコープ、主題、内容、フッターといった要素がありますが、その中でも型と主題は必須となっており、型についてはさらに fixfeatdocs など様々な型がありますがcommitlintのドキュメントを見ると予め規定されているものとしてこれらの型が許容されるそうです。

[
  'build',
  'chore',
  'ci',
  'docs',
  'feat',
  'fix',
  'perf',
  'refactor',
  'revert',
  'style',
  'test'
];

Convventional CommitsのドキュメントによるとこれらはAngularの規約が基になっているようです。また、この他に独自の型を定義することもでき、それらは設定ファイルに定義することで変更することができます。

型に限らず他にも様々な仕様がありますのでまずは予めドキュメントを確認するのが良さそうです。

コミットメッセージの入力補助ツールを使う

さて、これでCLIでコミットする際にメッセージを検査することが可能となりました。が、いざ運用となるとこれらの規約を覚えておかなければならず慣れるまでは特に個々人の負荷が高いのではないでしょうか。
実はこのような問題を解決することを目的としたツールに git-cz というものがあります。これはCLIツールとなっておりCLI上で呼び出すことで対話が始まり順々に表示される質問に答えるだけで仕様に準拠したコミットメッセージが生成できるようになっています。
せっかくなのでこの記事でこちらも一緒に紹介しようと思います。といってもただ入れるだけですぐに使えるので本当に大した作業は必要ありません。今回はdevDependenciesに追加するのでpackage.jsonにスクリプトとして登録し呼び出せる形にします。

$ yarn add -D git-cz
$ npm set-script commit "git cz"

これで依存が追加され yarn commit で呼び出せるようになりました。

これですぐに動きますが、必要に応じて設定ファイルを通じて実行する内容を変更することが可能です。今回はjsonで設定したいので changelog.config.json という名前にしていますが有効な設定ファイル名はソースコード上から確認することができます。今回は次のとおりに設定してみます。

changelog.config.json
{
  "disableEmoji": false,
  "format": "{type}{scope}: {emoji}{subject}",
  "list": ["test", "feat", "fix", "chore", "docs", "refactor", "style", "ci", "perf"],
  "maxMessageLength": 64,
  "minMessageLength": 3,
  "questions": ["type", "scope", "subject", "body", "breaking", "issues", "lerna"],
  "scopes": [],
  "types": {
    "chore": {
      "description": "ビルド関連や補助ツールの変更",
      "emoji": "🤖",
      "value": "chore"
    },
    "ci": {
      "description": "CI関連の変更",
      "emoji": "🎡",
      "value": "ci"
    },
    "docs": {
      "description": "ドキュメントの更新",
      "emoji": "✏️",
      "value": "docs"
    },
    "feat": {
      "description": "機能の追加、変更、削除(セマンティックバージョニングのマイナー以上)",
      "emoji": "🎸",
      "value": "feat"
    },
    "fix": {
      "description": "バグ修正(セマンティックバージョニングのパッチ相当)",
      "emoji": "🐛",
      "value": "fix"
    },
    "perf": {
      "description": "パフォーマンス改善",
      "emoji": "⚡️",
      "value": "perf"
    },
    "refactor": {
      "description": "リファクタリング",
      "emoji": "💡",
      "value": "refactor"
    },
    "release": {
      "description": "リリースコミット",
      "emoji": "🏹",
      "value": "release"
    },
    "style": {
      "description": "フォーマッティングなどのコードのスタイル調整",
      "emoji": "💄",
      "value": "style"
    },
    "test": {
      "description": "テストコードの変更",
      "emoji": "💍",
      "value": "test"
    }
  }
}

では実際にこれらをコミットしどのような動きになるか見てみましょう。呼び出しはステージングにコミット対象ファイルがある状態でCLIで git commit の代わりに git cz を呼び出します。

$ git add package.json yarn.lock changelog.config.json
$ yarn commit

実際の動きはこのようになります。

run-git-cz

絵文字も付いていてとても良いですね ✨

なお、上述の設定ですと questions ブロックに scope とあるにも関わらず scopes ブロックが空のためにスコープ名を問われることがなく、よってスコープを設定することができないようになっています。このあたりはチームで合意した内容に準じて設定ファイルを編集していくのが良さそうです。

まとめ

Conventional Commits を使ってみて、型とスコープによって統一されたコミットメッセージを少ない文字数で表現できるため本質的なコミットメッセージを表現できるのではないかと感じました。
また、今回は紹介しませんでしたが semantic-release や commitlint と同じ conventional-changelog で展開されている standard-versionconventional-changelog-cli を用いることでパッケージのバージョンアップやGitのタグ付けを行いつつコミットメッセージの内容から新機能やバグフィックス、破壊的変更が記載されたCHANGELOGファイルの自動生成までできるため、バージョニングとCHANGELOGのどちらも必須となるようなプロジェクトではこれらをコミットメッセージから自動化できるメリットも享受できそうです。
一方でコミットが統一されていないと自動化のメリットが薄れたりコミットログを読む際にフォーマットに沿っていないものが混ざることによる認知的な負荷が増えてしまう恐れもあるかもしれません。これに限った話ではないですがチームで導入したいのであればまずはメンバー間でしっかりと話し合った方が良いでしょう。

もしCHANGELOGやGitのタグによるバージョニングの管理が煩雑に感じたりコミットメッセージのフォーマットをOSSでも通用するような規約で統一し読み書きの負担を減らしたいのであればぜひ検討してみてはいかがでしょうか。

筆者はこの記事を書きそのために各種ツールを試してみてからというもの、それまでいかに冗長で可読性に欠け情報量の偏ったコミットメッセージを書いていたかに気付きました。新たにコミットメッセージを書く際にもConventional Commitsの簡潔さと表現力には及びません。独自のルールと違い仕様として定義されることで知識の転用が効くため覚えて無駄になることもなさそうです。なによりコミットメッセージを選択肢の中にある分類からいずれか一つだけで表現しなければならなくなるためコミット自体が綺麗になる効果も魅力的です(これについてはこれまでも個人では意識し実践していたのですがいざチームとなると意識のバラつきが出やすく、かといって都度レビューで指摘するのも遠慮していた部分があったのでツールとして使えるのはありがたい限りです)。

次どこかで新規プロジェクトを始める機会があればぜひ導入を検討してみたいと思います。

この記事がお役に立てたら一杯のコーヒーを恵んで頂けると励みになります 😊

Buy Me A Coffee
© 2020, Soudai Sasada