git merge

コマンドの概要

複数のブランチに分岐して管理されたコミットを統合して1つのブランチにするコマンド。
コミットは親子関係で把握されており、共通のご先祖さまにさかのぼってそこからの変更が統合される。

mainブランチでEをコミットしてgit switch -c topicとし、topicブランチではAからCのコミット、mainブランチではF、Gのコミットをした場合、以下のようになる。

gitGraph commit id:"D" commit id:"E" branch topic checkout topic commit id:"A" commit id:"B" commit id:"C [topic]" checkout main commit id:"F" commit id:"G [HEAD]"
ここで、mainブランチをチェックアウト、git merge topicとすると
gitGraph commit id:"D" commit id:"E" branch topic checkout topic commit id:"A" commit id:"B" commit id:"C [topic]" checkout main commit id:"F" commit id:"G" merge topic id:"H [HEAD]"
となり、Hというマージコミットが作成され、topicブランチとmainブランチの共通のご先祖さまであるEを始点として、そこからの変更であるコミットA,B,Cをmainブランチに統合する。
mainブランチが指し示すコミットHは、コミットAからFまでの内容を含んだものとなり、topicブランチはCを指したまま。git mergeしてもtopicブランチは削除されない。マージによりtopicブランチがもう不要となったら、git branch -d topicでブランチを削除する。
gitGraph commit id:"D" commit id:"E" branch topic checkout topic commit id:"A" commit id:"B" commit id:"C" checkout main commit id:"F" commit id:"G" merge topic id:"H [HEAD]"
Cのポインタであった[topic]が削除されるだけで、コミットA,B,Cが削除されるわけではない。

コンフリクト

各ブランチで同じ箇所を変更してしまった場合、Gitがどちらを採用すべきか判断できず、マージがストップしてしまうので、手動で解決する。
コンフリクトするはずじゃなかった、というときはgit merge --abortでマージを中止し、各ブランチを見直してみる。

コマンドで

  1. コンフリクトでマージがストップ
  2. git statusでコンフリクトしているファイルを確認
  3. git diff git diff --ours git diff --theirs git diff --baseでコンフリクトしている差分を確認。
  4. git checkout --ours git checkout --theirs でどちらかの変更を適用するか、ファイルを直接編集して解決する

マージツールで

git diffしながらエディタをさわるのも面倒なので、git mergetoolでマージツールを利用して解決する。

  1. コンフリクトでマージがストップ
  2. $ git mergetool
    vim diff を使用する場合、
LOCALMBEARSGEEDREMOTE

という画面構成。ウインドウ間の移動は ctrl+w hjkl
MERGEDを直接編集してコンフリクトを解決し、:xaで保存、全ウインドウを終了。

  • vim の:wq:x
    どちらも保存して終了だが、:wqはかならず保存して終了するが、:xは変更があった場合だけ保存して終了する。(:ZZ:xと同じ)

ファストフォワード

マージしたいブランチが現在のブランチより先に進んでおり、現在のブランチがコミットしていないとき、git mergeをすると、そのままHEADがマージしたいブランチが指しているコミットに移動する。

コミットE でgit branch -c topicとし、mainではコミットなし、topicでA、B、C のコミットをしたとき

gitGraph commit id:"D" commit id:"E [HEAD]" branch topic checkout topic commit id:"A" commit id:"B" commit id:"C [topic]"
ここで、git merge topicとすると
gitGraph commit id:"D" commit id:"E" commit id:"A" commit id:"B" commit id:"C [HEAD,topic]"
となる。

git merge --no-ff topicとし、ファストフォワードさせずにマージコミットを作ることもできる。

gitGraph commit id:"D" commit id:"E" branch topic checkout topic commit id:"A" commit id:"B" commit id:"C [topic]" checkout main merge topic id:"F [HEAD]"
ファストフォワードありがデフォルトになっており、設定可能。 ただし、git pull時にはファストフォワードしたいので(ファストフォワードしないとgit pullのたびにマージコミットができてしまう)、その設定もあわせて。

1// ファストフォワードせずにマージコミットを作る、ただし git pull のときはつくらない
2$ git config --global merge.ff false
3$ git config --global pull.ff only

ブランチをまとめてひとつのコミットにする

ファストフォワードができる状況で、マージしたいブランチのコミットをまとめてひとつのコミットにすることもできる。 コミットE でgit branch -c topicとし、mainではコミットなし、topicでA、B、C のコミットをしたとき

gitGraph commit id:"D" commit id:"E [HEAD]" branch topic checkout topic commit id:"A" commit id:"B" commit id:"C [topic]"
ここで、git merge --squash topicとするとコミットA、B、Cにおける変更がインデックスにステージされる。そのままgit commitすると

1$ git merge --squash topic
2$ git commit -m"merge branch topic"

gitGraph commit id:"D" commit id:"E" branch topic checkout topic commit id:"A" commit id:"B" commit id:"C [topic]" checkout main commit id:"F [HEAD]"
コミットFにコミットA、B、Cの変更がまとめられる。