My tech diary

ソフトウェアエンジニアをやっています。技術的内容を中心に調べたことを書いていきます。

Git 2.27 での git pull 時の warning について

概要

今更ですが、いつぞや Git のバージョンを上げた後、 git pull したときに、以下の warning が出るようになりました。

warning: Pulling without specifying how to reconcile divergent branches is
discouraged. You can squelch this message by running one of the following
commands sometime before your next pull:

  git config pull.rebase false  # merge (the default strategy)
  git config pull.rebase true   # rebase
  git config pull.ff only       # fast-forward only

You can replace "git config" with "git config --global" to set a default
preference for all repositories. You can also pass --rebase, --no-rebase,
or --ff-only on the command line to override the configured default per
invocation.

いつかちゃんと対処しようと思いつつ、時間が経ってしまったので、ちゃんと対処します。

このメッセージは、検索すると山ほど情報が出てくる通り Git 2.27.0 で導入されたメッセージのようです。 メッセージにあるように、以下の 3 つの設定のうち、いずれかを実施すれば、 warning は出なくなります。

  • git config pull.rebase false
  • git config pull.rebase true
  • git config pull.ff only

特に、今までの動作に不満がないという人は git config --global pull.rebase false をやっておけば、今までの挙動の通り、 warning だけ出なくなります。

まぁ、なのですが、せっかくの機会なので、振り返りを込めて、もうちょっと理解を深めておきます。

git merge の選択肢の理解

git pull というのは、基本的には remote branch の local branch への "マージ" です。

git merge の戦略には、以下の 2 つがあります。

  • merge commit を作成 (3-way merge が行われる)
  • (必要なら merge 対象のブランチを rebase した上での) fast-forward (merge commit を作成しない)

例えば、以下のように main branch と developcommit a 以降で分岐しているとします。

git_merge_base.png

merge commit を作って merge する場合は、それぞれの branch の履歴はそのままに、新たに merge commit m が作られます。

git_merge_merge_commit.png

fast-forward する場合は、 merge commit を作成しないということなので、 develop 分岐後に main branch には何もコミットがされていないことが条件になります。 (つまり main branch が commit a の時点のままなら、そのまま develop を fast-forward merge できる)

git_merge_base_simple.png

そうでない場合は、まず develop の分岐元が main の HEAD コミットである commit d になるように develop の付け替え (rebase) を行います。 develop 側の全ての commit は、元の commit とは別物の e', f', g' になります。 このように、履歴が書き換わるので、コンフリクトにご注意ください。 また、 Git 上はコンフリクトしなくても、コードの意味的・実際の動作上、コンフリクトする場合があるので、テストは必ず行いましょう。 (これは merge commit 作るときも同じですが)

git_merge_fast_forward_1.png

その上で fast-forward を行います。これは main branch の HEAD を commit g' に移動させるだけです。このように履歴の上では一直線になります。

git_merge_fast_forward_2.png

git pull 操作の選択肢の理解

さて、そこで、先程の warning に対処するための 3 つの選択肢を考えたいのですが、その前に、 git pull--rebase option について確認します。 何も指定しない場合は、 --rebase false と同じで、これがデフォルトの動作です。 --rebase を指定した場合は --rebase true と同じです。 他にも rebase の挙動を細かく制御するための --rebase merges, --rebase preserve, --rebase interactive がありますが、割愛します。

git_pull_base.png

通常、 git pull (--rebase を付けない) を行うと、 git fetch を行った上で git merge を行う流れになります。 デフォルトの設定では、 rebase せずに fast-forward 可能な場合は fast-forward を行い、そうでない場合は、先程の図のように merge commit を生成しようとします。

git_pull_merge.png

それに対して git pull --rebase を行うと、 git fetch を行った上で、ローカルブランチ (この例では main branch) の git rebase を行う流れになります。 上述の git merge の際の fast-forward の説明では、 git merge を行うために develop 側の rebase を行ってから fast-forward を実行しましたが、この場合は、 main 側の rebase を行っていることに注意してください。(main - develop の関係が origin/main - main の関係になっているだけで、全く同じことではあるのですが)

こうすることで、リモートブランチにはなんら影響を与えることがないのです。

git_pull_rebase_1.png

git_pull_rebase_2.png

git pull--rebase option の使い所ですが、主に以下の 2 つがあるようです。

  • 同一ブランチに対して、複数人が同時並行で開発を行う場合
    • ローカルブランチを rebase した上で push することで、 merge commit が乱立することを防ぐことができます
  • Pull Request マージ前に、マージ先のデフォルトブランチを取り込んで、テストなどを行う場合
    • 例えば、 Pull Request の feature ブランチにマージ先である main ブランチをあらかじめマージしても、 feature ブランチには merge commit が作られずに済みます (結果的に履歴が綺麗になります)

また git pull の fast-forward 関連の以下の 3 つの option も確認します。 (git merge にも全く同じ option があります) 以下の表にまとめます。 (デフォルトは --ff です)

option fast-forward 可能な場合 fast-forward できない場合
--ff fast-forward で merge する merge commit を生成する
--no-ff merge commit を生成する merge commit を生成する
--ff-only fast-forward で merge する merge せず、エラー終了する

先程の 3 つの選択肢をそれぞれ選んだ場合の挙動

ここまでくれば、 git pull にオプションを付けずに実行したときの動作について、 Git が以下のいずれかを要求していることが分かります。

git config pull.rebase false

デフォルトの挙動です。git pull--rebase option を付けずに実行するのと同じです。 標準動作では rebase せずに fast-forward 可能な場合は fast-forward を行い、そうでない場合は、 merge commit を生成しようとします。

git config pull.rebase true

git pull --rebase を行う場合と同じです。 git fetch を行った上で、ローカルブランチに対して rebase を実施するので、merge commit が作られずに、コミット履歴が一直線になります。

git config pull.ff only

--ff-only option を付けた時と同様、 fast-forward 可能な場合のみ、 fast-forward します。 そうでない場合は、 merge/rebase せず、エラー終了します。

結局

merge/pull の戦略をどうするかは、結局は、チームごとの方針だと思います。 基本的にはチーム内で揃えておいた方が、 Git の履歴を見るときにやりやすいとは思います。人が増えるとなかなかそれも難しかったりしますが。

merge/pull の戦略は、コードの履歴をどうまとめたいかという話であって、現在のスナップショットには特に影響を及ぼしません。 (Git 運用時の操作で問題が起きやすいとかの影響は出るかもしれませんが) 特に致命的な問題はないので、結論としては、悩んだときはデフォルトの設定にしておきましょう😂

References