0
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 の「Fast-forward」とは? ブランチの枝分かれから理解する

0
Posted at

はじめに

git pull を実行したときに表示される Fast-forward というメッセージ。なんとなく見過ごしている人も多いのではないでしょうか。

この記事では、Fast-forward が何を意味するのかを「ブランチの枝分かれ」という観点から整理し、さらに「ローカルとリモートで同名ブランチを作った場合の push」など、実際の作業でつまずきやすいケースまで解説します。


Fast-forward とは

Fast-forward(早送り)が起きるのは、自分のローカルブランチがリモートの最新コミットの「一直線の手前」にいる場合です。

言い換えると、自分の HEAD がリモートの最新コミットの「祖先」になっていて、ローカル側にリモートが持っていない独自コミットが1つも無い状態です。

このとき git は、枝分かれをマージする必要がなく、ただブランチのポインタを前に進めるだけで済みます。これが「Fast-forward(早送り)」と呼ばれる理由です。マージコミットは作られません。

pull 前:   A---B---C (自分の HEAD)
                    \
                     D---E (リモート)  ← 一直線に進んでいるだけ

pull 後:   A---B---C---D---E (HEAD が E まで早送りされる)

ポイントは、Fast-forward は「コミットが進んだ」というより 「枝分かれせずまっすぐ進められた」という合図 だという点です。


枝分かれ(divergence)とは

では、Fast-forward にならないのはどんなときでしょうか。それは履歴が 枝分かれ(divergence) しているときです。

枝分かれが起きる条件は、共通の祖先コミットから、ローカルとリモートの両方がそれぞれ別のコミットを持っている ことです。

ここで重要なのは、「ローカルに独自コミットがある」だけでは枝分かれにはならないという点です。あくまで 両方が共通地点より先に進んでいるか で判定されます。

3つのパターンで整理すると分かりやすいです。

① 自分は進んでいない、リモートだけ進んだ

A---B---C        ← ローカル(C のまま)
         \
          D---E  ← リモート

Fast-forward(ポインタを前に進めるだけ)

② 自分だけ進んだ、リモートは進んでいない

A---B---C---F    ← ローカル
     \
      (リモートは C のまま)

→ pull しても取り込むものが無い(Already up to date)。push すれば今度はリモート側が Fast-forward する。

③ 自分もリモートも、それぞれ別に進んだ ← これが「枝分かれ」

共通の祖先
   ↓
   C
   ├─→ D---E   (リモート)
   └─→ F---G   (ローカル)

→ Fast-forward できず、マージコミット(コンフリクトが出たら解消)で対応する。


応用1: main を先に修正してしまったときの切り出し

「main から dev ブランチを切ってから作業するつもりが、先に main 上で修正してしまった」というケースはよくあります。その修正を dev に移したいとき、まだコミットしているかどうか で対応が変わります。

コミット前の場合(一番ラク)

未コミットの変更(作業ツリーの状態)は、まだどのブランチにも属していない「宙に浮いた」状態です。そのため、新しいブランチを作って切り替えると、変更はそのまま一緒に移動します。

git switch -c dev

これだけで dev ブランチが作られ、修正内容も一緒に移ります。main は修正前のキレイな状態のまま残ります。あとは dev 上でコミットすれば完了です。

git add .
git commit -m "修正内容"

git switch -c dev は「dev を新規作成して切り替える」コマンドです(-c は create)。古い書き方では git checkout -b dev で同じことができます。

すでに main にコミット済みの場合

この場合は「dev に修正を移す」→「main を元に戻す」の2ステップです。

# 今いる main から dev を切り出す(dev は修正コミット込みの状態になる)
git switch -c dev

# main に戻して、コミットを1つ巻き戻す
git switch main
git reset --hard HEAD~1
操作前:   A---B---C (main, HEAD) ← C が間違って入れた修正

git switch -c dev:
          A---B---C (main, dev)  ← dev が C を持った状態で分岐

git switch main + reset --hard HEAD~1:
          A---B (main)
                \
                 C (dev)  ← 修正 C は dev だけに残る

注意: git reset --hard は変更を破棄する操作です。main をすでに push している場合は履歴の書き換えになり共有相手とズレるため、git revert を検討してください。また --hard は他の未コミットの変更も巻き込んで消すので、実行前に確認しましょう。


応用2: ローカルとリモートで同名ブランチを作ったときの push

リモートとローカルでそれぞれ独立に同名(例: dev)のブランチを作った場合、push はどうなるでしょうか。

基本コマンドは次のとおりです。

git push -u origin dev

-u--set-upstream)は「ローカル dev とリモート origin/dev を紐付ける(追跡関係を作る)」指定です。一度やっておくと、次回からは git push / git pull だけでこのブランチが対象になります。

ただし、通るかどうかは中身が枝分かれしているかで変わります。

リモート dev に独自の変更がない場合

リモート dev がブランチ元から1コミットも進んでいない(中身が無い)なら、枝分かれは起きていません。

共通の元の地点
   ↓
   A         (リモート dev ← ここから動いていない)
   └─→ B---C (ローカル dev ← こっちだけ進んでいる)

リモートはローカルの「祖先」であり、Fast-forward が可能なパターンです。この場合は次の一発で通ります。

git push -u origin dev

リモート dev にも独自コミットがある場合

リモートとローカルがそれぞれ別コミットを持っていると、枝分かれ状態になり push は弾かれます。

リモート dev:   A---X   (リモートで作ったコミット)
ローカル dev:   A---Y   (ローカルで作ったコミット)
! [rejected]  dev -> dev (fetch first)

リモートのコミットを上書きしてしまうため、git が止めてくれます。この場合は 先にリモートを取り込んでから push します。

git pull origin dev   # 枝分かれしているのでマージ(コンフリクトが出たら解消)
git push -u origin dev

実務上は、まず git push -u origin dev を試し、rejected が出たら git pull する、という順番で問題ありません。


まとめ

  • Fast-forward は「枝分かれせずまっすぐ進められた」という合図。マージコミットは作られない。
  • 枝分かれ は「共通の祖先から、ローカルとリモートの両方がそれぞれ別コミットを持つ」状態のときだけ起きる。
  • 枝分かれしていれば push は弾かれるので、先に git pull で取り込んでから push する。
  • 未コミットの変更は git switch -c でそのまま新しいブランチへ移動できる。

「両方が共通地点より先に進んでいるか?」というイメージを持っておくと、Fast-forward できるか・push が通るかが自然に判断できるようになります。

0
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
0
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?