【git】checkoutせずに特定ブランチをpullする

# 要約
`git pull`したいブランチを`checkout`せずに、その場で`pull`と同じ動きをさせるコマンド。

```bash:bash
# リモートoriginからdevelopブランチをpull
git fetch
git fetch . origin/develop:develop
```
ただし、ファストフォワード可能な場合に限る。また、現在のブランチの場合は動作しないので、その場合はおとなしく`pull`を使う。
追記：[エイリアス版](https://qiita.com/YamEiR/items/630fcdd3c4c1984fdc2a)ができた

# 解説
`git pull`コマンドは基本的に`HEAD`の指すブランチ(要するに現在のブランチ)に対してのみ実行できる。例外として`git pull --all`を用いて全てのリモートから全ての追跡ブランチを一括`pull`することは出来るが、ブランチ数が増えてくると中々使いづらい。
という訳で、通常は`git checkout <branch name>`で`HEAD`を切り替えた後に`pull`するが、`checkout`はそこそこ「重い」処理であるため回避したい場合がある。あった。

ところで`git fetch`コマンドは[リモートブランチの内容をトラッキングブランチに反映させる](https://qiita.com/Teloo/items/95a860ae276b49edb040)ものらしい。一般的にはネットワーク上からローカルにダウンロードしてくるイメージで良いと思うが、[公式ドキュメント](https://git-scm.com/docs/git-fetch)に "git-fetch - Download objects and refs from another repository" とある通り、コミットのオブジェクトと同時に参照の指す先を更新するという機能も持ち合わせている。
今回利用するfetchの構文は`git fetch [<repository> [<refspec>…​]]` のものであり、`repository`に対象のgitリポジトリ、`refspec`(参照仕様)に「書き込み先/書き込み元のパス」を渡す。

```bash:.gitconfig
#公式ドキュメントより引用(https://git-scm.com/book/ja/v1/Git%E3%81%AE%E5%86%85%E5%81%B4-%E5%8F%82%E7%85%A7%E4%BB%95%E6%A7%98%EF%BC%88Refspec%EF%BC%89)
[remote "origin"]
       url = git@github.com:schacon/simplegit-progit.git
       fetch = +refs/heads/*:refs/remotes/origin/*
```

`.gitconfig`を見ると大抵このような表記が(自動的に)生成されていると思うが、ここの`fetch = `以降が`git fetch`におけるデフォルトの`refspec` (参照仕様)を示す。『参照仕様はコロン（:）で分割した < src >:< dst > の形式』とのことなので、つまりはローカルの`refs/remotes/origin` 以下の参照をリモートの`refs/heads`以下の参照で上書きすることを意味する。ここで「デフォルトの `refspec` 」と書いたとおり、`refspec`は引数としてコマンドの末尾に追加すれば明示的に指定できる。ただしこの時リポジトリ名(URLやパスないし`origin`などのエイリアス)が必要になるが、 `.`  でも通る(カレントディレクトリのパスとして解釈されている)。

今回の記事で実行したいのは特定ブランチの`pull`だが、これはローカルブランチの参照とトラッキングブランチ(およびその時点でのリモートブランチ)の参照が同じコミットを指せば良い。
ここで、「ファストフォワード可能」とは(厳密さを求めず端的に言えば)ローカルブランチのコミットがトラッキングブランチに全て含まれている状態である。そして、トラッキングブランチに含まれるコミットは`git fetch`した時点で全てダウンロード済みである。
ここで`git fetch . origin/develop:develop`を実行すると、「ローカルのdevelopの参照先をトラッキングブランチのorigin/developの参照先で書き換える」という挙動が発生する。これはまさに、`develop`ブランチを`pull`した時の挙動に他ならない。そして、このコマンドは`HEAD`がどのブランチ(あるいは`detached HEAD`)に居ても実行可能である。

なお、ファストフォワード可能でない場合は失敗する。

```bash:bash
$ git fetch . origin/develop:develop
From .
 ! [rejected]          origin/develop -> develop  (non-fast-forward)
```
この時は`src`側に`+`を追加することで強制的に上書きできる(すべきか否かは置いておいて)。

```bash:bash
$ git fetch . +origin/develop:develop
From .
 + 6c3fe849...22098671 origin/develop -> develop  (forced update)

```

また、HEADが当該ブランチを指している場合(要するにcheckoutしていない場合)も失敗する。この場合はおとなしく`pull`を使おう。

```bash:bash
#develop上で操作した場合.
$ git fetch . origin/develop:develop
fatal: Refusing to fetch into current branch refs/heads/develop of non-bare repository
fatal: The remote end hung up unexpectedly

```

# 書いておいて何だが
逆に言うと、`fetch`済みでファストフォワード可能な状態での`pull`は参照の付け替えしか実行されないため、処理としては一瞬である。そして`pull`が必要になるのは結局そのブランチをローカルで利用する時のみであるため、あまり利用頻度は無さそうだ(自ブランチに対して`merge`したいときは`git merge origin/<ブランチ名>`とすれば良いため)。
ただ、「現在位置に関わらず特定ブランチをpullする」という挙動はスクリプトを組むときに役立ちそうということで、記事としてここに残しておく。

## 追記
`git flow`で`feature`ブランチや`release`を`finish`する際、`develop`が`origin/develop`と一致していないと「`develop`を`pull`しろ」というエラーが出る。こういう時に便利。

# 参考
[checkoutせずにmergeする - Qiita](https://qiita.com/keisuke-nakata/items/cd8b448215747c29fd81)