目次
OSSのリポジトリを覗いていて、コミットログがずらっと feat: fix: docs: で始まっているのを見て「これ何かのルールなんだろうな」と思った人は多いと思います。自分も最初は意味が分からず、update とか fix bug で雑にコミットしていました。
この feat: や fix: で始まる書き方は Conventional Commits という仕様です。コミットメッセージの頭に決まった種別を付けるだけの軽いルールなのですが、地味に効いてくるメリットがいくつかあります。何ができて、わざわざ従う価値があるのかを、初心者向けに整理します。
Conventional Commitsとは
Conventional Commits は、コミットメッセージに人間にも機械にも読める意味を持たせるための書式の仕様です。基本の形はこうなっています。
<type>[任意のスコープ]: <説明>
[任意の本文]
[任意のフッター]
公式仕様(v1.0.0)はこう定めています[1]。
Commits MUST be prefixed with a type, which consists of a noun,
feat,fix, etc.
訳すと「コミットは feat fix などの名詞からなる type を接頭辞として付けなければならない」ということです。一番上の1行だけ書式に従えば成立する仕様で、本文やフッターは付けなくても構いません。実際こんな見た目になります。
feat: ユーザー一覧にページネーションを追加
feat が type、コロンとスペースの後ろが説明です。仕様では A description MUST immediately follow the colon and space after the type/scope prefix.(説明はコロンとスペースの直後に続けなければならない)とされているので、feat:ログイン追加 のようにスペースを詰めるのは外れます。
スコープを付けると、どこを変えたかを括弧で補足できます。
fix(api): トークン期限切れ時に500が返る不具合を修正
type には何があるか
仕様が必ず定義しているのは feat と fix の2つだけです。
feat: 新機能を追加したときfix: バグを修正したとき
これ以外の type は仕様本体では強制されていません。ただ慣習として、多くのプロジェクトが Angular の規約由来の type 群を使っています。@commitlint/config-conventional などで広く採用されているものを挙げておきます。
| type | 使う場面 |
|---|---|
feat |
新機能の追加 |
fix |
バグ修正 |
docs |
ドキュメントのみの変更 |
style |
動作に影響しない整形(空白・セミコロンなど) |
refactor |
バグ修正でも機能追加でもないコード変更 |
perf |
パフォーマンス改善 |
test |
テストの追加・修正 |
build |
ビルドシステムや依存関係の変更 |
ci |
CI 設定・スクリプトの変更 |
chore |
その他の雑多な変更 |
この一覧は仕様の必須要件ではなく慣習なので、プロジェクトによって増減します。チームで使うときは、その場で何を許可するかを commitlint の設定などで決めておくのが普通です。
破壊的変更の示し方
後方互換を壊す変更(APIの引数を変えた、設定キーをリネームしたなど)は、特別に目立たせる決まりがあります。仕様にはこうあります。
Breaking changes MUST be indicated in the type/scope prefix of a commit, or as an entry in the footer.
示し方は2通りです。type の後ろに ! を付ける方法と、フッターに BREAKING CHANGE: を書く方法。
feat!: 設定ファイルのフォーマットをYAMLに変更
feat: config の extends キーを廃止
BREAKING CHANGE: `extends` キーは使えなくなりました。代わりに `presets` を使ってください。
両方を併用しても構いません。! だけでも破壊的変更とみなされますが、何がどう壊れるかは BREAKING CHANGE: フッターに書いておくと、利用者が移行の手がかりを得られます。
どんなメリットがあるか
ここが一番気になるところだと思います。手で type を付ける手間を払ってまで従う価値があるのか。主な利点はこの3つです。
- コミットログがそのまま一覧として読める:
fix:だけ拾えば「このリリースで直ったバグ」が分かる。git log --onelineでも種別が頭に揃うので眺めやすい。 - CHANGELOG とバージョンを自動生成できる: これが最大の実利。後述します。
- チームでメッセージの粒度が揃う: 「何を書けばいいか」の枠ができるので、
updateのような中身ゼロのメッセージが減る。
2つ目の自動化が、わざわざ規約に従う一番の見返りです。Conventional Commits は セマンティックバージョニング(SemVer) と対応するよう設計されていて、仕様はこう述べています。
fix type commits should be translated to PATCH releases. feat type commits should be translated to MINOR releases. Commits with BREAKING CHANGE in the commits, regardless of type, should be translated to MAJOR releases.
つまり type を見るだけで、次のバージョンを上げ幅まで機械的に判定できます。
| コミット | バージョンの上がり方 |
|---|---|
fix: |
PATCH(1.2.3 → 1.2.4) |
feat: |
MINOR(1.2.3 → 1.3.0) |
BREAKING CHANGE / ! |
MAJOR(1.2.3 → 2.0.0) |
semantic-release や Changesets といったツールは、この対応を使ってコミットログから次のバージョン番号を決め、CHANGELOG を生成し、タグ打ちとリリースまで自動でやってくれます。「リリースのたびに手でバージョンを上げて変更点をまとめる」作業がまるごと消えるので、リリース頻度の高いプロジェクトほど効いてきます。
逆に言うと、一人で書き捨てる趣味リポジトリのように自動リリースを回さない場合、得られるのは「ログが読みやすい」くらいなので、無理に導入しなくてもいい仕様でもあります。
自分はこう使い分けている
個人開発では、リリース自動化を入れているリポジトリだけ Conventional Commits を守って、それ以外は普通に書いています。全部に規約を敷くと、type を考えるのが面倒で手が止まるからです。
導入するリポジトリには commitlint + husky を入れて、規約から外れたメッセージはコミット時点で弾くようにしています。人間の善意に任せると feat と fix の判断はわりとブレるので、refactor と fix で迷ったら「外から見て挙動が変わったか」で切る、という基準を1つ決めておくと楽になりました。挙動が変わったなら fix、内部だけなら refactor、という具合です。
チームに持ち込むなら、最初から10種類のtypeを覚えさせるより feat fix docs chore くらいで始めて、足りなくなったら足す方が定着しやすいと感じています。仕様自体が必須は feat と fix だけなので、薄く始められるのがこの規約のいいところです。
Conventional Commits v1.0.0 仕様。本文中の引用はいずれもこの版より。https://www.conventionalcommits.org/ja/v1.0.0/ ↩︎
