git pull でリモートの変更を取り込むとき、次のオプションを見かけることがあります。
git pull --ff-only
--ff-only は、Fast Forwardできる場合だけ更新し、できなければ処理を止めるオプションです。
なぜ、わざわざ失敗させるのでしょうか。結論から言えば、履歴が分岐したときに統合方法をGitへ勝手に決めさせず、人が判断するためです。
ブランチはコミットを指すポインタ
Fast Forwardを理解するには、Gitのブランチを「コミットを指すポインタ」と考えると分かりやすいです。
A --- B --- C
↑
main
新しくコミットDを作ると、main がCからDへ移動します。
A --- B --- C --- D
↑
main
ブランチの中にコミットが格納されているのではなく、ブランチが特定のコミットを指しています。
Fast Forwardはポインタの移動
ローカルの main がB、リモート追跡ブランチの origin/main がDを指しているとします。
A --- B --- C --- D
↑ ↑
main origin/main
DはBの子孫なので、ローカルの main はDへそのまま移動できます。
A --- B --- C --- D
↑
main, origin/main
これがFast Forwardです。新しいコミットやマージコミットは作られません。Gitは既存の履歴を使い、ブランチが指すコミットを前へ進めます。
Fast Forwardできない状態
リモートにC、ローカルにDという別々のコミットが追加された場合を考えます。
C ← origin/main
/
A --- B
\
D ← main
CとDはどちらもBの子孫ですが、DはCの子孫ではありません。main をDからCへ移動するだけでは、ローカルで作ったDが main の履歴から外れてしまいます。
この状態ではFast Forwardできません。CとDをどう統合するか決める必要があります。
merge を選ぶなら、両方の履歴を親に持つマージコミットMを作ります。
C -----
/ \
A --- B M
\ /
D -----
rebase を選ぶなら、Dの変更をCの後ろへ付け替え、新しいコミットD'を作ります。
A --- B --- C --- D'
どちらも正しい選択肢ですが、残る履歴は異なります。ここには人の判断が必要です。
git pull は取得だけではない
git pull は、ざっくり次の2段階で動きます。
git fetch
↓
取得したリモートの履歴を現在のブランチへ統合
単に最新データをダウンロードするコマンドではありません。fetch のあと、現在のブランチへどう反映するかまで含んでいます。
履歴が一直線ならFast Forwardできます。しかし履歴が分岐していれば、merge や rebase などの統合方法を選ばなければなりません。
--ff-only は分岐時の安全装置
git pull --ff-only
このコマンドの動作は明快です。
Fast Forwardできる
↓
そのまま更新する
Fast Forwardできない
↓
統合せずにエラーで止まる
価値があるのは、Fast Forwardできないときです。そこで一度止まるため、次のような意図しない操作を防ぎやすくなります。
- 分岐に気づかないまま履歴を統合する
- 不要なマージコミットを作る
- 本当は
rebaseしたい場面でmergeする
失敗したら、まず履歴を確認します。
git status
git log --graph --oneline --decorate --all
そのうえで、merge、rebase、ローカルコミットの取り消しなどから、状況に合う方法を選びます。
Fast Forwardが常に正しいわけではない
Fast Forwardは履歴を一直線に保ちやすい一方、マージコミットを残したい場面もあります。
たとえばfeatureブランチを main へ統合するとき、複数のコミットを1つの機能開発として履歴に残したいなら、Fast Forwardできる状態でも --no-ff を使えます。
git merge --no-ff feature
つまり、Fast Forwardが正しく、マージコミットが間違いという話ではありません。どのような履歴を残したいかで選びます。
--ff、--no-ff、--ff-only の違い
| オプション | Fast Forwardできる場合 | Fast Forwardできない場合 |
|---|---|---|
--ff |
Fast Forwardする | マージコミットを作る |
--no-ff |
マージコミットを作る | マージコミットを作る |
--ff-only |
Fast Forwardする | エラーで止まる |
git merge では、通常 --ff が既定です。Fast Forwardできるならブランチのポインタだけを動かし、できなければマージコミットを作ります。
一方、現在のGit公式ドキュメントでは、分岐した履歴の統合方法を指定していない git pull は --ff-only が既定とされています。ただし設定によって挙動を変更できるため、コマンドに明示すれば「Fast Forwardできなければ止める」という意図が読み手にも伝わります。
常にこの動作を使いたい場合は、設定でも指定できます。
git config --global pull.ff only
git pull --ff-only が表す意思
git pull --ff-only は、次の方針をコマンドとして表したものです。
リモートの変更が自分の履歴の単純な続きなら取り込む。そうでなければ止まり、統合方法を自分で決める。
Fast Forwardの実体は、ブランチのポインタを子孫のコミットへ移動できるかどうかです。
git pull --ff-only が失敗したら、単に「pullできなかった」と捉えるのではなく、「ローカルとリモートの履歴が分岐している」と読めます。そこから履歴を確認し、どうつなげるかを選ぶのが次の一手です。