git fetch解説

以下は社内向け勉強会のLT枠で話した内容をベースにして編集増補したものである。増補分があるので、この内容を5分で話したわけではない。

## git fetchコマンドの概要

git fetchはリモートリポジトリからブランチやタグ、またそれらの履歴を完成させるに必要な[オブジェクト](https://qiita.com/yunano/items/49cee64b98e242458a15#git-pushコマンドの概要)を取得するためのコマンドである。

git fetchで取得したブランチは通常[リモート追跡ブランチ](https://qiita.com/yunano/items/49cee64b98e242458a15#リモート追跡ブランチ)に反映される。それをローカルブランチへと持ってくるのはgit mergeやgit rebase等の仕事であるが、git pullコマンドがgit fetchの後に一緒にgit mergeあるいはgit rebaseを実行してくれる機能を持つ。

## git fetchコマンドの形

git fetchコマンドは通常、`git fetch [オプション] [<リポジトリ名> [<refspec>...]]`という形を取る。このリポジトリ名は[git remoteコマンド等で設定されたリモートリポジトリ](https://qiita.com/yunano/items/49cee64b98e242458a15#リモートリポジトリ設定)の名称である。

リポジトリ名が省略された場合は現在作業中のブランチ（HEADが指しているブランチ）の[upstream](https://qiita.com/yunano/items/49cee64b98e242458a15#引数なくgit-pushだけを実行した時の動作)が指すリモートリポジトリ（upstreamが設定されていなければorigin）、refspecが省略された場合はgit configで設定可能なremote.<リポジトリ名>.fetchの値（これもrefspecと同じ書式で指定する）が使用される。ただし、remote.<リポジトリ名>.fetchの値が現在作業中のブランチを対象としていなくてもそのupstreamの元であるリモートブランチ（upstreamが設定されていなければoriginリポジトリのHEAD）からだけはフェッチされる。この場合、送信先となるリモート追跡ブランチが指定されていないのでリモート追跡ブランチは更新されず、後述するFETCH_HEADには情報が記載される。

なお、remote.<リポジトリ名>.fetchの値は[git clone時に自動で設定され](https://qiita.com/yunano/items/29e40b54ffae2b218d51#単一のブランチだけを取得)、git cloneの-oあるいは--originオプションを使用していなければリポジトリ名はoriginとなっている。

### git fetchのrefspec

refspecは[git pushでも指定できた](https://qiita.com/yunano/items/49cee64b98e242458a15#refspec)がgit fetchに当てはめると以下のようになる。

* オプションとして、フェッチが強制的に（[fast-forward](https://qiita.com/yunano/items/9f48fedca0a58f3befd1#fast-forward-mergeとtrue-merge)でなくても）送信先に対して行われるかどうかを示す「+」
* リモート側の送信元。「refs/」で始まる[リファレンス](https://qiita.com/yunano/items/f3133ea64efed762df2f#リファレンス)の位置を表す文字列で指定するか[commit-ish](https://qiita.com/yunano/items/f3133ea64efed762df2f#commit-ish)で指定する
* 「:」
* 送信先となるローカルのリファレンス。通常はリモート追跡ブランチを「refs/remotes/<リポジトリ名>/<ブランチ名>」の形で指定する

git pushと同様ディレクトリとサブディレクトリにあるファイルすべてを表す「\*」を使用することができる。その場合それらの対象がすべて取得対象となる。送信元に「\*」を使ったならば送信先も「\*」を使い`refs/remotes/<リポジトリ名>/*`等と指定する必要がある。

「\*」を使った指定の例としてはオプションなしでgit cloneを実行した時のremote.origin.fetchの値である`+refs/heads/*:refs/remotes/origin/*`がある。送信元の「refs/heads/\*」はリモートのすべてのブランチを表す。これが指定されているために、オプションなしでgit cloneを実行した後の`git fetch`はoriginにあるすべてのブランチから取得を行うことになる。

なお、git fetchはデフォルトで（設定が変わっていたとしてもオプションで）すべてのタグは取得される。そのため「\*」を使う場合にタグを対象にしてもあまり幸せなことにならないだろうから、「\*」を使う際の送信元の指定は通常`refs/heads/*`となるだろう。

送信先を省略する場合、その前の「:」も一緒に省略するのはgit pushと同じである。送信先が指定された場合はremote.<リポジトリ名>.fetchに書かれた送信先と、ここで指定された送信先との両方が更新される。逆に送信先が省略され、remote.<リポジトリ名>.fetchの値も送信先がないなら更新が行われない。

refspecは複数指定できるが、それぞれの前に「^」を指定するとその送信元を取得対象外とすることができる。「\*」を使用しているが一部取得対象にしたくないブランチがある場合に有用である。「^」を付けた場合は送信先（その前の「:」も含む）を指定するとエラーになる。

git fetchに-fあるいは--forceオプションを付けると指定したすべてのrefspecに「+」が付いたのと同じ効果があることもgit pushと同じである。

### FETCH_HEAD

送信先の更新とは別に、git fetchによって取得したそれぞれの先頭コミットのオブジェクトIDやブランチの名前といった情報が.git/FETCH_HEADにブランチ1つにつき1行で記録される。

以下は現在作業中のブランチ（HEADが指しているブランチ）がb1である時に`git fetch`を実行した後の.git/FETCH_HEADの内容の例である。

```text
07fb22cfb1354c9219cd8b9012475e972d747fbb                branch 'b1' of github.com:___-repos/repo1
ea417fb9aa230e169e4deb7750e6827c503481d8        not-for-merge   branch 'b2' of github.com:___-repos/repo1
0903772951cd3274ea6aa9c44761895b2928f727        not-for-merge   branch 'b3' of github.com:___-repos/repo1
24ad3d904809d2a0c7e32b95ad953204f8ab65cb        not-for-merge   branch 'main' of github.com:___-repos/repo1
```

`git merge FETCH_HEAD`や`git rebase FETCH_HEAD`は.git/FETCH_HEADの現在作業中のブランチと同じ名前のブランチの情報を元に取り込みを行う。git pullはgit fetchの後にgit mergeあるいはgit rebaseを実行してくれることは述べたが、実際のところgit fetchをした後に行っているのは`git merge FETCH_HEAD`あるいは`git rebase FETCH_HEAD`である。

ただし「not-for-merge」フラグがある行は`git merge FETCH_HEAD`や`git rebase FETCH_HEAD`による取り込みの対象にはならない。「not-for-merge」フラグはgit fetchの実行時にマージする対象でないと判定されたものに付けられる。例えばrefspecが省略されていた場合に現在作業中のブランチの[upstreamとして設定されたリモート追跡ブランチ](https://qiita.com/yunano/items/49cee64b98e242458a15#引数なくgit-pushだけを実行した時の動作)の変更に使われた行以外は「not-for-merge」となる。

つまり、上記の例の.git/FETCH_HEADの内容だと現在作業中のブランチがb1の時にのみ`git merge FETCH_HEAD`や`git rebase FETCH_HEAD`によって取り込みが行われる。

.git/FETCH_HEADはgit fetchコマンドやgit pullコマンドを実行する度に上書きされる。ただし、-aあるいは--appendオプションを使うと上書きではなく追記するようにすることもできる。.git/FETCH_HEADへまったく記録しないようにする--no-write-fetch-headというオプションもある。

## git fetchの主なオプション

以下ではここまでに紹介していない主なオプションについて説明する。

* --all

リポジトリに設定されたすべてのリモートリポジトリからフェッチを行う。勘違いされやすいが、すべてのブランチをフェッチするためのオプションではない（フェッチされるブランチは前述の通りコマンドに指定したrefspecやremote.<リポジトリ名>.fetchの値によって決定される）。このオプションを使用した時は（リポジトリ名と）refspecを指定できないので、refspecを省略した時と同様remote.<リポジトリ名>.fetchの値が使用される。

* -n, --no-tags

git configで設定可能なremote.<リポジトリ名>.tagOptの値が未設定か--tagsの場合はタグもフェッチされるが、このオプションを指定することでタグをフェッチされないようにする。一方、remote.<リポジトリ名>.tagOptの値が--no-tagsの場合タグがフェッチされないが、その時にタグをフェッチするようにする-tあるいは--tagsオプションもある。

* -p, --prune

refspec（git configで設定可能なremote.<リポジトリ名>.fetchも含む）が「\*」を使って指定されており、かつフェッチ対象内にリモート側に存在しなくなったブランチがある場合、その送信先であるリモート追跡ブランチを削除する。加えて-Pあるいは--prune-tagsオプションも指定するとリモート側に存在しないタグも削除することができるが、-Pはどこから来たタグかを気にせずそのリモートになければ全部消すので注意しなければならない。

* --refmap

`--refmap=[<送信先となるローカルのリファレンス>]`というように使用し、remote.<リポジトリ名>.fetchのrefspecの送信先となるローカルのリファレンスの値を上書きする。git fetchコマンドの引数のrefspecにも送信先が書かれていれば--refmapオプションの値との両方が送信先となる。なお、git fetchコマンドの引数のrefspecが省略された場合に--refmapオプションを使用するとエラーとなる。

--refmap=とだけ指定、つまり送信先となるローカルのリファレンスを指定しない場合はremote.<リポジトリ名>.fetchのrefspecの送信先は--refmapによって空に上書きされるため、git fetchコマンドの引数に指定されたrefspecの送信先だけに送信されることになる。

* --set-upstream

フェッチ完了後、現在作業中のブランチに対して送信先のリモート追跡ブランチをupstreamとして記録する。これはコマンドのrefspecが1つだけ指定されており、それには送信先が指定されておらず、remote.<リポジトリ名>.fetchには送信先が指定されている場合にのみ働く。

* --recurse-submodules

--recurse-submodulesの後に「=」を挟んでyes, no, on-demandの3種類のサブオプションを指定しても良い。サブオプションのない--recurse-submodulesと、--recurse-submodules=yesは同じで、サブモジュールも一緒にフェッチを行う。この場合、フェッチ元のリモートリポジトリがサブモジュールの更新に追随していなかったとしても、サブモジュール自体のリモートリポジトリから取得する。

--recurse-submodules=noとする（あるいは--no-recurse-submodulesオプションを使用する）とサブモジュールはフェッチしない。

--recurse-submodules=on-demandとするとフェッチ元のリモートリポジトリがサブモジュールの更新に追随していればフェッチする。

--recurse-submodulesを指定していない場合は--recurse-submodules=on-demandと同じである。ただし--recurse-submodules無指定時にgit configで設定可能なfetch.recurseSubmodulesの値が指定されている場合はその値に従う。fetch.recurseSubmodulesもyes, no, on-demandの3種類の値が指定できる。

* -j <並列数>, --jobs <並列数>

フェッチの作業を指定した数までの並列で行う。このオプションはリポジトリ1つだけをフェッチする場合には作業を分割できないため意味がなく、フェッチされるリポジトリが複数あるかサブモジュールもフェッチするかの場合に並列化してくれるものである。

### シャロークローンに関するオプション

git cloneと同様、--depth, --shallow-since, --shallow-excludeの各オプションで指定したコミットだけを取得して[シャロークローン状態](https://qiita.com/yunano/items/29e40b54ffae2b218d51#シャロークローン)とすることができる。これらのオプションによってこれまでに取得していたのと比べてより少ない量のコミットを取得するように指定した場合には新たに対象外となったコミットは履歴上見えなくなる。従って、実行前がシャロークローン状態であってもなくても、実行後に新たに指定したコミットだけのシャロークローン状態に変更することができる。

逆に、--unshallowオプションによってシャロークローン状態を解除することができる。