本文へ移動
aapp-hacks
← 記事一覧へ
著者
app-hacks
公開日

Git / Conventional Commits / Workflow

個人開発を加速させるConventional Commitsの導入とコミット粒度の設計

Conventional Commitsのprefixと破綻しにくいブランチ運用を、レビュー可能なコミット粒度とともに整理する。

個人開発でも、数日後の自分は変更理由を知らない別のレビュアーになる。コミットメッセージが「update」「fix」の繰り返しでは、履歴から目的を復元できない。Conventional Commitsは、メッセージの先頭へ変更の種類を付け、履歴を人とツールの双方が解釈できる形にする規約である。

基本形式を小さく始める

基本形はtype: descriptionである。対象領域を示したい場合だけ括弧でscopeを足す。

feat(blog): add article tag list
fix(seo): generate canonical URL from site origin
refactor(content): extract post sorting helper
docs: document required Node.js version
chore: update development dependencies

最初から多数のtypeを作らず、次の役割を共通語彙にすれば十分である。

  • feat: 利用者から見える機能や振る舞いの追加
  • fix: 意図した振る舞いとの差を修正
  • refactor: 外部動作を変えずに内部構造を改善
  • docs: ドキュメントだけの変更
  • test: テストの追加や修正
  • chore: 開発補助、設定、依存関係など

CSSの調整がfeatfixかはファイル拡張子では決まらない。新しい記事カードを追加したならfeat、狭い画面のはみ出しを直したならfixである。typeは変更手段ではなく、プロジェクトへ与える意味を表す。

descriptionは命令形の短い英語へ統一しても、日本語へ統一してもよい。重要なのは同じリポジトリ内で規則を揃えることだ。「何を変更したか」が差分から明らかな場合は、「なぜ必要か」を本文へ書く。

fix(content): reject empty article tags

Empty tags render blank pills in the article list, so the schema now
requires every tag to contain at least one character.

本文はすべてのコミットに必須ではない。変更理由、採用しなかった方法、移行上の注意など、差分だけでは復元できない情報がある場合に使う。

一つのコミットへ一つの判断を入れる

良い粒度はファイル数ではなく、独立して説明し、検証し、取り消せる変更単位で決まる。Content Collectionsのスキーマ追加と、そのスキーマを使う一覧ページは一つの機能として同じコミットでも理解できる。一方、無関係な依存関係更新や文言修正まで混ぜると、レビューとrevertが難しくなる。

作業中は次の順序で差分を整理する。

git status --short
git diff
git add src/content.config.ts src/pages/blog/index.astro
git diff --staged
git commit -m "feat(content): add typed blog collection"

git add .ですべてを入れる前に、対象ファイルを指定し、staged diffを読む。デバッグ用ログ、生成物、別課題の変更が混ざるのを防げる。一つのファイルに複数の目的が混在した場合はgit add -pでhunk単位に分けられるが、分割後の各コミットがビルド可能かも確認する。

コミットを細かくしすぎ、単独では構文エラーになる状態を残すのも避ける。たとえばコンポーネントのimportだけを先にコミットし、実体を次のコミットへ置くと、その途中の履歴をcheckoutした際にビルドできない。履歴上の各地点を検証可能に保つことを粒度の下限とする。

ブランチを短命に保つ

個人開発でもmainを常に公開可能な状態にし、作業は目的ごとの短いブランチへ分けるとデプロイ事故を減らせる。

git switch main
git pull --ff-only
git switch -c feat/article-layout

# 実装と検証
npm run build

git push -u origin feat/article-layout

ブランチ名にもfeat/fix/などの分類を使えるが、コミットtypeと完全一致させる必要はない。一つの機能ブランチ内に、先行するrefactorやドキュメント更新が含まれることはある。ブランチは最終目的、コミットは個々の変更単位を表す。

長期間のブランチへ複数機能を積み上げると、mainとの差が大きくなり、競合解消が本来の変更と混ざる。小さな完成単位でmainへ統合し、次の作業は新しいブランチから始める。個人リポジトリでもPull Requestを作れば、CI結果、説明、最終差分を一画面で確認できる。

履歴を整えるためのforce pushは、自分だけが使う作業ブランチに限定する。共有ブランチやmainの履歴を書き換えると、他の作業コピーとの整合が壊れる。修正コミットをまとめたい場合も、共有状況を確認してからinteractive rebaseを使う。

CHANGELOG自動生成へつなげる

Conventional Commitsのtypeが揃うと、将来のリリースツールはfeatを機能追加、fixを修正として分類できる。破壊的変更はtypeの後へ!を付けるか、footerへBREAKING CHANGE:を書く。

feat(content)!: require publication date on every article

BREAKING CHANGE: Existing Markdown files must define pubDate.

ただし、規約を導入しただけでCHANGELOGが自動生成されるわけではない。リリースツール、タグ付け、バージョン方針は別途必要になる。まず人が読める一貫した履歴を作り、その後にツールを接続する。自動化の都合で実際の変更意味と異なるtypeを選ぶと、生成されたCHANGELOGも不正確になる。

メッセージ検証をCIへ追加する場合、既存履歴や外部コントリビューターへの影響を考える。最初はPull Requestの説明に規則を記載し、新しいコミットから適用する方が移行しやすい。厳格なlintを先に置くより、typeの意味とコミット粒度の例を共有することが先である。

Conventional Commitsの価値はprefixの見た目ではなく、変更を意味のある単位に分ける習慣にある。目的ごとの短命ブランチ、検証可能なコミット、差分から分からない理由の記録を組み合わせれば、個人開発でも修正、revert、リリースノート作成の負担を抑えられる。