Gitのコミットをサプラッシュする方法:例を交えたガイド

「早めにコミットし、頻繁にコミットする」は、Gitを使用したソフトウェア開発で一般的なマントラです。そうすることで、それぞれの変更がしっかりと記録され、協力が促進され、プロジェクトの進化を追いやすくなります。しかし、これが多すぎるコミットにつながることもあります。

ここで重要になるのがコミットを統合することです。コミットを統合するとは、複数のコミットエントリを一つの包括的なコミットに結合するプロセスです。

例えば、ログインフォームを実装するフィーチャーに取り組んでいるとします。そして、以下の4つのコミットを作成します:

機能が完了した後、全体のプロジェクトにとって、これらのコミットは слишком подрóбны. 将来、開発中に遭遇したバグが修正されたことを知る必要はありません。メインブランチの履歴をクリーンに保つために、これらのコミットを単一のコミットに squash します:

GitでコミットをSquashする方法: インタラクティブリベース

コミットをsquashする最も一般的な方法は、インタラクティブリベースを使用することです。次のコマンドで開始します:

git rebase -i HEAD~<number_of_commits>

<number_of_commits>を、squashしたいコミットの数に置き換えてください。

私たちの場合、4つのコミットがあるので、コマンドは次のようになります:

git rebase -i HEAD~4

このコマンドを実行すると、インタラクティブなコマンドラインエディタが開きます:

上部にはコミットが表示され、下部にはコミットをすりつぶす方法に関するコメントが含まれています。

4つのコミットが見えます。それぞれに対して、どのコマンドを実行するか決めなければなりません。私たちはpickp)とsquashs)のコマンドに注意します。これらの4つのコミットを1つのコミットにすりつぶすには、最初のものをにして、残りの3つをすりつぶします。

コマンドを適用するには、各コミットの前にあるテキストを修正し、特に2番目、3番目、4番目のコミットのpicksまたはsquashに変更します。これらの編集を行うには、コマンドラインテキストエディタの「INSERT」モードに切り替える必要があり、キーボードのiキーを押します。

iを押した後、下部に-- INSERT --というテキストが表示され、インサートモードに入ったことが示されます。今、カーソルを矢印キーで移動し、文字を削除し、標準的なテキストエディタと同様にタイプすることができます。

変更に満足したら、キーボードのEscキーを押してインサートモードから退出します。次のステップは変更を保存してエディタを終了することです。まず、:キーを押してエディタにコマンドを実行する意思を伝えます:

エディタの下部には、セミコロン:が表示され、コマンドの入力を促します。変更を保存するために、wコマンドを使用します。これは「write」の略です。エディタを閉じるにはqコマンドを使用します。これは「quit」の略です。これらのコマンドは組み合わせてwqと一緒に打てます:

コマンドを実行するために、Enterキーを押します。このアクションは現在のエディタを閉じて新しいエディタを開き、新たに潰したコミットのコミットメッセージを入力するのを許可します。エディタには、潰す4つのコミットのメッセージからなるデフォルトのメッセージが表示されます:

これらの統合コミットによる変更を正確に反映するメッセージを修正することをお勧めします—毕竟、squashingの目的はクリーンで読みやすい履歴を維持することだからです。

エディタと対話し、メッセージを編集するために、再びiキーを押して編集モードに入り、好みに応じてメッセージを編集します。

この場合、コミットメッセージを「Implement login form。」に置き換えます。編集モードを終了するには、Escキーを押します。その後、:キーを押し、wqコマンドを入力してEnterキーを押して変更を保存します。

コミット履歴の表示方法

一般的に、全文のコミット履歴を思い出すのは難しいです。コミット履歴を表示するためには、git logコマンドを使用できます。前述の例では、squashを実行する前にgit logコマンドを実行すると、以下が表示されます:

コミットの一覧を移動するには、上下矢印キーを使用します。終了するには、qを押します。

スクワッシュの成功を確認するには、git logを使用できます。スクワッシュ後に実行すると、新しいメッセージを持つ単一のコミットが表示されます:

スクワッシュされたコミットのプッシュ

上記のコマンドはローカルリポジトリに対して作用します。リモートリポジトリを更新するには、変更をプッシュする必要があります。しかし、コミット履歴を変更したため、--forceオプションを使用して強制プッシュする必要があります:

git push --force origin feature/login-form

強制プッシュはリモートブランチ上のコミット履歴を上書きし、そのブランチで作業している他の人に影響を与える可能性があります。これを行う前にチームとコミュニケーションを取るのが良い習慣です

コラボレーターの作業を中断するリスクを減らすための、強制プッシュのより安全な方法は、--force-with-leaseオプションを使用することです:

git push --force-with-lease origin feature/login-form

このオプションは、最後のフェッチまたはプル以来的リモートブランチが更新されていない場合にのみ強制プッシュを行うことを確保します。

特定のコミットをマージする

5つのコミットがあると仮定します:

コミット1、2、および5を保持し、コミット3と4をマージしたいとします。

インタラクティブリベースを使用する際、マージされるコミットは直接前のコミットと結合されます。この場合、Commit4Commit3にマージする必要があります。

これを行うためには、この二つのコミットを含むインタラクティブリベースを開始する必要があります。この場合、三つのコミットで十分なので、コマンドを使用します:

git rebase -i HEAD~3

次に、Commit4sに設定して、それをCommit3と統合します:

このコマンドを実行し、コミットを一覧表示した後、コミット3と4が統合されたことを観察しますが、他のものは変更されていません。

特定のコミットからの統合

コマンドgit rebase -i HEAD~3では、HEADの部分は最新のコミットの略です。~3の構文は、コミットの祖先を指定するために使用されます。たとえば、HEAD~1HEADコミットの親を指します。

インタラクティブリベースでは、指定されたコマンドのコミットに至る全ての祖先コミットが考慮されます。指定されたコミットは含まれない点に注意してください:

HEADを使用する代わりに、直接コミットハッシュを指定できます。例えば、Commit2のハッシュはdbf3cc118d6d7c08ef9c4a326b26dbb1e3fe9ddfであり、コマンド:

git rebase -i dbf3cc118d6d7c08ef9c4a326b26dbb1e3fe9ddf

Commit2以降に作成された全てのコミットを考慮してリベースを開始します。したがって、特定のコミットからリベースを開始し、そのコミットを含めたい場合、以下のコマンドを使用します:

git rebase -i <commit-hash>~1

コミットをスクエッシュする際の衝突解決

コミットをスクエッシュすると、複数のコミットの変更を单一のコミットにまとめることになりますが、変更が重複したり大幅に分かれた場合、衝突が発生する可能性があります。以下は、衝突が発生する可能性のある一般的なシナリオです:

  1. 重複する変更:2つ以上のコミットが同じファイルの同じ行や非常に近い行を修正した場合、Gitはこれらの変更を自動的に調和できない場合があります。
  2. 変更状態の違い:1つのコミットが特定のコードを追加し、別のコミットがその同じコードを修正または削除する場合、これらのコミットを統合すると解決する必要がある冲突が発生する可能性があります。
  3. 名前の変更と修正:コミットがファイルを名前変更し、その後のコミットが古い名前に対して変更を行う場合、これらのコミットを統合するとGitが混乱し、冲突が発生する可能性があります。
  4. バイナリファイルへの変更:バイナリファイルはテキストベースのdiffツールでうまくマージできません。複数のコミットが同じバイナリファイルを変更し、squashを試みると、Gitが自動的にこれらの変更を調整できないため、衝突が発生することがあります。
  5. 複雑な履歴:コミットが複数のマージ、ブランチ、リベースを含む複雑な履歴を持っている場合、squashを行うと、変更の非線形性により衝突が発生することがあります。

squashを行う際、Gitは各変更を一つずつ適用しようとします。このプロセス中に衝突に遭遇すると、一時停止し、私たちがそれを解決する機会を与えます。

コンフリクトはコンフリクトマーカー<<<<<<>>>>>>でマークされます。コンフリクトを処理するためには、ファイルを開いて、どの部分のコードを残すかを手動で選択して各コンフリクトを解決する必要があります。

コンフリクトを解決した後、git addコマンドを使用して解決したファイルをステージングします。その後、以下のコマンドを使用してリベースを続けます:

git rebase --continue

Gitのコンフリクトについて更多信息は、このチュートリアルをチェックしてください。Gitでのマージコンフリクトの解決方法

リベースでのスクエッシュの代替手段

git merge --squashコマンドは、git rebase -iの代替方法として、複数のコミットを1つのコミットにまとめる手法です。このコマンドは、ブランチからメインブランチへの変更をマージする際に、個々のコミットをすべて1つにまとめたいときに特に便利です。以下は、git mergeを使用してsquashを行う方法の概要です:

  1. 変更を取り込む対象のブランチに移動します。
  2. git merge --squash <branch-name>コマンドを実行し、<branch-name>をブランチの名前に置き換えます。
  3. 変更をgit commitでコミットして、フィーチャーブランチからのすべての変更を表す単一のコミットを作成します。

例えば、ブランチfeature/login-formの変更をmainに単一のコミットとして取り込もうとします:

git checkout main git merge --squash feature-branch git commit -m "Implement login form"

git rebase -iと比較したこのアプローチの制限は以下の通りです:

  • コントロールの粒度:個々のコミットに対するコントロールが少ないです。rebaseを使用すると、どのコミットをマージするかを選択できますが、マージはすべての変更を1つのコミットに結合するように強制的です。
  • 中間歴史: メージャーブランチでマージを使用する際、フィーチャーブランチの個別のコミット履歴が失われます。これは、フィーチャーの開発中に加えられた段階的な変更を追跡するのが難しくすることがあります。
  • コミット前レビュー: すべての変更を一つの変更セットとしてステージングするため、サquashする前に各コミットを個別にレビューやテストすることができず、インタラクティブなリベースの際には各コミットを順番にレビューおよびテストできるのとは異なります。

結論

頻繁で小さなコミットを開発ワークフローに取り入れると、協力と明確なドキュメント化を促進しますが、プロジェクトの履歴を混乱させる可能性もあります。コミットをサquashすることでバランスを取ることで、重要なマイルストーンを保持しつつ、小さな反復的な変更のノイズを排除します。

Gitに関する更多信息を学ぶために、以下のリソースを推荐します:

Source:
https://www.datacamp.com/tutorial/git-squash-commits