4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

コミットメッセージがチームで決めたものと違うモノだった場合CIで自動通知する(GithubActions + Commitlint)

Last updated at Posted at 2025-08-18

みなさんコミットメッセージをちゃんと書いていますか?意外と綺麗に書けていない人が多いと思います(偏見)。
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 を追加する。

.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 は使えません。このリポジトリではワークフロー側で比較範囲からマージコミットを除外しています。

このように設定することで、コミットメッセージの粒度・表現をチームで統一することができます!

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?