マージの仕組み(Fast-forward と3-way)
このページで分かること
git mergeでほかのブランチの変更を取り込める- Fast-forward と 3-way マージの違いを、履歴の形で説明できる
- マージベースとマージコミット(親が2つのコミット)が分かる
git branch --mergedで取り込み済みのブランチを確かめて片付けられる
このページの目次9 items
22で、クッキーのレシピを recipe-cookie ブランチに記録しました。このページでは、それを main に取り込みます。取り込む操作がマージ(merge、合流させる)です。マージには Fast-forward と 3-way の2つの形があり、どちらになるかは履歴の形で決まります。
マージの基本:取り込みたい側に立って git merge#
git merge ブランチ名 は、今いるブランチに、指定したブランチの変更を取り込むコマンドです。向きを間違えやすいので、「取り込まれる側(main)に立ってから、取り込むブランチを指定する」と覚えてください。
cd ~/git-practice
git switch main
git merge recipe-cookieUpdating 4e16e23..4b541f5
Fast-forward
recipes/cookie.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 recipes/cookie.txt2行目に Fast-forward(早送り)と出ました。
git log --oneline --decorate --graph -n 3* 4b541f5 (HEAD -> main, recipe-cookie) クッキーのレシピを追加
* 4e16e23 やることに掃除を追加
* 370768c (tag: v1.0) ケーキのレシピを追加main が recipe-cookie と同じコミットまで進みました。新しいコミットは作られていません。
Fast-forward:付箋を前に動かすだけ#
recipe-cookie を作ってから、main には新しいコミットがありませんでした。つまり recipe-cookie の履歴は、main の履歴の「続き」になっています。
この場合、取り込むために合わせる作業は何もありません。main の付箋を recipe-cookie と同じコミットへ貼り替えれば、それで取り込み完了です。これが Fast-forward です。Updating 4e16e23..4b541f5 は「main を 4e16e23 から 4b541f5 へ動かした」という意味です。
3-way マージ:両方が進んでいるとき#
今度は、両方のブランチがそれぞれ進んだ状態を作ります。買い物メモの変更をブランチで、やることリストの変更を main で記録します。
git switch -c shopping-apple
echo "りんごを買う" >> shopping.txt
git commit -am "買い物メモにりんごを追加"
git switch main
echo "花に水をやる" >> todo.txt
git commit -am "やることに水やりを追加"
git log --oneline --decorate --graph --all -n 4* dd3f00e (HEAD -> main) やることに水やりを追加
| * 587a247 (shopping-apple) 買い物メモにりんごを追加
|/
* 4b541f5 (recipe-cookie) クッキーのレシピを追加
* 4e16e23 やることに掃除を追加|/ のところで、履歴が2本に分かれています。main の付箋を前に動かすだけでは、shopping-apple のりんごが入りません。ここでマージします。
git merge shopping-appleMerge made by the 'ort' strategy.
shopping.txt | 1 +
1 file changed, 1 insertion(+)git log --oneline --decorate --graph -n 5* ca9807e (HEAD -> main) Merge branch 'shopping-apple'
|\
| * 587a247 (shopping-apple) 買い物メモにりんごを追加
* | dd3f00e やることに水やりを追加
|/
* 4b541f5 (recipe-cookie) クッキーのレシピを追加
* 4e16e23 やることに掃除を追加2本に分かれた履歴が、ca9807e で1つに合流しました。このような、2本の流れを合わせるコミットをマージコミットと呼びます。
なぜ「3-way」なのか#
Git は、次の3点を比べてマージの結果を決めます。
| 比べるもの | 今回のコミット |
|---|---|
| 2本が分かれる前の共通の祖先(マージベース) | 4b541f5 クッキーのレシピを追加 |
| 今いるブランチの先端 | dd3f00e(main) |
| 取り込むブランチの先端 | 587a247(shopping-apple) |
マージベースは git merge-base A B で確かめられます。マージする前なら git merge-base main shopping-apple です。マージしたあとの今は、main が shopping-apple を含んでしまっているので、マージコミットの1つ目の親(HEAD^1)と2つ目の親(HEAD^2)を指定します。
git merge-base HEAD^1 HEAD^24b541f59822864b80799607d10f7a1950890b8f9マージベースから見て、main では todo.txt が、shopping-apple では shopping.txt が変わっています。片方だけで変わった部分はその変更を採用し、両方の変更をすべて含んだ状態を作ります。2つの先端だけを比べると「main にはりんごの行がない」のが「削除した」のか「まだ追加していない」のか区別できません。祖先を合わせて3点で比べるので、それが判断できるのです。
マージコミットは親が2つ#
第3部の17の方法で、マージコミットの中身を開けてみます。
git cat-file -p HEADtree 996bb0821c0f4eedd73e735831e19049043e74cf
parent dd3f00e5f823cc55b226131612308da74f43fdf7
parent 587a2478031e9009c864c112f0ca164097bee80f
author Taro Yamada <[email protected]> 1793111602 +0900
committer Taro Yamada <[email protected]> 1793111602 +0900
Merge branch 'shopping-apple'ふつうのコミットと違うのは、parent が 2行あることだけです。1つ目が main 側(マージしたときに立っていたブランチ)、2つ目が取り込んだ shopping-apple 側です。中身の tree は、両方の変更を合わせた状態のスナップショットです。
cat shopping.txt todo.txt豆乳を買う
卵を買う
パンを買う
りんごを買う
歯医者を予約する
銀行に行く
部屋を掃除する
花に水をやるりんごと水やりの両方が入っています。
取り込んだブランチを片付ける#
マージが済んだブランチは、役目を終えています。git branch --merged で、今いるブランチに取り込み済みのブランチを一覧できます。
git branch --merged* main
recipe-cookie
shopping-appleここに出ているブランチは、-d で安全に消せます(22)。
git branch -d recipe-cookie shopping-appleDeleted branch recipe-cookie (was 4b541f5).
Deleted branch shopping-apple (was 587a247).ブランチ名は消えても、コミットはマージによって main の履歴に入っているので、何も失われません。
よくある疑問#
Q. マージを途中でやめたり、やり直したりできますか?
A. 途中で止まった場合(次の24のコンフリクト)は git merge --abort でマージ前に戻れます。完了したマージを取り消したいときは、まだ共有していなければ第2部の14の git reset --hard HEAD~1 で、マージ前のコミットへ戻せます。
Q. 3-way マージで、両方が同じ行を変えていたらどうなりますか?
A. Git にはどちらを採るべきか決められないので、マージが止まります。これがコンフリクトです。次のページで、実際に起こして解決します。
まとめ#
git merge ブランチ名は、今いるブランチに指定したブランチを取り込む。取り込まれる側に立ってから実行する- 取り込まれる側が進んでいなければ Fast-forward になり、付箋を前に動かすだけで新しいコミットはできない
- 両方が進んでいれば 3-way マージになり、マージベースと2つの先端を比べて、親が2つのマージコミットを作る
git merge-baseでマージベース、git branch --mergedで取り込み済みのブランチを確かめられる- 取り込み済みのブランチは
git branch -dで片付けてよい
次のページ#
24. コンフリクトを解決する ── 2本のブランチで同じ行を書き換えてマージし、止まったマージを自分の手で仕上げます。