1. ホーム
  2. Git

【Git】git fetch と git pull の違い|リモートの変更を安全に取り込む方法

Share

チームで開発していると、ほかの人が push した変更を自分の手元に取り込む場面が毎日のようにあります。そのときに使うのが git fetch と git pull ですが、「どちらも最新にするコマンドでしょう?」と区別があいまいなまま使っている方も多いのではないでしょうか。この記事では、2つのコマンドが内部で何をしているのかを origin/main という「リモート追跡ブランチ」の仕組みから説明し、fetch で確認してから取り込む手順、pull の動きを決める設定、そして pull したときに出るエラーへの対処までを解説します。

fetch は「取ってくるだけ」、pull は「取ってきて統合する」

最初に結論を書いておきます。git fetch はリモートリポジトリ(GitHub など)から新しいコミットをダウンロードするだけのコマンドで、自分が作業しているブランチやファイルには一切手を触れません。一方の git pull は、fetch を実行したあとに続けて git merge(設定によっては git rebase)を行い、取ってきた変更をいまのブランチに統合するところまでを一気に済ませます。

コマンドリモートから取得リモート追跡ブランチ(origin/main)手元のブランチ・ファイル
git fetchする更新される変わらない
git pullする更新されるmerge(または rebase)されて変わる

この表の「リモート追跡ブランチ」が、2つのコマンドの違いを理解するカギになります。

origin/main(リモート追跡ブランチ)の仕組み

リポジトリを clone すると、手元には main というブランチのほかに、origin/main という名前の参照が作られます。origin は clone 元のリモートにつけられる既定の名前で、origin/main は「最後に通信したとき、リモートの main はこのコミットを指していた」という記録です。これをリモート追跡ブランチ(remote-tracking branch)と呼びます。

リモート追跡ブランチは git branch -r で確認できます。

ターミナル
$ git branch -r
  origin/HEAD -> origin/main
  origin/main
  origin/feature/login

ポイントは、origin/main が自動では更新されないことです。GitHub 上で誰かが main に push しても、手元の origin/main は古いコミットを指したまま。リモートの最新状態を手元の記録に反映させる唯一の手段が git fetch です(git pull も内部で fetch を呼んでいます)。また、origin/main は自分で直接コミットするためのブランチではなく、あくまで「リモートの写し」として読み取り専用で扱います。

git status の「Your branch is up to date with ‘origin/main’」という表示も、比べている相手は手元の origin/main です。fetch していなければ、リモートが先に進んでいても「up to date」と表示されるので注意してください。

git fetch で変更を確認してから取り込む流れ

fetch を使うと、「リモートで何が変わったのかを見てから、自分のブランチに取り込む」という慎重な進め方ができます。手順を順番に見ていきましょう。

1. git fetch でリモートの最新を取得する

ターミナル
$ git fetch
From github.com:example/myapp
   fbdccde..46536cd  main       -> origin/main

出力の fbdccde..46536cd main -> origin/main は、「リモートの main を取ってきて、手元の origin/main を fbdccde から 46536cd に進めた」という意味です。引数を省略すると、現在のブランチに設定されたリモート(通常は origin)から取得します。すべてのリモートから取りたいときは git fetch --all を使います。

この時点で git status を見ると、手元の main が遅れていることが分かるようになります。

ターミナル
$ git status
On branch main
Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)

2. git log と git diff で中身を確認する

取り込む前に、どんなコミットが来ているのかを確認します。main..origin/main は「origin/main にはあって main にはないコミット」を表す範囲指定です。

ターミナル
# リモートで増えたコミットの一覧
$ git log --oneline main..origin/main
46536cd fix header

# 変更されたファイルと行数の概要
$ git diff --stat main origin/main
 header.php | 1 +
 1 file changed, 1 insertion(+)

--stat を外せば、行単位の差分がそのまま表示されます。逆に git log origin/main..main と書けば、「自分だけが持っていて、まだ push していないコミット」を確認できます。

3. git merge で手元のブランチに取り込む

内容に問題がなければ、origin/main を手元の main にマージします。

ターミナル
$ git merge origin/main
Updating fbdccde..46536cd
Fast-forward
 header.php | 1 +
 1 file changed, 1 insertion(+)

Fast-forward(早送り)は、手元の main に独自のコミットがなく、ブランチの位置を origin/main まで進めるだけで済んだことを示します。この場合はマージコミットは作られません。手元にも独自のコミットがあるときは、両方を合わせるマージコミットが作られます。

このように fetch → 確認 → merge と分けて実行すれば、「pull したら知らない変更が混ざってコンフリクトした」という事態を事前に察知できます。

git pull は fetch と merge をまとめたもの

ここまでの手順1と手順3を1回で行うのが git pull です。次の2つは、基本的に同じ結果になります。

ターミナル
# pull を使う場合
git pull

# 分けて実行する場合
git fetch
git merge origin/main

確認の手間が要らない分、pull は手軽です。自分しか触っていないブランチや、変更内容がだいたい分かっている場面では pull で十分でしょう。一方で、何が入ってくるか分からないときや、大きな変更が予想されるときは、fetch で中身を見てから取り込むほうが安心です。

–rebase を付けると merge の代わりに rebase する

手元にもリモートにも新しいコミットがある状態で普通に pull すると、両者をまとめるマージコミットができます。これを避けて履歴を一直線にしたいときは --rebase を付けます。

ターミナル
$ git pull --rebase
Successfully rebased and updated refs/heads/main.

$ git log --oneline --graph
* 0d4b1c1 add footer
* 46536cd fix header
* fbdccde first

rebase は「自分のコミットをいったん外し、リモートの最新コミットの後ろに付け直す」操作です。上の例では、自分の add footer がリモートの fix header の後ろに並び直しています。付け直されたコミットはハッシュが変わるので、すでに push 済みのコミットを含む状態で rebase するのは避けてください。pull –rebase で付け直されるのは「まだ push していない自分のコミット」だけなので、通常の使い方であれば問題になりません。

–ff-only は早送りできるときだけ取り込む

git pull --ff-only は、手元に独自のコミットがなく早送りで済む場合だけ更新し、そうでなければ何もせずにエラーで止まります。「main では自分でコミットしない」という運用なら、意図しないマージコミットを防ぐ安全装置になります。

pull.rebase と pull.ff で pull の動きを決めておく

毎回オプションを付けるのは面倒なので、git config で pull の既定の動きを決めておくのが一般的です。

設定pull の動き
pull.rebase falsemerge で統合する(オプションなしの pull と同じ)
pull.rebase truerebase で統合する(--rebase と同じ)
pull.ff only早送りできるときだけ更新し、できなければ中止する(--ff-only と同じ)
ターミナル
# すべてのリポジトリで pull を rebase にする
git config --global pull.rebase true

# このリポジトリだけ、早送りできるときしか pull しない
git config pull.ff only

--global を付けるとユーザー全体(~/.gitconfig)に、付けなければそのリポジトリの .git/config に保存されます。設定していても、git pull --no-rebase や git pull --rebase のようにコマンドラインでオプションを指定すれば、その回だけ上書きできます。

git fetch –prune で消えたブランチを掃除する

GitHub でプルリクエストをマージしてブランチを削除しても、手元の origin/feature/login のようなリモート追跡ブランチは残り続けます。普通の fetch は「新しいものを取ってくる」だけで、消えたものは消さないからです。--prune(短縮形は -p)を付けると、リモートに存在しなくなったリモート追跡ブランチを削除してくれます。

ターミナル
$ git fetch --prune
From github.com:example/myapp
 - [deleted]         (none)     -> origin/feature/login

消えるのは origin/feature/login という「リモートの写し」だけで、自分で作ったローカルの feature/login ブランチは残ります。不要なら git branch -d feature/login で別途削除してください。毎回付けるのが面倒なら、git config --global fetch.prune true を設定しておくと、fetch(と pull)のたびに自動で掃除されます。

git pull でエラーになるときの対処

Need to specify how to reconcile divergent branches と表示される

手元とリモートの両方に新しいコミットがある(ブランチが分岐している)状態で、pull の動きを何も設定していないと、次のメッセージで止まります。

ターミナル
$ git pull
hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint:
hint:   git config pull.rebase false  # merge
hint:   git config pull.rebase true   # rebase
hint:   git config pull.ff only       # fast-forward only
hint:
...
fatal: Need to specify how to reconcile divergent branches.

これは「merge と rebase のどちらで合わせればいいか決めてください」という確認で、壊れているわけではありません。fetch 自体は完了しているので、origin/main はすでに最新です。今回だけ処理したいなら git pull --no-rebase(merge)か git pull --rebase を実行し、今後も同じにしたいならメッセージにあるとおり pull.rebase を設定します。どちらを選ぶかはチームの方針に合わせるのが一番です。

Not possible to fast-forward, aborting と表示される

pull.ff only を設定している(または --ff-only を付けた)状態で、ブランチが分岐していると出るエラーです。

ターミナル
$ git pull
hint: Diverging branches can't be fast-forwarded, you need to either:
hint:
hint: 	git merge --no-ff
hint:
hint: or:
hint:
hint: 	git rebase
...
fatal: Not possible to fast-forward, aborting.

設定どおりに「早送りできないので何もしなかった」という結果です。git log --oneline origin/main..main で自分側にどんなコミットがあるかを確認し、それが意図したものなら git rebase origin/main か git merge origin/main で明示的に統合します。main にうっかりコミットしてしまっていた場合は、そのコミットを別ブランチに移すなど、整理してから取り込み直しましょう。

未コミットの変更があって pull できない

作業中のファイルと、リモートから来た変更が同じファイルに触れていると、Your local changes to the following files would be overwritten by merge のようなメッセージで止まります。Git は未コミットの変更を上書きしないように保護しているためです。いったんコミットするか、git stash で退避してから pull し、終わったら git stash pop で戻します。

まとめ

git fetch はリモートの最新コミットを取ってきて origin/main などのリモート追跡ブランチを更新するだけのコマンドで、手元のブランチや作業中のファイルは変わりません。git pull はそこに merge(または rebase)を加え、取ってきた変更をいまのブランチへ統合するところまで行います。慎重に進めたいときは、fetch のあとに git log main..origin/main や git diff main origin/main で中身を確認してから git merge origin/main すると安心です。

pull の統合方法は pull.rebase や pull.ff only で決めておくと、分岐時のメッセージで止まることがなくなります。あわせて fetch.prune を有効にしておけば、マージ済みで消えたリモートブランチの写しも自動で片付きます。fetch と pull の役割を分けて考えられるようになると、リモートとのやりとりで迷うことがぐっと減るはずです。

参考ページ