🗂

Conventional Commits に沿ったコミットメッセージを簡単に書く方法を模索する

2025/02/13に公開

ソフトウェア開発において、Conventional Commits に従ったコミットメッセージの記述は、履歴の可読性や自動化ツールとの連携を向上させるために非常に重要です。私は普段、手打ちでコミットメッセージを書くため、つい絵文字などの付加情報を忘れてしまうことがあります。そこで、今回はコミットメッセージを簡単に、そして漏れなく書くためのツール選定とその設定方法について検証した内容をまとめます。

背景

  • 手打ちの限界:
    普段は慣れた手打ちでコミットメッセージを書いているものの、絵文字などの関連情報を書き忘れてしまうことが度々ある。

  • 自動化ツールへの期待:
    チーム全体で統一したフォーマットを採用するためにも、手間を減らしつつ正しいフォーマットでコミットメッセージを生成できるツールがあったらいいな。

目標

最終的には以下のフォーマットに沿ったコミットメッセージを楽に作成できる環境を目指しました。

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

<description> の冒頭には、 <type> に応じた絵文字を付与する

検討したオプション

いくつかの選択肢を検討しました。

npm を利用する方法

  1. commitizen/cz-clileoforfree/cz-customizable の組み合わせ
  2. commitizen/cz-cliwithblocks/cz-customizable の組み合わせ
  3. commitizen/cz-clistreamich/git-cz の組み合わせ
  4. commitizen/cz-clingryman/cz-emoji の組み合わせ
  5. commitizen/cz-cliZhengqbbb/cz-git の組み合わせ

fish シェルの場合

Rust 製のツール

推奨する組み合わせ

検討の結果、以下の組み合わせが最も良さそうでした。

これらを使うことで、 git-cz で足りないと感じた「scope の選択式+任意記載」「typeに応じた絵文字の入力」を実現することができました。

cz-git の導入と設定

以下は、cz-git を利用した環境構築手順の一例です。

インストール

まずは npm 経由で必要なパッケージをグローバルにインストールします。

npm install -g cz-git commitizen

次に、プロジェクトルートまたはユーザーのホームディレクトリに .czrc を作成し、以下の内容を記述します。

{
  "path": "cz-git",
  "$schema": "https://cdn.jsdelivr.net/gh/Zhengqbbb/cz-git@1.11.0/docs/public/schema/cz-git.json"
}

設定ファイル例

コミットメッセージの検証ツールとして commitlint を利用している場合、~/.commitlintrc.json などで以下のように設定します。
基本的には Configure Template | cz-git で記載されている設定のママですが、 emoji の有効化や、コミットメッセージに入力される絵文字の部分をカスタムしています。

{
  "rules": {
    // 必要に応じてルールを追加
  },
  "prompt": {
    "alias": { "fd": "docs: fix typos" },
    "messages": {
      "type": "Select the type of change that you're committing:",
      "scope": "Denote the SCOPE of this change (optional):",
      "customScope": "Denote the SCOPE of this change:",
      "subject": "Write a SHORT, IMPERATIVE tense description of the change:\n",
      "body": "Provide a LONGER description of the change (optional). Use \"|\" to break new line:\n",
      "breaking": "List any BREAKING CHANGES (optional). Use \"|\" to break new line:\n",
      "footerPrefixesSelect": "Select the ISSUES type of changeList by this change (optional):",
      "customFooterPrefix": "Input ISSUES prefix:",
      "footer": "List any ISSUES by this change. E.g.: #31, #34:\n",
      "generatingByAI": "Generating your AI commit subject...",
      "generatedSelectByAI": "Select suitable subject by AI generated:",
      "confirmCommit": "Are you sure you want to proceed with the commit above?"
    },
    "types": [
      { "value": "feat", "name": "feat:     ✨ A new feature", "emoji": "✨" },
      { "value": "fix", "name": "fix:      🐛 A bug fix", "emoji": "🐛" },
      { "value": "docs", "name": "docs:     📝 Documentation only changes", "emoji": "📝 " },
      { "value": "style", "name": "style:    💄 Changes that do not affect the meaning of the code", "emoji": "💄" },
      { "value": "refactor", "name": "refactor: ☘️ A code change that neither fixes a bug nor adds a feature", "emoji": "☘️" },
      { "value": "perf", "name": "perf:     ⚡️ A code change that improves performance", "emoji": "⚡️" },
      { "value": "test", "name": "test:     ✅ Adding missing tests or correcting existing tests", "emoji": "✅" },
      { "value": "build", "name": "build:    📦️ Changes that affect the build system or external dependencies", "emoji": "📦️" },
      { "value": "ci", "name": "ci:       🎡 Changes to our CI configuration files and scripts", "emoji": "🎡" },
      { "value": "chore", "name": "chore:    🔨 Other changes that don't modify src or test files", "emoji": "🔨" },
      { "value": "revert", "name": "revert:   ⏪️ Reverts a previous commit", "emoji": "⏪️" }
    ],
    "useEmoji": true,
    "emojiAlign": "center",
    "useAI": false,
    "aiNumber": 1,
    "themeColorCode": "",
    "scopes": ["git","github","terraform","docker","script"],
    "allowCustomScopes": true,
    "allowEmptyScopes": true,
    "customScopesAlign": "bottom",
    "customScopesAlias": "custom",
    "emptyScopesAlias": "empty",
    "upperCaseSubject": false,
    "markBreakingChangeMode": false,
    "allowBreakingChanges": ["feat", "fix"],
    "breaklineNumber": 100,
    "breaklineChar": "|",
    "skipQuestions": [],
    "issuePrefixes": [{ "value": "closed", "name": "closed:   ISSUES has been processed" }],
    "customIssuePrefixAlign": "top",
    "emptyIssuePrefixAlias": "skip",
    "customIssuePrefixAlias": "custom",
    "allowCustomIssuePrefix": true,
    "allowEmptyIssuePrefix": true,
    "confirmColorize": true,
    "minSubjectLength": 0,
    "defaultBody": "",
    "defaultIssues": "",
    "defaultScope": "",
    "defaultSubject": ""
  }
}

この設定により、対話形式でコミットメッセージが生成され、各ステップで適切な選択肢(type、scopeなど)を選ぶことができるようになります。


git cz コマンドの実行結果

追記

作者の方からコメントいただいたので、 czg の方も試してみました。

https://x.com/zhengqbbb/status/1891840170955051074

次のように、インストールした cz-gitcommitizen を削除して czg をインストールします。

console
npm uninstall -g cz-git commitizen

npm install czg

# git repository 配下で実行
czg

# カスタム設定の適用
czg --config="./config/cz.json"

こちらのコマンドの方がサクサクと動作する気がします。

e.g. Config
.config/cz-git/cz.js
// .config/cz-git/cz.js
const { definePrompt } = require('czg')

module.exports = definePrompt({
  "rules": {
    // 必要に応じてルールを追加
  },
  "prompt": {
    "alias": { "fd": "docs: fix typos" },
    "messages": {
      "type": "Select the type of change that you're committing:",
      "scope": "Denote the SCOPE of this change (optional):",
      "customScope": "Denote the SCOPE of this change:",
      "subject": "Write a SHORT, IMPERATIVE tense description of the change:\n",
      "body": "Provide a LONGER description of the change (optional). Use \"|\" to break new line:\n",
      "breaking": "List any BREAKING CHANGES (optional). Use \"|\" to break new line:\n",
      "footerPrefixesSelect": "Select the ISSUES type of changeList by this change (optional):",
      "customFooterPrefix": "Input ISSUES prefix:",
      "footer": "List any ISSUES by this change. E.g.: #31, #34:\n",
      "generatingByAI": "Generating your AI commit subject...",
      "generatedSelectByAI": "Select suitable subject by AI generated:",
      "confirmCommit": "Are you sure you want to proceed with the commit above?"
    },
    "types": [
      { "value": "feat", "name": "feat:     ✨ A new feature", "emoji": "✨" },
      { "value": "fix", "name": "fix:      🐛 A bug fix", "emoji": "🐛" },
      { "value": "docs", "name": "docs:     📝 Documentation only changes", "emoji": "📝 " },
      { "value": "style", "name": "style:    💄 Changes that do not affect the meaning of the code", "emoji": "💄" },
      { "value": "refactor", "name": "refactor: ☘️ A code change that neither fixes a bug nor adds a feature", "emoji": "☘️" },
      { "value": "perf", "name": "perf:     ⚡️ A code change that improves performance", "emoji": "⚡️" },
      { "value": "test", "name": "test:     ✅ Adding missing tests or correcting existing tests", "emoji": "✅" },
      { "value": "build", "name": "build:    📦️ Changes that affect the build system or external dependencies", "emoji": "📦️" },
      { "value": "ci", "name": "ci:       🎡 Changes to our CI configuration files and scripts", "emoji": "🎡" },
      { "value": "chore", "name": "chore:    🔨 Other changes that don't modify src or test files", "emoji": "🔨" },
      { "value": "revert", "name": "revert:   ⏪️ Reverts a previous commit", "emoji": "⏪️" }
    ],
    "useEmoji": true,
    "emojiAlign": "center",
    "useAI": false,
    "aiNumber": 1,
    "themeColorCode": "",
    "scopes": ["git", "github", "terraform", "docker", "script"],
    "allowCustomScopes": true,
    "allowEmptyScopes": true,
    "customScopesAlign": "bottom",
    "customScopesAlias": "custom",
    "emptyScopesAlias": "empty",
    "upperCaseSubject": false,
    "markBreakingChangeMode": false,
    "allowBreakingChanges": ["feat", "fix"],
    "breaklineNumber": 100,
    "breaklineChar": "|",
    "skipQuestions": [],
    "issuePrefixes": [{ "value": "closed", "name": "closed:   ISSUES has been processed" }],
    "customIssuePrefixAlign": "top",
    "emptyIssuePrefixAlias": "skip",
    "customIssuePrefixAlias": "custom",
    "allowCustomIssuePrefix": true,
    "allowEmptyIssuePrefix": true,
    "confirmColorize": true,
    "minSubjectLength": 0,
    "defaultBody": "",
    "defaultIssues": "",
    "defaultScope": "",
    "defaultSubject": ""
  }
})

使用感と感想

検討を進める中で、以下の点が印象に残りました。

  • 多くの類似ツール:
    名前や機能が似たツールが多数存在し、どれを採用するか悩みました。

  • 選択式の scope 入力:
    特に git-cz などは scope の選択が固定されているため、自由入力できない点が改善の余地として感じられました。

  • AI 支援の可能性:
    設定項目に AI を利用してコミットメッセージの生成候補を提示する機能があるものもあり、今後の発展に期待できると感じました。

最終的には、commitizen/cz-cliZhengqbbb/cz-git の組み合わせが最も使い勝手が良いと判断し、実際のプロジェクトでも採用しています。

まとめ

今回の記事では、Conventional Commits に従ったコミットメッセージを楽に書くためのツール検討と、実際に cz-git を中心とした環境構築手順を紹介しました。多くのツールが存在する中で、自分やチームの開発フローに最適な組み合わせを見つけることが大切です。必要に応じて、こういったツールの活用も検討してみてください。

【参考リンク】

以上、皆さんの開発環境に少しでも役立つ情報となれば幸いです。

Discussion