fetchはリモートの「情報」を取ってくるだけ、
pullは情報を取ってきて、さらに実データをローカルに反映する。
という理解でした。
でも、fetchで何やら情報を取得している様子なのに、その後行ったpullでは何も情報が取得されない。
ということが有ったので、fetchって一体何をしているの?ということで調べてみた。
まずはブランチについて
ブランチの種類
ローカルブランチ
ローカルで開発するときにcheckoutで開いてコミットしていくお馴染みのもの。
master
develop
feature/topic
ローカルで作成したブランチは、pushすることでリモート追跡ブランチにも反映される。
リモート追跡ブランチ
ローカルに保持している、クローン元リポジトリのブランチを取り込んだもの。
反映はfetchやpullコマンドを実行したタイミングであり自動取得はしないので、
いつの間にかリモートリポジトリ側と不一致になっていたということも有る。
git branch -a
とすると、
remotes/origin/master
remotes/origin/develop
remotes/origin/feature/topic
と表示されるもので、
コマンドのパラメータには、
origin/master
origin/develop
origin/feature/topic
と書く。
ローカルブランチとしてcheckoutするには、
origin/を外して、
git checkout feature/topic
とする。この時、ローカルブランチとリモート追跡ブランチのorigin/feature/topicが紐づけられる。
なお、リモート追跡ブランチは、あくまでも「リモートを"追跡する"ブランチ」なので、これをローカルでcheckoutしてコミットすることは出来ない。
git checkout origin/feature/topic
は不可。
リモートのブランチ
クローン元リポジトリにおけるローカルブランチ。
間接的にリモート追跡ブランチを通じてしか知り得ない。
リモートから取得する
手順としては、
- リモートの情報を取得する
- ローカルブランチにマージする
を行います。
この時、fetch, pull, merge, rebaseなどのコマンドを使います。
- fetchコマンドでリモートの情報を取得する
fetchコマンドは、前回取得して以降に更新された、全ブランチの全コミットデータを取ってきます。
この時、
「リモート追跡ブランチ」のみに反映します。ローカルブランチは変更しません。
コミットは、ローカルとリモート両方を共存する形で保持します。ローカルのコミットは変更しません。
なので、fetchではローカルのブランチとコミットには影響しない、ということになります。
- pullコマンド
fetch + mergeの動作を行います。
fetchで全ブランチの全コミットデータを取得してから、checkout中のカレントブランチに、対応するリモート追跡ブランチをマージします。
パターン別のリモートからの取得方法
具体的にどういう動きをしているかパターン別に見てみましょう。
必要に応じて、
[Git] 脱初級!誤コミットはもう怖くない
の「ブランチは枝では無い」辺りも参考にして下さい。
ローカルに無いリモートブランチ
リモートで作成されたブランチを初めて取得してくる時の動きです。
fetchで情報を取得する
- リモートブランチ
リモートにdevelopブランチが新規に作成され、コミットA,B,Cが存在する状態。
git fetch
- fetch後のローカル環境
のようにコミットA,B,Cとブランチ「origin/develop」がローカルに作成されます。
ただし、作成されるのは「リモート追跡ブランチ」(origin/develop)のみで、ローカルブランチは作成されません。
- ローカルブランチの作成
ブランチ名から origin/ を外した、
git checkout develop
でローカルブランチが作成されます。
参考) リモート追跡ブランチに無いブランチをローカルで作成する場合は、branchコマンドを使う必要が有ります。
ローカルでコミット無し、リモートでコミットされていた場合
fetchでリモートの情報を取得する
- リモートブランチ
- fetch前のローカルブランチ
リモート追跡ブランチ origin/develop は、前回fetchで取得した時の状態。
ここで、fetchを実行すると。
git fetch
- fetch後のローカルブランチ
のように、ローカルブランチにリモートのコミットCが継ぎ足されて、
リモート追跡ブランチorigin/developが更新されます。
ローカルブランチdevelopは変更されません。
注意点として、この状態からローカルブランチdevelopにコミットしてしまうと、
のように枝分かれしてしまいます。
対処法としては、fetchしたら速やかにマージするというところでしょうか。
そういう意味では、fetchとmergeがセットになっているpullを使うのがいいのかもしれません。
ローカルブランチにマージする
fetchにより以下図の状態になっています。
リモートのコミットをそのまま受け入れるだけでいいので、いわゆるFast-Forwardマージのケースです。
動作としては、developブランチがorigin/developのところに移動するだけです。
- merge コマンド
git checkout develop
git merge origin/develop
- pull コマンド
fetch + mergeと同じ動作です。
git checkout develop
git pull
pullの引数無しの場合、カレントブランチのみが対象となります。
- rebase コマンド
git checkout develop
git rebase origin/develop
(オプション指定によっては変わってきますが。とりあえず標準動作として)
ローカルでコミット有り、リモートでコミット無しの場合
リモートに更新情報が無いのでfetchやpullを行っても何も変化は有りません。
むしろ、Dコミットをリモートに反映させる必要が有ります。
ローカルとリモート両方でコミット有りの場合
fetchでリモートの情報を取得する
- リモートブランチ
- fetch前のローカルブランチ
前回fetchをしたコミットBから新しくDがコミットされている状態
git fetch
以下図のように、リモートのコミットCを別の枝として取り込むので、
ローカルブランチには影響が出ません。
- fetch後のローカルブランチ
ローカルブランチにマージする
developにorigin/developを取り込みます。
ある程度はGitが自動マージしてくれるのですが、同じファイルの同じ場所を編集していた場合は、
手で編集する必要が有ります。
- merge コマンド
git checkout develop
git merge origin/develop
ローカルにマージコミットEが新しく作られdevelopブランチが更新されます。
リモートより先に進むので、リモートに反映させる(pushコマンド)必要が有ります。
- pull コマンド
git checkout develop
git pull
fetch + mergeコマンドと動作、結果は同じです。
- rebase コマンド
origin/developが指すコミットCの後ろにdevelopブランチのコミットDを移動します。
git checkout develop
git rebase origin/develop
リモートへの反映
originよりローカルブランチの方がコミットが進んでいるケースです。
- ローカルブランチ
前回fetchしたコミットBから、ローカルに新しくCがコミットされた状態
pushでカレントブランチのコミットをリモートに反映します。
git checkout develop
git push
pushは、checkoutで開いたブランチのみをリモートに反映します。
(注意:gitのversion 1.x系は、リモートと紐づいている全てのブランチをリモートに反映するのがデフォルト動作なのでご注意下さい。必要に応じて、ブランチ名を指定するかオプションでデフォルト動作を変える必要が有ります)
これにより、リモートリポジトリ及び自リポジトリ内のリモート追跡ブランチorigin/developに、
ローカルブランチdevelopが反映されます。
あと、ローカルでブランチを新規作成してコミットしたものをリモートに反映する場合も同じです。
まとめ
- fetchは全ブランチの全コミットの情報を取ってきている
- fetchはローカルコミットとローカルブランチを変更しない
- pullはfetch+カレントブランチのみマージする動作
- pushもカレントブランチのみ対象