みなさんコミットメッセージをちゃんと書いていますか?意外と綺麗に書けていない人が多いと思います(偏見)。
fix
のみだと何をしたかわからないですし、モノレポでマイクロサービスを管理しているプロダクトの場合どのマイクロサービスに対しての変更かわからないです...
また、チーム開発ではコミットメッセージの粒度・表現がバラつくと、履歴の検索性や自動リリースノート生成が難しくなります。
そこで、コミットメッセージを統一することで、履歴の検索性や自動リリースノート生成が容易になります。
Conventional Commits 準拠の形式(例: feat: xxx
/ fix: xxx
)でコミットメッセージを統一し、間違った形式をCIで検知・失敗させる仕組みを構築してみましょう!
参考資料
こんなCIを作りたい(要件)
- Conventional Commits 準拠の形式(例:
feat: xxx
/fix: xxx
)でのような形式でコミットされているかを自動検証し、逸脱があればCIを失敗させる。 - 逸脱したコミットメッセージをPRにGithubActions(bot)がコメントする。
これにより、コミットメッセージの粒度・表現をチームで統一することができるようにしましょう!
Conventional Commits のルールの例
コミットメッセージを以下の形式に統一します。
type(scope)!: subject
-
type: 変更の種類を記載
-
feat
: ユーザーに価値のある新機能 -
fix
: バグ修正 -
docs
: ドキュメントのみの変更 -
refactor
: 挙動は変えず内部構造を改善 -
perf
: パフォーマンス改善 -
test
: テスト関連の追加・修正 -
build
: ビルド/依存関係/ツールの変更 -
ci
: CI設定/スクリプトの変更 -
style
: フォーマット/セミコロン等(ロジック無変更) -
chore
: 杂務・他分類に当てはまらないメンテ作業 -
revert
: 取り消し
-
-
scope: 影響範囲(任意, 例:
feat(api): ...
) -
!: 破壊的変更のマーカー(任意)。footer に
BREAKING CHANGE: ...
を併記 - subject: 命令形・現在形で簡潔に。末尾にピリオドは付けない
例(OK)
feat(auth): add JWT refresh endpoint
fix(video): handle 404 when manifest is missing
docs: update README for local setup
refactor(stream): simplify muxer state machine
perf(player): reduce initial buffer size for faster start
ci: add commitlint to PR workflow
chore: bump golang to 1.22.5
revert: revert "feat(auth): add JWT refresh endpoint"
例(NG)
update code
Fix bug
feat: Add Feature.
feat: multiple spaces
feat(): empty scope parentheses
CI(GitHub Actions)での検証
PR のオープン時と、そのPRに対する push(更新)時に、履歴に含まれるコミットメッセージをまとめてチェックするように設定する。
設定ファイルを作成する
まず、リポジトリのルートに .commitlintrc.json
を追加する。
{
"rules": {
"type-enum": [
2,
"always",
[
"feat",
"fix",
"refactor",
"docs",
"style",
"perf",
"revert",
"ci",
"build",
"chore",
"test"
]
],
"type-case": [
2,
"always",
"lower-case"
],
"subject-empty": [
2,
"never"
],
"subject-full-stop": [
2,
"never",
"."
],
"header-max-length": [
2,
"always",
100
]
}
}
GithubActionsを使う(PRにコメントで指摘)
wagoid/commitlint-github-action
を利用して、CIを作成する。
このアクションはイベント種別に応じて自動で比較範囲を解決し、該当コミットを検証します。
.github/workflows/commitlint.yml
name: commitlint
on:
pull_request: # PR作成/更新時のみ実行(fork対策やpush全体は対象外)
types: [opened, synchronize]
paths:
- 'backend/**'
- '.github/workflows/commitlint.yml'
jobs:
commitlint:
runs-on: ubuntu-22.04
if: github.event.pull_request.user.login != 'dependabot[bot]' # bot由来のPRは除外
permissions:
contents: read
pull-requests: write # PRにコメント投稿するために必要
timeout-minutes: 5
steps:
- uses: actions/checkout@v4 # fetch-depth: 0で全コミット取得
with:
fetch-depth: 0
- uses: actions/setup-node@v4 # Node準備(npxでcommitlint実行)
with:
node-version: '18'
# lockfile が無くても動作するよう、npxで @commitlint/cli を都度実行する
- name: Validate commits and prepare PR comment # PR差分の各コミットをチェックし、違反一覧のコメント本文を組み立てる
id: commitlint_check
shell: bash
run: |
BASE_SHA=${{ github.event.pull_request.base.sha }}
HEAD_SHA=${{ github.event.pull_request.head.sha }}
REPO_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}"
has_violations=false
COMMENT=$'[commitlint CI]コミットメッセージが規則と違うコミットがあります。\n以下のようにコミットメッセージを修正してください。\n\n'
COMMENT+=$'**コミットメッセージの形式**\n'
COMMENT+=$' type(scope): subject\n'
COMMENT+=$' - **type**: feat | fix | refactor | docs | style | perf | revert | ci | build | chore | test\n'
COMMENT+=$' - **scope**: 任意(モジュール名など)、不要なら省略可\n'
COMMENT+=$' - **subject**: 簡潔に要点のみ(文末に句点を付けない)、最大100文字\n'
COMMENT+=$'\n(例)\n'
COMMENT+=$'feat: add user login with email link\n'
COMMENT+=$'fix(microservice): return 404 when resource is not found\n\n'
COMMENT+=$'以下のコミットを修正してください。\n\n'
for sha in $(git rev-list "${BASE_SHA}..${HEAD_SHA}" --no-merges --reverse); do
message=$(git log -1 --format=%B "$sha")
# 1コミットずつ lint。失敗しても続行し、一覧を作る
if ! echo "$message" | npx --yes @commitlint/cli@19 --config .commitlintrc.json --color > /dev/null 2>&1; then
has_violations=true
subject=$(printf "%s" "$message" | head -n1)
short=${sha:0:7}
COMMENT+="- [${short}](${REPO_URL}/commit/$sha): ${subject}"
COMMENT+=$'\n'
fi
done
echo "has_violations=${has_violations}" >> "$GITHUB_OUTPUT"
{
echo "comment_body<<EOF"
printf "%s\n" "$COMMENT"
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Comment violations on PR # 違反がある場合のみ、PRにコメントを投稿
if: steps.commitlint_check.outputs.has_violations == 'true'
uses: actions/github-script@v7
env:
COMMENT_BODY: ${{ steps.commitlint_check.outputs.comment_body }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const body = process.env.COMMENT_BODY;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
- name: Fail when violations exist # コメント投稿後に失敗させてPRのチェックを落とす
if: steps.commitlint_check.outputs.has_violations == 'true'
run: exit 1
よくあるカスタマイズ
-
type を限定:
type-enum
で許可する種類を制限
feat や fix など変更の種類を制御
{
"rules": {
"type-enum": [2, "always", ["feat", "fix", "docs", "refactor", "perf", "test", "build", "ci", "style", "chore", "revert"]]
}
}
-
スコープをプロジェクトの領域で固定:
scope-enum
にモノレポのパッケージやマイクロサービス名などを列挙
feat(microservice): xxxx のmicroservice
に当たる部分の名前の制御
{
"rules": {
"scope-enum": [2, "always", ["api", "db", "web", "player", "auth"]]
}
}
- ヘッダーの文字数制限: チーム方針で文字数を制御したい場合のみ設定(本記事のCIは未設定)
{
"rules": {
"header-max-length": [2, "always", 72]
}
}
- 件名の書式: 先頭小文字や末尾のピリオド禁止などを厳格化
{
"rules": {
"subject-case": [2, "always", ["lower-case"]],
"subject-full-stop": [2, "never", "."]
}
}
- BREAKING CHANGE の明示: 破壊的変更のヘッダや本文を強制
{
"rules": {
"body-leading-blank": [2, "always"],
"footer-leading-blank": [2, "always"]
}
}
-
マージコミットの扱い: 無視する/しないの選択
JSON ベースの設定では関数を記述できないためignores
は使えません。このリポジトリではワークフロー側で比較範囲からマージコミットを除外しています。
このように設定することで、コミットメッセージの粒度・表現をチームで統一することができます!