Closed19

git の merge・rebase、pullの設定について学ぶ

kazskazs

自分の .gitconfig を見て、よくわかってない設定があった。(この設定矛盾してない?)
改めて整理する。

[pull]
        rebase = true
        ff = only
kazskazs

まず merge について理解する

merge とは他のブランチの変更を取り込むこと。
merge にはいくつかの種類がある。

kazskazs

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 という。

kazskazs

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)

kazskazs

ちなみに、上記の履歴(コミットの順番)は 以下のような 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番目
kazskazs

ここまででわかったこと。

merge の種類は2種類ある。

  • Fast Foward
  • Auto Merge

最初の設定ファイルの ff = onlyは、fast foward を強制(fast foward ができる状態じゃないと失敗 = merge commit を作らない)という設定であることが理解できる。

kazskazs

続いて rebase についても再度整理する。
というのは、↓で書いた rebase = true という設定があるように、git pull で fetch してきた後に、merge か rebase かを設定することができ、rebase を指定している。
https://zenn.dev/link/comments/2c3620115f9bda

kazskazs

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: 28fde08c6018d7d3c699e8537230c95a7a0d4e56add 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 の先端コミットもしっかりと残っています。また、マージコミットがありません。そうすることで、それぞれのブランチで行った変更のコミットだけが並んでおり、履歴が見やすくなっています。

kazskazs

rebase 前の main ブランチのコミットはadd comment 3: fac220e27fdd5c05f64a06653fdf1be1d5c42100でした。

一方 rebase 後を見てみましょう。add comment 3: efbd7b2bdaed5de3c60618a3ae151c44a5495564となっています。コミットハッシュが変わっています。

先ほどのように、main ブランチに対してfeature/ear ブランチをrebaseする際に以下のことが起きています

  1. mainブランチの add comment 3 コミットを一時的に避難させる
  2. feature/ear ブランチの変更をmainに取り込む(イメージ)
  3. feature/ear ブランチの先頭コミットを親として、add comment 3を作り直す(ここでコミットハッシュが変わる)
  4. 新しく作られた add comment 3コミットにmain ブランチを移動
kazskazs

rebase の注意点

ここで述べた通り、rebase はコミット履歴を改変してしまう。そのため、複数人で作業するリモートブランチがある場合には注意が必要。
コミット履歴が変わり、リモートブランチとの差分ができると、push ができない状態になってしまう。もしそこで push -f をしようものなら、一緒に開発している人のローカルブランチとの差分が生まれてしまい、大変な迷惑をかけてしまうことになる。

これが、rebase で注意すべき点と、push -f を安易にしてはいけない、と言われる理由。

kazskazs

ただ、rebase 自体はそこまで怖がらなくても良い。
というのも、push ができなかった時点で、リモートとの差分が生まれていることがわかるため、push -fさえ行わなければ、自分の手元で修正を試みることができる。

kazskazs

そのブランチを自分しか触らないという前提であれば、rebase をうまく使うことでマージコミットを作らないように履歴をきれいにすることができる。

上記のように、ブランチの派生元コミットを、派生元ブランチの先端コミットになるように付け替えすることで、master は Fast Foward 可能な形(マージコミットが生まれないで差分取り込み可能な状態)になる。

kazskazs

[Fast Foward/ Auto] merge、rebase について理解したところで、改めて以下の設定を見る。

[pull]
        rebase = true
        ff = only

rebase = true としているが、ff = only にしているので、Fast Foward できない状態だと fetch のみ実行になるのかな?と思いました。
しかし、ググってみるとどうやら違うみたいです。

https://small-livehack.com/best_gitconfig/#fast-forward_only

下記の設定をしておくとfast-forwardマージできない場合はrebaseをしてくれるようになります。
git config --global pull.rebase true

ということでした。

kazskazs

Fast Foward できなくて rebase になるケース

kazskazs

わかってきた気がする

例えば、前提を以下で考えるとする

  • main ブランチは push 不可
  • feature ブランチからPRをマージすることで main が更新される

この時、ff = onlyを設定することで、main ブランチで pull する際に、main ブランチに不要なマージコミットが生まれないようにすることができる。

一方rebase = true は main ブランチの pull では特に意味をなさないはず。
なぜなら前提の状況を踏まえると、main ブランチでFast Foward ができない状況はないはず。

kazskazs

では、複数人で開発する feature ブランチに対してはどうか。

複数人で feature ブランチに差分をコミットしていく場合、Fast Foward ができないケースが考えられる。そこで、マージコミットを作って差分を取り込む or fetchのみで留めるかという選択肢が生まれる。
ff = only を設定していれば、マージコミットは作らずfetchで留めることができる。

では、実際にff ができず fetch のみでとどまった場合はどうするかというと以下の選択肢があると思われる。

  1. merge を実行し、マージコミットを作って差分を取り込む
  2. rebase を実行し、自分のコミットをremoteブランチに付け替える

この時に、問答無用で2の対応がしたい場合は、rebase = true という設定と併用することで対応できる。

kazskazs

理解ができたところで、pull の設定をどうするかだが、ff = onlyのみにしようと思う。
というのも、今の自分のgit運用だとそんなに複雑なブランチ運用をすることがないので、rebase = trueはつけておいて良いと理解しているのだが、一旦外して自分が意図せず rebase されているタイミングがあるのか、観察してみよう、と考えた。

kazskazs

https://zenn.dev/link/comments/45813631a6b23c

下記の設定をしておくとfast-forwardマージできない場合はrebaseをしてくれるようになります。
git config --global pull.rebase true

とありましたが、実際に試してみたところ、そうはなっていませんでした。
普通に fatal: Not possible to fast-forward, aborting. となって失敗しています。
なので、ff = only にする、という結論は同じだが、両方の設定をすることはできない、という結論でした。

このスクラップは12日前にクローズされました