1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

git pull --ff-only から理解する Git の Fast Forward

1
Posted at

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できなかった」と捉えるのではなく、「ローカルとリモートの履歴が分岐している」と読めます。そこから履歴を確認し、どうつなげるかを選ぶのが次の一手です。

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?