アトミックコミット
アトミックコミットとは、正確に1つの論理的な変更を含むコミットです。その利点は、整理整頓そのものではなく、コミットが混在するとチームが失う3つの具体的な能力にあります。
- レビュー: レビュアーは1つのアイデアを頭の中で保持し、それを判断できます。
- リバート: 悪い変更を、良い変更を巻き添えにせずに取り消せます。
- バイセクト:
git bisectでバグを導入したコミットを特定できます。これは、各コミットが1つの変更であり、かつ各コミットがビルドされる場合にのみ機能します。
最後の条項が最も見落とされがちです。アトミックとは「可能な限り小さく」という意味ではなく、「1つの完全な変更」を意味します。関数を、それが必要とする呼び出し元から分離するコミットは、より小さいですがアトミックではありません。ツリーが壊れたままになるからです。
ワークフロー
1. ステージング前に調査する
ツリー全体を理解してから、どのように分割するかを決定します。
git status --porcelain # 追跡対象外を含むすべてのパス
git diff # ステージングされていない変更
git diff --cached # すでにステージングされているもの
git log --oneline -15 # HEADの位置とローカルスタイル
最初の一手として git add -A や git commit -a は避けてください。これらは混在したツリーを混在したコミットにする方法であり、一度コミットしてしまうと、事前に分割するよりも厳密に多くの作業が必要になります。また、コミットすべきでない変更(ビルド出力、.env ファイル、デバッグ出力、無関係なエディタ設定など)がないか確認してください。
2. 変更を論理単位にグループ化する
最も信頼できるテストは言語的なものです。コミットの件名を草案し、それを正確にするために「and」が必要なら、それは複数のコミットがある証拠です。 「Fix login redirect and bump lodash」は2つのコミットです。レビュアーの質問が2つ目のテストです。変更Xを理解しなければ変更Yを判断できないでしょうか?そうでなければ、それらの変更は独立しており、別々に属するべきです。
分割する:
| 分けて保持 | 理由 |
|---|---|
| フォーマット/空白 と ロジック | リフォーマットにより、3行の本当の変更が400行のノイズに埋もれます。これは最も価値の高い分割です。 |
| リネーム と 動作変更 | 同じ理由です。リネームはすべてに影響し、重要な1行を隠してしまいます。 |
| 依存関係のバンプ と コード | それぞれに独自のリバートストーリーが必要です。 |
| 無関係なバグ修正 | 独立したリバート、独立したバイセクト。 |
| リファクタリング と それを使用する機能 | レビュアーがリファクタリングが動作を保持していることを確認できるようにします。 |
一緒に保持する:
| 1つのコミットに維持 | 理由 |
|---|---|
| 変更とそのテスト | それらは1つの論理単位です。出荷前のコードに対するテスト、またはテストのないコードは、それぞれ半分の変更です。 |
| リネームとそのすべての呼び出し元 | 分割するとツリーが壊れたままになります。アトミックの逆です。 |
| シグネチャ変更とその呼び出し元 | 同上。 |
| 変更とそれを説明するドキュメント/チェンジログ | 同じ決定を説明しており、一緒にリバートされるべきです。 |
3. すべてのコミットがビルドされるように順序付ける
各コミットがツリーを機能させたままにする必要があるため、順序が重要です。それを使用するものの前に基礎を置きます。ヘルパーを、それを呼び出すコードの前に追加します。リファクタリングを、それに基づいて構築される機能の前に行います。スキーマを、クエリの前に配置します。2つのグループが相互に依存している場合、それは実際には1つのコミットであるというシグナルです。
4. 各グループをステージングする
機能する最も侵襲性の低いテクニックを使用します。より単純な方法で分割を表現できない場合にのみ、より高度なテクニックにエスカレーションします。
レベル1 - ファイル全体をステージングする。 これは現実の分割の大半をカバーします。なぜなら、論理的な変更は通常、異なるファイルに存在するからです。
git add src/auth.py tests/test_auth.py
git commit -m "..."
レベル2 - ファイル全体を退避させる。 ツリー内の何かが誤ってコミットされやすい場合に使用します。
git stash push -- requirements.txt # 退避
# ... 残りをコミット ...
git stash pop # 戻す
レベル3 - 1つのファイル内の変更を分割する。 1つのファイルに本当に2つの論理的な変更が含まれている場合にのみ必要です。手順は煩雑で微妙に間違えやすいため、試みる前に references/splitting-one-file.md を読んでください。
git add -pは非対話環境では動作せず、 静かに失敗します。最初のハンクを表示し、標準入力でEOFに達し、何もステージングせず、終了コード0で終了します。ここでの成功終了コードは、何も起こっていないことを意味します。同じことがgit add -i、git commit -p、git checkout -pにも当てはまります。失敗が成功のように見えるため、被害は通常、空のコミットや意図した変更が欠落したコミットです。そのため、代わりにリファレンスファイルのテクニックを使用し、コミット前にgit diff --cachedでインデックスを確認してください。
5. リポジトリ自身の言葉でメッセージを書く
慣習をインポートするのではなく、周囲の履歴に合わせてください。プレーンな散文の件名が並ぶログに feat: プレフィックスを入れるのはノイズであり、履歴を2つのプロジェクトのように見せます。まずローカルスタイルを読んでください。
git log --no-merges --format='%s' -n 30 # 件名スタイル
git log --no-merges -n 10 --format='%n---%n%s%n%b' # 本文が使われているか
以下の点を確認してください:type: または type(scope): プレフィックス(Conventional Commits)、[PROJ-123] のようなチケットプレフィックス、大文字小文字、命令形 vs 過去形(「Add」 vs 「Added」)、典型的な件名の長さ、本文が通常使われるか件名だけで完結するか。見つけたものを採用してください。Conventional Commits は、ログですでに使用されている場合、またはリポジトリにまだ履歴がなくユーザーに好みがない場合にのみ、適切なデフォルトです。
ローカルスタイルが何であれ、件名は何が変わったかを示し、本文(履歴で本文が使われる場合)はなぜ変わったかを示すべきです。なぜなら、diff はすでに何を示しているからです。リポジトリに CONTRIBUTING.md や commit-msg フックがある場合、それらがログからの推測よりも優先されます。
慣習に合わせることは、それが運ぶデータを発明することを決して意味しません。 ローカルスタイルがチケットや Issue の参照([PROJ-123]、Fixes #456)を埋め込んでいて、実際の識別子が与えられていない場合、推測しないでください。また、ログで見られる番号を続けて付けないでください。もっともらしい [PROJ-124] は、プレフィックスがないより悪いです。それは永続的であり、実際のトレーサビリティとして読まれ、おそらく他の誰かの無関係なチケットを指しています。参照を省略し、スタイルの残りの部分に合わせ、返信でIDがないためユーザーが提供できるよう伝えてください。コミットはまだプッシュされておらず、修正が簡単です。ユーザーがそこに質問できる場合は、番号を尋ねてください。
6. 返す前に確認する
git log --oneline -n <count> # 構築したものの形状
git status --porcelain # 残っているものが何か
各コミットが自身の変更のみを含んでいること(git show --stat <sha>)、および何も取りこぼしていないことを確認します。変更が設計上コミットされずに残っている場合は、明示的にそう伝えてください。ユーザーがクリーンだと思っていたツリーが実際はクリーンでないことは、ここでの最悪の結果です。なぜなら、ユーザーは後でそれを発見し、それが意図的だったかどうかわからないからです。
ガードレール
- コミットの仕方を形作り、コミットするかどうかは形作りません。 ユーザーがコミットを求めていない場合、このスキルはコミットを作成することを適切にせず、プッシュを決して暗示しません。
- 分割を容易にするために作業を決して破棄しないでください。
git checkout -- <file>、git reset --hard、git stash dropは、まだどのコミットも保持していない未コミットの作業を破壊します。それは回復不可能です。テクニックがこれらのいずれかを必要とする場合、まずコンテンツをバックアップし、バックアップが存在することを確認してから実行してください。 - 公開済みの履歴を整理するために書き換えないでください。 すでにプッシュされたコミットを分割することは、他の人がプルした可能性のある履歴を書き換えるフォースプッシュを意味します。最初に確認してください。
- 徹底的に見せるために過剰に分割しないでください。 それぞれがビルドを壊す3つのコミットは、1つで機能するコミットより悪いです。迷った場合は、各コミットを機能させ続けるグループ化を優先してください。
- ツリーが1つの論理的な変更のみを含んでいることが判明した場合は、1つとしてコミットしてください。 すべてのツリーが分割を必要とするわけではなく、そう伝えることは立派な結果です。