git fetch・git pull・git switchの違いを、ブランチ操作の流れで整理する
はじめに
Gitを使い始めた頃、次の3つのコマンドの違いが曖昧でした。
git fetch origin
git pull origin dev
git switch dev
-
fetchしたら、ローカルのファイルも最新になるのか -
pullとswitchは、どちらもブランチを取得する操作なのか - リモートブランチからローカルブランチを作るには、どれを使うのか
コマンドだけを個別に覚えると混乱しやすいため、この記事ではリモートの更新を取得し、ローカルブランチへ反映し、作業ブランチへ移動する流れに沿って整理します。
先に結論
| コマンド | 主な役割 | 現在のブランチ | 作業中のファイル |
|---|---|---|---|
git fetch |
リモートの最新情報を取得する | 切り替わらない | 変わらない |
git pull |
リモートの変更を取得し、現在のブランチへ取り込む | 基本的に切り替わらない | 変わる可能性がある |
git switch |
ローカルで作業するブランチを切り替える | 切り替わる | 切替先の内容に変わる |
一言で表すと、次のようになります。
fetch = 情報を取りに行く
pull = 取りに行って、現在のブランチへ反映する
switch = 作業するブランチを切り替える
前提:ローカルブランチとリモート追跡ブランチ
違いを理解するには、ローカルに存在する2種類の参照を知っておく必要があります。
たとえば、次の2つは別物です。
dev
origin/dev
-
dev- 自分がローカルで作業するブランチ
-
origin/dev- 最後に取得した、リモートの
devブランチの状態を表す参照
- 最後に取得した、リモートの
originは、多くの場合GitHub上のリポジトリにつけられたリモート名です。
git fetch originを実行すると、GitHub上の最新状態を取得し、origin/devなどのリモート追跡ブランチを更新します。しかし、ローカルのdevは自動では更新されません。
GitHubのdev
│
│ git fetch origin
▼
origin/dev ← 更新される
dev ← まだ更新されない
この区別が、fetchとpullを理解するポイントです。
git fetch:リモートの最新情報だけを取得する
git fetch origin
このコマンドは、originから最新のコミットやブランチ情報を取得し、リモート追跡ブランチを更新します。
実行しても、次のものはそのままです。
- 現在いるブランチ
- ローカルブランチの位置
- 作業中のファイル
つまり、fetchはリモートの状態を安全に確認するための取得操作です。
たとえば、自分のローカルdevがコミットB、GitHub上のdevがコミットDまで進んでいるとします。
ローカルdev: A---B
GitHubのdev: A---B---C---D
git fetch originを実行した後は、次の状態になります。
dev: A---B
origin/dev: A---B---C---D
ローカルdevはBのままですが、origin/devを見ることで、リモートがDまで進んでいると確認できます。
差分を確認する場合は、たとえば次のコマンドを使えます。
git log --oneline dev..origin/dev
これは、origin/devには存在するが、ローカルdevにはまだ存在しないコミットを表示します。
ファイルの差分を確認するなら、次のように実行できます。
git diff dev..origin/dev
削除されたリモートブランチも整理する
リモートで削除済みのブランチ情報をローカルから整理したい場合は、--pruneを付けます。
git fetch origin --prune
これにより、リモートにはもう存在しないorigin/xxxという参照が削除されます。ローカルの作業ブランチそのものが削除されるわけではありません。
pullで取得できるなら、なぜfetchを使うのか
ここまで読むと、次のように感じるかもしれません。
pullの中でもfetchが実行されるなら、最初からpullだけでよいのでは?
現在のブランチへリモートの変更をすぐ反映したいだけなら、pullだけで問題ない場合もあります。
ただし、pullは情報を取得するだけでなく、現在いるブランチへの統合まで続けて行います。そのため、次のような場面ではfetchを単独で使う意味があります。
- 作業中のファイルを変えずに、リモートの更新だけ確認したい
- 更新内容を
logやdiffで確認してから取り込みたい - リモートに新しいブランチが作られたか確認したい
- リモートで削除されたブランチ情報を
--pruneで整理したい - スクリプト内で、最新のリモート情報を基に存在確認や分岐を行いたい
たとえば、次の操作なら、確認するまではローカルdevも作業中のファイルも変わりません。
git fetch origin
git log --oneline dev..origin/dev
git diff dev..origin/dev
内容を確認して問題がなければ、次のように反映できます。
git switch dev
git merge --ff-only origin/dev
一方、確認せずそのまま更新してよい場合は、次の操作にまとめられます。
git switch dev
git pull --ff-only origin dev
つまり、使い分けの基準は次のとおりです。
fetch = まず情報だけ取得して、確認や判断をしたい
pull = 取得後、そのまま現在のブランチへ反映したい
git pull:取得して、現在のブランチへ取り込む
git pull
git pullは、大まかには次の2段階の処理です。
git fetch
+
取得した変更を現在のブランチへ統合
統合方法には、設定やオプションに応じてmergeやrebaseなどが使われます。
ローカルdevにいて、上流ブランチとしてorigin/devが設定されている場合、次のコマンドでorigin/devの変更をローカルdevへ取り込めます。
git switch dev
git pull
明示的にリモート名とブランチ名を指定する場合は、次のように書けます。
git pull origin dev
ただし、ここで重要なのは、git pull origin devがローカルのdevへ自動で移動するコマンドではないことです。
このコマンドは、リモートのdevを取得し、原則として現在いるブランチへ統合します。
そのため、別の作業ブランチにいる状態で実行すると、意図せずそのブランチへdevの変更を取り込む可能性があります。
安全のため、先に現在のブランチを確認します。
git branch --show-current
そして、対象ブランチへ移動してからpullします。
git switch dev
git pull --ff-only origin dev
--ff-onlyとは
git pull --ff-only origin dev
--ff-onlyは、一言で表すと次の指示です。
履歴を単純に前へ進められる場合だけ更新し、分岐していたら何も統合せず停止する
Fast-forward(早送り)できるのは、ローカル側に独自のコミットがなく、リモートだけが先へ進んでいる状態です。
更新前:
A---B ← dev
\
C---D ← origin/dev
この場合、ローカルdevの位置をBからDへ動かすだけで更新できます。
更新後:
A---B---C---D ← dev、origin/dev
履歴を組み合わせる必要がないため、git pull --ff-onlyは成功します。
一方、ローカルとリモートの両方に異なるコミットがある場合、履歴は分岐しています。
C ← dev
/
A---B
\
D ← origin/dev
この状態を一つにするには、マージまたはリベースが必要です。--ff-onlyを付けている場合、Gitはどちらも自動では行わず、エラーで停止します。
--ff-onlyを付けないpullでは、Gitの設定やオプションによってマージやリベースが行われる可能性があります。意図しない統合を避けたい場合に、--ff-onlyが安全装置になります。
ローカルとリモートの履歴が分岐している場合は、Gitが勝手に統合せずエラーで停止します。共有ブランチを意図せずマージする事故に気づきやすいため、devやmainを最新化するときに使いやすいオプションです。
なお、--ff-onlyは「リモートを強制的に正しい状態として上書きする」オプションではありません。Fast-forwardできない理由を確認し、人がマージ、リベース、不要なローカルコミットへの対応などを判断するために、いったん止めるオプションです。
git switch:作業するローカルブランチを切り替える
git switch dev
git switchは、作業対象のローカルブランチを切り替えるコマンドです。
切り替えると、通常は次のものが変わります。
- 現在のブランチ
-
HEADが指す位置 - 作業ディレクトリのファイル
一方で、git switch自体には、リモートから最新情報を取得する役割はありません。
git switch dev
を実行しても、ローカルにすでに存在するdevへ移動するだけです。そのdevが古ければ、移動後も古いままです。
最新状態にするには、移動後に別途pullします。
git switch dev
git pull --ff-only
新しいローカルブランチを作成して切り替える
現在のコミットから新しいブランチを作成する場合は、-cを使います。
git switch -c feature/add-login
これは、次の2つをまとめて行います。
ローカルブランチを作成する
+
作成したブランチへ切り替える
リモートブランチからローカルブランチを作る
リモートにだけ存在するブランチから、対応するローカルブランチを明示的に作る場合は、次のように実行できます。
git fetch origin
git switch -c feature/add-login --track origin/feature/add-login
この操作によって、
- ローカルに
feature/add-loginを作る -
origin/feature/add-loginを開始地点にする - リモート追跡ブランチとして設定する
- 作成したブランチへ切り替える
という処理が行われます。
Gitの状態によっては、次の短いコマンドだけで、同名のリモートブランチを基にローカルブランチが自動作成されることもあります。
git switch feature/add-login
ただし、複数のリモートに同名ブランチがある場合などは曖昧になります。何を基に作成するか明確にしたい場合は、--trackまで書くと分かりやすくなります。
ブランチ操作の基本的な流れ
1. devを最新化して、新しい作業ブランチを作る
新しい作業を始めるときの基本的な流れです。
# 未コミットの変更がないか確認
git status
# ベースブランチへ移動
git switch dev
# リモートのdevを安全に取り込む
git pull --ff-only origin dev
# 最新のdevから作業ブランチを作成して移動
git switch -c feature/add-login
流れを整理すると、次のようになります。
devへ移動
↓
devを最新化
↓
最新のdevから作業ブランチを作成
↓
作業ブランチへ移動
2. まず変更内容を確認してからdevを更新する
いきなり作業ファイルを変えたくない場合は、先にfetchします。
git switch dev
git fetch origin
git log --oneline dev..origin/dev
git diff dev..origin/dev
git merge --ff-only origin/dev
この方法では、次のように操作を分けられます。
fetchで情報を取得
↓
logやdiffで確認
↓
mergeでローカルdevへ反映
pullは便利ですが、fetchと統合を分けると、何が起きるかを確認してから反映できます。
3. リモートにある作業ブランチで作業を再開する
ローカルに対象ブランチがまだない場合の流れです。
git fetch origin
git switch -c feature/add-login --track origin/feature/add-login
すでにローカルブランチが存在する場合は、切り替えてから更新します。
git switch feature/add-login
git pull --ff-only
よくある勘違い
git fetchすれば、ローカルのファイルも最新になる
なりません。
fetchで更新されるのは、主にorigin/devなどのリモート追跡ブランチです。ローカルdevや作業中のファイルへ反映するには、merge、rebase、pullなどの操作が別途必要です。
git switch devすれば、最新のdevになる
なりません。
switchは、ローカルにあるdevへ移動するだけです。リモートの最新状態を取り込むには、fetchやpullが必要です。
git pull origin devは、devへ移動して更新する
違います。
pullはブランチを切り替えません。別ブランチにいる状態で実行すると、取得したorigin/devの内容を現在のブランチへ統合する可能性があります。
git switchとgit checkoutは同じ
ブランチ切り替えに関しては似ていますが、git checkoutはブランチ操作とファイル復元の両方を担当する多機能なコマンドです。
現在は用途を分けて、次のコマンドを使えます。
ブランチの切り替え:git switch
ファイルの復元: git restore
役割が明確なので、ブランチ操作ではgit switchのほうが意図を読み取りやすくなります。
未コミットの変更がある状態でswitchするとどうなるか
未コミットの変更は、まだ特定のブランチのコミットとして保存されていません。そのため、変更内容が切替先でも適用できる場合、git switch後も作業ディレクトリに残ることがあります。
一方、切替先の内容と衝突する場合は、Gitが切り替えを中断します。
誤って別ブランチへ変更を持ち込まないよう、切り替える前に確認します。
git status
作業途中なら、状況に応じて次のいずれかを行います。
- コミットする
-
git stashで一時退避する - 不要な変更であれば内容を確認してから取り消す
ブランチを切り替える前にgit statusを見る習慣をつけると、事故を減らせます。
使い分けの判断
リモートの変更を確認したいだけ
git fetch origin
作業中のファイルを変えず、リモートの最新情報だけを取得できます。
現在のブランチをリモートに合わせて更新したい
git pull --ff-only
ただし、先に現在のブランチと作業状態を確認します。
git status
git branch --show-current
別のローカルブランチへ移動したい
git switch <branch-name>
新しいローカルブランチを作って移動したい
git switch -c <new-branch-name>
リモートブランチを基にローカルブランチを作りたい
git fetch origin
git switch -c <branch-name> --track origin/<branch-name>
まとめ
3つのコマンドの役割は、次のように整理できます。
| やりたいこと | コマンド |
|---|---|
| リモートの最新情報を取得する | git fetch origin |
| 現在のブランチへリモートの変更を取り込む | git pull --ff-only |
| 別のローカルブランチへ移動する | git switch <branch-name> |
| 新しいローカルブランチを作って移動する | git switch -c <branch-name> |
| リモートブランチからローカルブランチを作る | git switch -c <branch-name> --track origin/<branch-name> |
特に覚えておきたいのは、次の3点です。
-
fetchは、ローカルブランチや作業ファイルを更新しない -
pullは、現在いるブランチへ変更を取り込む -
switchは、ブランチを切り替えるが最新化はしない
コマンドを単独で暗記するよりも、
リモートの情報を取得する
↓
必要ならローカルブランチへ反映する
↓
作業するブランチを切り替える
という流れで考えると、使い分けやすくなります。