Squash and Merge と Conventional Commits の両立を支える GitHub Actions
これはなに
Git のマージ戦略が "Squash and Merge" となっているリポジトリーで Conventional Commits を適切に運用するノウハウをまとめたものです。
Squash and Merge とは
作業ブランチ上のコミット履歴をひとかたまりに圧縮してからトランク(= メインブランチ)にマージする戦略です。圧縮したものは1つの新しいコミットとしてトランクにマージされます。これによりトランク上のコミット履歴は、各作業ブランチに相当する Pull request (PR) と 1:1 でマッピングされるようになります。

引用: GitHub上のマージ方法について - GitHub Docs
GitHub では "Create a merge commit" がデフォルトのマージ戦略となっており、これを切り替えることで "Squash and Merge" を選択できます。

PR をマージする際にマージ戦略を選択できる。
GitHub で Squash and Merge を行うと通常は Pull request タイトル (#PR番号) というコミットメッセージでマージされます(例外あり)[1]。 (#PR番号) の部分は該当 PR へのリンクとして表示されるため、作業がいつ完了したのかを正確に確認できます。

例: Squash and Merge を導入しているリポジトリのトランク履歴( main ブランチ)
Conventional Commits とは
Git などにおけるコミットメッセージを一定の書式に統一することを目的とした規約です。人間と機械のどちらも理解しやすいように設計されており、端的でありながら明瞭なコミットメッセージを記述できます。これにより後からコードベースの変遷を追跡しやすくなるだけでなく、リリースワークフローやコードレビューを自動化するツールとの連携も容易になります。詳細は以下の記事を参照ください。
参考: Conventional Commits とは - Conventional Commits の基礎知識とそれを支える技術(commitlint + husky)
課題
双方の両立は理論的には可能ですが、以下の課題が存在します。
1. Pull Request タイトルを Conventional Commits の書式で記述する必要がある
先述したように、Squash and Merge を導入しているリポジトリーのトランク履歴には PR のタイトルがそのままコミットメッセージとして列挙されます。つまり、そのようなリポジトリーで Conventional Commits を運用するためには、PR のタイトルを Conventional Commits の書式に準拠させねばなりません。日頃から Conventional Commits が習慣化されている人なら造作もないことでしょうが、そうでない人がこれを遵守するのは難しい話です。コードレビューで指摘するのも不毛ですし、見落としの頻発が懸念されます。
2. コミット数が1つのみの Pull Request はそのコミットメッセージがマージ時のコミットメッセージとなる
GitHub は、コミット数が1つのみの PR を Squash and Merge でマージする際に PR タイトルではなくその作業ブランチ上のコミットメッセージをマージ時のコミットメッセージとして提案します。また、コミット数が1つのみの場合は PR 作成時に PR タイトル欄にそのコミットメッセージが自動的に入力されます。

Squash and Merge でマージする挙動のデモ。PR タイトルではなくコミットメッセージが自動入力されている。
つまり、この場合は作業ブランチ上のコミットメッセージが Conventional Commits の書式に準拠していることが前提となるため、PR タイトルのみのチェックでは不足です。
解決策
これらの課題を人力による運用のみで解決するのは非現実的です。そこで、PR タイトルの書式を機械的にチェックする仕組みを構築し、Squash and Merge と Conventional Commits の両立を実現します。
GitHub Actions で Pull Request タイトルをチェックする
action-semantic-pull-request は、PR のタイトルが Conventional Commits の書式に準拠しているかをチェックする GitHub Action です。これをコードレビューのワークフローに組み込むことで、PR タイトルが Conventional Commits の書式に準拠していない場合はマージを阻止できます。
name: Check Pull Request Title
on:
pull_request_target:
types:
- opened
- edited
- reopened
- synchronize
permissions:
pull-requests: read
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
# Based on the Angular convention
# cf. https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines
types: |
feat
fix
docs
style
refactor
perf
test
build
ci
chore
validateSingleCommit: true
validateSingleCommitMatchesPrTitle: true
types フィールドはデフォルトで feat と fix しか有効になっていないため、必要に応じて他の type も列挙します。
コミット数が1つのみの PR には validateSingleCommit と validateSingleCommitMatchesPrTitle フィールドを有効化することで対応します。validateSingleCommit フィールドを有効化すると、PR が含むコミット数が1つのみの場合にそのコミットメッセージをチェックします。また、validateSingleCommitMatchesPrTitle フィールドを有効化することで、PR タイトルと単一のコミットメッセージが一致しているかをチェックします。これにより、Squash and Merge でマージする際のコミットメッセージを完璧に準拠させられます。

失敗例: PR タイトルが Conventional Commits の書式に準拠していないためマージが阻止される。

成功例: PR タイトルが Conventional Commits の書式に準拠しているためマージが許可される。
コミット時にコミットメッセージの書式をチェックしたい場合は、commitlint や husky などのツールを利用するのが適切です。以下の記事で解説していますので、詳細はそちらを参照ください。
参考: コミットメッセージをチェックするツール - Conventional Commits の基礎知識とそれを支える技術(commitlint + husky)
締め
Squash and Merge ならびに Conventional Commits はコードベースの変遷をきれいに保ち追跡しやすくするうえで有用な手法です。本稿で紹介したノウハウを活用することで、双方のメリットを享受しつつコードベースの品質向上が期待できます。
参考文献
- Conventional Commits
- GitHub上のマージ方法について - GitHub Docs
- amannn/action-semantic-pull-request: A GitHub Action that ensures that your PR title matches the Conventional Commits spec
-
手動でコミットメッセージを編集することも可能ですが、殆どの場合は何もせずそのままマージすることでしょう。 ↩︎
Discussion