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-a2. マージの衝突の解決方法
マージの衝突は、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 コラボレーションの中核をなすものなので、それを理解しマスターすることは重要です。 この記事が、あなたが始めるための助けになれば幸いです。
