git merge command: 衝突の解決、差分のリベース、戦略のマージ
この記事では、git merge
というメソッドについて説明します。マージの方法からコンフリクトの解決方法、マージコミットの機能、マージ戦略、rebase や squash との違い、そして git merge
コマンドのその他のオプションについて説明します。
1. ブランチのマージ方法
あるブランチを別のブランチにマージするには git merge
コマンドを使います。
git branch post で、ブランチ名はそのブランチの最後のコミットであると述べました。
同じ意味で、ブランチをマージすると、両方のブランチの最後のコミットからのすべての変更を含む新しいコミットが作成されます、
これは、git merge
コマンドを実行した作業ブランチへの新しいコミットだと考えることもできます。
git merge
コマンドは、常にそのコマンドを実行したブランチでマージコミットを行います。
たとえば、main
ブランチから feature-a
ブランチをマージする場合は、次のようなコマンドを使用します。
$ git switch main
switched to branch 'main'
$ git merge feature-a
Merge made by the 'ort' strategy
上の例では、merge-ort
戦略を使っています。これは、2022 年に変更された Git の最新のデフォルトのマージ戦略です。
オルト戦略
次のセクションに進む前に、Git の最新のマージアルゴリズムである ort
戦略を簡単に見てみましょう。
ort
は "Ostensibly Recursive's Twin" の略で、古いデフォルトのアルゴリズムである recursive
戦略を置き換えるために作られたという意味です。
recursive
戦略と比較すると、競合を減らし、リネームされたファイルを処理することができると言われている。
基本的には3ウェイマージアルゴリズムに基づいており、3つのソースからの変更を比較します:両方のブランチからの同じ先祖コミット、両方のブランチからの最新のコミット、両方のブランチからの最新のコミットです。
を実行します。2つのブランチをマージする際には、主に ort
戦略を使用します。これは、これまで使用されてきた resolve
戦略や recursive
戦略よりも性能が良いからです。
他にも、2つ以上のブランチをマージするための octopus
戦略や、サブディレクトリにマージするための subtree
戦略も知っておく価値がある。
とりあえず、名前だけ覚えておこう。
これらの戦略は以下のように -s
または --strategy
オプションで指定することができる。
$ git merge -s recursive feature-a
2. マージの衝突の解決方法
マージの衝突は、2つの異なるブランチの同じファイルの同じ行の内容が異なる場合に発生します。 データを保持するために、Gitはあなたが戦略でオプションを与えない限り、何かを恣意的に削除することはありません。その代わり、マージを一時停止して選択肢を与えます。
これを実際に見てみましょう。
まず、1.md
ファイルを main
ブランチと develop
ブランチで異なる内容に変更しました。
そして、main
ブランチに移動して develop
ブランチをマージしようとすると、マージの競合が発生します。
初めて git commit
コマンドの -a
オプションを使う場合は、git commit post を参照ください。
この時点で、Git は <<<<<HEAD
, =========
, >>>>>>
branch-name
のようなシンボルを衝突しているファイルの内容に追加し、何を修正すべきかを正確に知らせます。
1.md
ファイルを開いてみましょう。
これがNeovimエディタで開いた1.md
ファイルです。この部分で必要なものを残し、Git が追加したシンボルを削除してコミットを進めればいいのです。
VSCodeのようなGitをサポートしているエディタなら、マージはもっと簡単です。ハイライト表示は直感的で、上の4つの文章の <<<<< HEAD
ボタンを押すと、除外された内容と Git のシンボルが自動的に削除されます。
内容を修正したら、通常のコミットと同じように git commit
コマンドでマージを完了させます。
3. rebase との違い
git merge
コマンドと git rebase
コマンドには共通点があります。それは、どちらもふたつのブランチをマージしてマージコンフリクトを解決する方法であるということです。
どちらも 2 つのブランチをマージし、マージ時のコンフリクトを解決するためのコマンドです。しかし、それぞれのコマンドには動作のしかたに違いがあり、それを理解することが重要です。
git merge
コマンドは、上の図のように 2 つのブランチの最後のコミットを親として新しいマージコミットを作成します。
その結果、コミット履歴には 2 つのブランチに対応するコミットが混在することになります。
feature-a
や feature-b
などのパスは、main
ブランチと呼ばれるメインパス上を行き来します。
git rebase
コマンドは 2 つのブランチをマージするので、コミット履歴の行き止まりがなくなります。
この仕組みは意外と簡単です。下の画像を見てみましょう。
feature-a
ブランチを共通の祖先コミット A
から分岐した main
ブランチにマージしたいとすると、git rebase
コマンドは feature-a
のコミットと同じ変更を加えた新しいコミットを作成します。
図では、これがコミット C
と D
です。
そして、この 2 つのコミットを main
ブランチの最後のコミットである B
に追加します。
これにより、コミット履歴はひとつになりますが、feature-a
ブランチのすべての変更を main
ブランチにマージできるようになります。
新しいマージコミットを作成する必要がないので、履歴もシンプルになります。
しかし、コミット C
と D
が別のブランチからマージされたコミットなのか、それとも以前 main
ブランチにあったコミットなのかを一目で見分けるのは簡単ではありません。
そのため、履歴の観点からは情報を失うことになります。
このような理由から、マージやリベースはチームの方向性に応じて適切な場面で使うようにしましょう。
git rebase
コマンドについての詳細は、この記事 を参照ください。
4. スカッシュとの違い
スクワッシュとは、複数のコミットをひとつにまとめる Git の概念です。
独立したコマンドとしては存在しませんが、merge や rebase コマンドのオプションとして使うことができます。
git merge
コマンドに --squash
オプションをつけると、スカッシュマージと呼ばれます。
下の画像を見て、スカッシュマージを理解しましょう。
現在のコミット 8c12
では、main
ブランチと develop
ブランチが分かれています。
squash マージを main
ブランチで試してみたところ、無事に成功しました。
以下の git status
コマンドの結果は、develop
ブランチのすべてのコミットが main
ブランチにステージされていることを示しています。
コミット以降の履歴を確認すると、develop
ブランチはマージされていませんが、その変更が main
ブランチの新しいコミットとして登録されていることがわかります。
これが squash マージの機能です。
squash
オプションはリベース処理中にも使用できますが、develop
ブランチの履歴が上記のように残らないという違いがあります。
これについてはまた rebase post で取り上げます。
5. 最後に
マージの衝突は、Git 初心者が最初につまづくことのひとつだと思います。 しかし、マージとマージコンフリクトの解決は Git コラボレーションの中核をなすものなので、それを理解しマスターすることは重要です。 この記事が、あなたが始めるための助けになれば幸いです。
