高度なGit履歴編集:コミット修正とインタラクティブリベース

git commit --amend とインタラクティブリベースを使用して、共有ブランチを乱さずにローカル履歴を整理する方法。

高度なGit履歴編集:コミット修正とインタラクティブリベース

高度なGit履歴編集は、コミットが共有履歴の一部になる前にクリーンアップするのに役立ちます。コミットの修正とインタラクティブリベースを理解すれば、小さなミスを修正したり、ノイズの多いコミットをまとめたり、コードレビューのために明確なストーリーを提示できます。

重要なルールはシンプルです:ローカル履歴の編集は普通ですが、共有履歴の書き換えには注意が必要です。これらのコマンドは、コミットメッセージだけでなくコミットIDも変更するため強力です。

Git履歴編集で実際に変わるもの

すべてのGitコミットには、その内容、メタデータ、親コミット、作成者、日付、メッセージに基づくIDがあります。コミットを修正したり、リベース中に書き換えたりすると、Gitは新しいIDを持つ新しいコミットを作成します。

これは、コミットが自分のマシンにのみ存在する場合は問題ありません。他の人がすでに古いコミットをプルしている場合は危険です。彼らのブランチは古い履歴を指し続けるかもしれませんが、あなたのブランチは書き換えられた履歴を指します。

履歴編集は以下の場合に使用します:

  • 最新のコミットメッセージのタイポ修正。
  • 最後のローカルコミットに忘れたファイルを追加。
  • プルリクエストを開く前に、作業中のコミットをいくつかまとめる。
  • 関連する変更をレビューしやすくするためにローカルコミットを並べ替える。
  • 1つの大きなローカルコミットを小さな論理コミットに分割する。

履歴編集は以下の場合に避けます:

  • メインブランチ。
  • リリースブランチ。
  • 複数人が使用する共有フィーチャーブランチ。
  • デプロイタグや監査に敏感なコミットがあるブランチ。

始める前に、現在のブランチと既にプッシュされたものを確認します:

git status
git branch --show-current
git log --oneline --decorate -5

書き換えが安全かどうかわからない場合は、バックアップブランチを作成します:

git branch backup-before-rebase

このブランチは、リベースが失敗した場合の既知の復旧ポイントを提供します。

最新のコミットを修正する

git commit --amend は、最新のコミットを新しいものに置き換えます。これは、他の誰かが依存する前に最後のコミットを修正する最速の方法です。

コミットメッセージのみを編集する場合:

git commit --amend

Gitがエディタを開き、現在のメッセージが表示されます。修正したメッセージを保存すると、Gitが置き換えコミットを作成します。

最後のコミットに忘れたファイルを追加する場合:

git add path/to/file
git commit --amend --no-edit

--no-edit フラグは既存のメッセージを保持します。これは、コミットメッセージがすでに正しく、ステージングし忘れた変更が1つだけの場合に便利です。

一般的なシナリオを紹介します。Dockerfileの更新をコミットした後、関連する .dockerignore の変更を忘れたことに気づいた場合:

git add Dockerfile
git commit -m "Dockerビルドキャッシュを改善"
git add .dockerignore
git commit --amend --no-edit

これで、ブランチには「ファイルを忘れた」という2番目のコミットではなく、1つのクリーンなコミットが含まれます。これによりレビューが容易になります。

元のコミットが既にプッシュされていた場合、リモートブランチには古いコミットIDが残っています。修正したコミットをプッシュするには、強制更新が必要です:

git push --force-with-lease

--force よりも --force-with-lease を推奨します。これは、最後のフェッチ以降に他の誰かが新しい作業をプッシュした場合、リモートブランチの上書きを拒否します。

インタラクティブリベースで複数のコミットをクリーンアップする

インタラクティブリベースを使用すると、一度に複数のコミットを編集できます。範囲を選択すると、GitがTODOリストを開き、コミットの選択、メッセージの編集、スカッシュ、フィックスアップ、並べ替え、停止ができます。

最後の5つのコミットを編集する場合:

git rebase -i HEAD~5

次のようなリストが表示されます:

pick a1b2c3d デプロイスクリプトを追加
pick b2c3d4e タイポを修正
pick c3d4e5f ヘルスチェックを追加
pick d4e5f6a ドキュメントを更新
pick e5f6a7b タイムアウトを調整

各行の先頭のコマンドを変更して、動作を制御します。一般的な選択肢は:

  • pick:コミットをそのまま保持。
  • reword:変更は保持するがメッセージを編集。
  • squash:このコミットを前のコミットに結合し、結合メッセージを編集。
  • fixup:このコミットを前のコミットに結合し、このコミットのメッセージを破棄。
  • edit:このコミットで停止し、内容を変更可能。
  • drop:コミットを削除。

例えば、「タイポを修正」が「デプロイスクリプトを追加」に属する場合、2行目を変更します:

pick a1b2c3d デプロイスクリプトを追加
fixup b2c3d4e タイポを修正
pick c3d4e5f ヘルスチェックを追加
pick d4e5f6a ドキュメントを更新
pick e5f6a7b タイムアウトを調整

エディタを保存して閉じます。Gitがコミットを再生し、タイポ修正を前のコミットに結合します。

インタラクティブリベースは、プルリクエスト前にコミットメッセージを改善するのにも役立ちます。3つのコミットがすべて「更新」と書かれている場合、reword を使用して実際の変更を説明するメッセージに変更します。

競合が発生した場合は、通常のマージ競合のように解決します:

git status
git add resolved-file
git rebase --continue

リベースを中止する場合:

git rebase --abort

これにより、ブランチはリベース開始前の状態に戻ります。

リベース中にコミットを分割する

1つのコミットに2つの無関係な変更が含まれることがあります。例えば、Kubernetesマニフェストを更新し、READMEセクションも修正するコミットがある場合です。これらの変更はおそらく別々にするべきです。

インタラクティブリベースを開始します:

git rebase -i HEAD~3

分割したいコミットの pickedit に変更します。Gitがそのコミットで停止したら、変更を作業ツリーに保持したままリセットします:

git reset HEAD^

次に、最初の論理的な部分をステージングしてコミットします:

git add k8s/deployment.yaml
git commit -m "デプロイメントのヘルスチェックを更新"

次に、2番目の部分をステージングしてコミットします:

git add README.md
git commit -m "ヘルスチェックの動作を文書化"

リベースを続行します:

git rebase --continue

これにより、1つの広範なコミットが2つの焦点を絞ったコミットになります。レビュアーは、無関係な作業を精神的に分離することなく、各変更を理解できます。

書き換え前に助けを求めるタイミング

他の人が使用するブランチを書き換える前に、助けを求めてください。チームリーダー、リリースエンジニア、またはリポジトリメンテナーが、ブランチの書き換えが安全かどうか、チームがフォースプッシュをどのように扱うかを教えてくれます。

リベースが既にデプロイ、タグ付け、またはインシデントレポートで参照されているコミットに影響する場合は、すぐに助けを求めてください。そのような場合、履歴をきれいに見せるよりも、履歴を保持することが重要です。

通常のフィーチャーブランチでは、履歴編集は一般的なクリーンアップ手順です。最新のコミットには git commit --amend、小さなローカルシリーズにはインタラクティブリベース、所有するリモートブランチを更新する必要がある場合は --force-with-lease を使用します。