git の merge・rebase、pullの設定について学ぶ
自分の .gitconfig を見て、よくわかってない設定があった。(この設定矛盾してない?)
改めて整理する。
[pull]
rebase = true
ff = only
まず merge について理解する
merge とは他のブランチの変更を取り込むこと。
merge にはいくつかの種類がある。
Fast Foward
Fast Fowardは早送りマージとも言われます。その理由は、ブランチのポインタが先に進むだけだからです。
以下のような状況があるとします。
❯ g log
commit 269cafb9656a41fbc61c65e29b745a2d2a48187e (HEAD -> feature/add-hige)
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Mon Dec 9 23:21:29 2024 +0900
add left and right hige
commit 499aef7db7bdac177a3923bbf7e3b621688353fd
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Mon Dec 9 23:20:00 2024 +0900
add hige
commit 40aa8f4eaddc7549151f3faeab5745c1dca07a4b (main)
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Mon Dec 9 23:15:27 2024 +0900
first commit
mainブランチは first commit を指しており、そこから派生した feature/add-hige ブランチから2つのコミットが派生している状況です。
この状況で、main ブランチに対して git merge feature/add-hige
を実行するとどうなるでしょうか。
❯ g merge --ff feature/add-hige
Updating 40aa8f4..269cafb
Fast-forward
neko.html | 3 +++
1 file changed, 3 insertions(+)
❯ g log
commit 269cafb9656a41fbc61c65e29b745a2d2a48187e (HEAD -> main, feature/add-hige)
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Mon Dec 9 23:21:29 2024 +0900
add left and right hige
commit 499aef7db7bdac177a3923bbf7e3b621688353fd
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Mon Dec 9 23:20:00 2024 +0900
add hige
commit 40aa8f4eaddc7549151f3faeab5745c1dca07a4b
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Mon Dec 9 23:15:27 2024 +0900
first commit
main ブランチが、feature/add-hige の先端コミットを指すようになります。
このように、merge するブランチ(feature/add-hige)の派生元となるコミットを、派生元ブランチ(mainブランチ)が指している状態で merge を実施する際に、merge コミット(後述)を作成せずにポインタを進めるだけの操作を Fast Foward という。
Auto Merge
Auto Merge はマージコミットを生成することで変更取り込む方法です。
ここでは、main ブランチと feature/mouth ブランチの2つが存在するとします。
feature/mouth は以下のように、mainの e986b03ef524db34d97b972481b8161d8a9301d7
コミットから派生して、add mouth: 216934f4d5f1f8a02c0731e51f6e769cf8f27de4
コミットが追加されている状況です。
❯ g log
commit 216934f4d5f1f8a02c0731e51f6e769cf8f27de4 (HEAD -> feature/mouth)
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Mon Dec 9 23:49:08 2024 +0900
add mouth
commit e986b03ef524db34d97b972481b8161d8a9301d7 (main, feature/add-hige)
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Mon Dec 9 23:38:18 2024 +0900
write type: mikeneko
一方、main ブランチにも変更が入ったとします。
以下のように、add color: 862510394519dc069f083d34d4a61939f2524a92
コミットが追加されました。
❯ g log
commit 862510394519dc069f083d34d4a61939f2524a92 (HEAD -> main)
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Mon Dec 9 23:52:43 2024 +0900
add color
commit e986b03ef524db34d97b972481b8161d8a9301d7 (feature/add-hige)
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Mon Dec 9 23:38:18 2024 +0900
write type: mikeneko
この状況で、git merge feature/mouth
を実行してみます。
すると、以下のような log になります。
❯ git merge feature/mouth
Auto-merging neko.html
Merge made by the 'ort' strategy.
neko.html | 2 ++
1 file changed, 2 insertions(+)
❯ g log
commit 2113fa098b65ee5f437643ba6a002d9cc3c11ec5 (HEAD -> main)
Merge: 8625103 216934f
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Mon Dec 9 23:54:28 2024 +0900
Merge branch 'feature/mouth'
commit 862510394519dc069f083d34d4a61939f2524a92
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Mon Dec 9 23:52:43 2024 +0900
add color
commit 216934f4d5f1f8a02c0731e51f6e769cf8f27de4 (feature/mouth)
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Mon Dec 9 23:49:08 2024 +0900
add mouth
commit e986b03ef524db34d97b972481b8161d8a9301d7 (feature/add-hige)
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Mon Dec 9 23:38:18 2024 +0900
write type: mikeneko
Merge branch 'feature/mouth': 2113fa098b65ee5f437643ba6a002d9cc3c11ec5
というコミットが追加されています。これがマージコミットです。
マージコミットの特徴として、2つのコミットを親にもっています。(Merge: 8625103 216934f
)
ちなみに、上記の履歴(コミットの順番)は 以下のような log 表示になります
❯ g log
commit 1e02bb8319a8305728fa6b50ef6974b21453d097 (HEAD -> main)
Merge: 7f2d47c 2b31baf
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Tue Dec 10 00:02:16 2024 +0900
Merge branch 'feature/eye'
commit 7f2d47c8f30d3f543e36cc1980fd0c0f581b2eb4
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Tue Dec 10 00:01:53 2024 +0900
4番目
commit 2b31baf9a4e3d099dcf5aec3145e04da5df10517 (feature/eye)
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Tue Dec 10 00:01:27 2024 +0900
3番目
commit 932eab65cc014dc862da315cb9e8ecb6d95a1731
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Tue Dec 10 00:01:16 2024 +0900
2番目
commit ffc3b90b347d5d165920bac9fa2fc7449643358f
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Tue Dec 10 00:00:53 2024 +0900
1番目
ここまででわかったこと。
merge の種類は2種類ある。
- Fast Foward
- Auto Merge
最初の設定ファイルの ff = only
は、fast foward を強制(fast foward ができる状態じゃないと失敗 = merge commit を作らない)という設定であることが理解できる。
続いて rebase についても再度整理する。
というのは、↓で書いた rebase = true
という設定があるように、git pull で fetch してきた後に、merge か rebase かを設定することができ、rebase を指定している。
rebase
rebase とは、コミットの付け替え操作です。
付け替えとはどういうことか、実際の例で確認していきましょう。
main ブランチと feature/ear ブランチがあるとします。
feature/ear ブランチは Merge branch 'feature/eye'
から派生して、2つのコミットがされている状態です。
❯ g log
commit 28fde08c6018d7d3c699e8537230c95a7a0d4e56 (HEAD -> feature/ear)
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Tue Dec 10 00:24:44 2024 +0900
add right ear
commit 1f232dee0b1326a3999b8ae4f9021d9365ff8c98
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Tue Dec 10 00:24:31 2024 +0900
add left ear
commit 1e02bb8319a8305728fa6b50ef6974b21453d097
Merge: 7f2d47c 2b31baf
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Tue Dec 10 00:02:16 2024 +0900
Merge branch 'feature/eye'
一方 main ブランチでも1つのコミットが追加されています。
❯ g log
commit fac220e27fdd5c05f64a06653fdf1be1d5c42100 (HEAD -> main)
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Tue Dec 10 00:25:09 2024 +0900
add comment 3
commit 1e02bb8319a8305728fa6b50ef6974b21453d097
Merge: 7f2d47c 2b31baf
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Tue Dec 10 00:02:16 2024 +0900
Merge branch 'feature/eye'
この状態で、main ブランチに対して、feature/ear の変更を取り込みたい場合、git merge
をする、というのも選択肢の1つです。
そうすると、add right ear: 28fde08c6018d7d3c699e8537230c95a7a0d4e56
と add comment 3: fac220e27fdd5c05f64a06653fdf1be1d5c42100
を親としたマージコミットが生成されることになります。
このマージコミットですが、各ブランチの変更をまとめるために生まれたコミットで普通のコミットとは少し毛色が違います。
マージコミットが増えると、普通の変更をコミットした履歴が見にくくなるため増やしたくない、という考え方があります。
そうした場合の選択肢として、rebase があります。git rebase feature/ear
を実行すると、どうなるでしょうか。
❯ g rebase feature/ear
Successfully rebased and updated refs/heads/main.
❯ g log
commit efbd7b2bdaed5de3c60618a3ae151c44a5495564 (HEAD -> main)
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Tue Dec 10 00:25:09 2024 +0900
add comment 3
commit 28fde08c6018d7d3c699e8537230c95a7a0d4e56 (feature/ear)
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Tue Dec 10 00:24:44 2024 +0900
add right ear
commit 1f232dee0b1326a3999b8ae4f9021d9365ff8c98
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Tue Dec 10 00:24:31 2024 +0900
add left ear
commit 1e02bb8319a8305728fa6b50ef6974b21453d097
Merge: 7f2d47c 2b31baf
Author: Kazuki Shimamoto <jakky0529@gmail.com>
Date: Tue Dec 10 00:02:16 2024 +0900
Merge branch 'feature/eye'
rebase をすると、feature/ear の差分を取り込みつつ、main の先端コミットもしっかりと残っています。また、マージコミットがありません。そうすることで、それぞれのブランチで行った変更のコミットだけが並んでおり、履歴が見やすくなっています。
rebase 前の main ブランチのコミットはadd comment 3: fac220e27fdd5c05f64a06653fdf1be1d5c42100
でした。
一方 rebase 後を見てみましょう。add comment 3: efbd7b2bdaed5de3c60618a3ae151c44a5495564
となっています。コミットハッシュが変わっています。
先ほどのように、main ブランチに対してfeature/ear ブランチをrebaseする際に以下のことが起きています
- mainブランチの
add comment 3
コミットを一時的に避難させる - feature/ear ブランチの変更をmainに取り込む(イメージ)
- feature/ear ブランチの先頭コミットを親として、
add comment 3
を作り直す(ここでコミットハッシュが変わる) - 新しく作られた
add comment 3
コミットにmain ブランチを移動
rebase の注意点
ここで述べた通り、rebase はコミット履歴を改変してしまう。そのため、複数人で作業するリモートブランチがある場合には注意が必要。
コミット履歴が変わり、リモートブランチとの差分ができると、push ができない状態になってしまう。もしそこで push -f
をしようものなら、一緒に開発している人のローカルブランチとの差分が生まれてしまい、大変な迷惑をかけてしまうことになる。
これが、rebase で注意すべき点と、push -f を安易にしてはいけない、と言われる理由。
ただ、rebase 自体はそこまで怖がらなくても良い。
というのも、push
ができなかった時点で、リモートとの差分が生まれていることがわかるため、push -f
さえ行わなければ、自分の手元で修正を試みることができる。
そのブランチを自分しか触らないという前提であれば、rebase をうまく使うことでマージコミットを作らないように履歴をきれいにすることができる。
上記のように、ブランチの派生元コミットを、派生元ブランチの先端コミットになるように付け替えすることで、master は Fast Foward 可能な形(マージコミットが生まれないで差分取り込み可能な状態)になる。
[Fast Foward/ Auto] merge、rebase について理解したところで、改めて以下の設定を見る。
[pull]
rebase = true
ff = only
rebase = true としているが、ff = only にしているので、Fast Foward できない状態だと fetch のみ実行になるのかな?と思いました。
しかし、ググってみるとどうやら違うみたいです。
下記の設定をしておくとfast-forwardマージできない場合はrebaseをしてくれるようになります。
git config --global pull.rebase true
ということでした。
Fast Foward できるようなケース
Fast Foward できなくて rebase になるケース
わかってきた気がする
例えば、前提を以下で考えるとする
- main ブランチは push 不可
- feature ブランチからPRをマージすることで main が更新される
この時、ff = only
を設定することで、main ブランチで pull する際に、main ブランチに不要なマージコミットが生まれないようにすることができる。
一方rebase = true
は main ブランチの pull では特に意味をなさないはず。
なぜなら前提の状況を踏まえると、main ブランチでFast Foward ができない状況はないはず。
では、複数人で開発する feature ブランチに対してはどうか。
複数人で feature ブランチに差分をコミットしていく場合、Fast Foward ができないケースが考えられる。そこで、マージコミットを作って差分を取り込む
or fetchのみで留めるか
という選択肢が生まれる。
ff = only
を設定していれば、マージコミットは作らずfetchで留めることができる。
では、実際にff ができず fetch のみでとどまった場合はどうするかというと以下の選択肢があると思われる。
- merge を実行し、マージコミットを作って差分を取り込む
- rebase を実行し、自分のコミットをremoteブランチに付け替える
この時に、問答無用で2の対応がしたい場合は、rebase = true
という設定と併用することで対応できる。
理解ができたところで、pull の設定をどうするかだが、ff = only
のみにしようと思う。
というのも、今の自分のgit運用だとそんなに複雑なブランチ運用をすることがないので、rebase = true
はつけておいて良いと理解しているのだが、一旦外して自分が意図せず rebase されているタイミングがあるのか、観察してみよう、と考えた。
下記の設定をしておくとfast-forwardマージできない場合はrebaseをしてくれるようになります。
git config --global pull.rebase true
とありましたが、実際に試してみたところ、そうはなっていませんでした。
普通に fatal: Not possible to fast-forward, aborting.
となって失敗しています。
なので、ff = only にする、という結論は同じだが、両方の設定をすることはできない、という結論でした。