要約
git pullしたいブランチをcheckoutせずに、その場でpullと同じ動きをさせるコマンド。
# リモートoriginからdevelopブランチをpull
git fetch
git fetch . origin/develop:develop
ただし、ファストフォワード可能な場合に限る。また、現在のブランチの場合は動作しないので、その場合はおとなしくpullを使う。
追記:エイリアス版ができた
解説
git pullコマンドは基本的にHEADの指すブランチ(要するに現在のブランチ)に対してのみ実行できる。例外としてgit pull --allを用いて全てのリモートから全ての追跡ブランチを一括pullすることは出来るが、ブランチ数が増えてくると中々使いづらい。
という訳で、通常はgit checkout <branch name>でHEADを切り替えた後にpullするが、checkoutはそこそこ「重い」処理であるため回避したい場合がある。あった。
ところでgit fetchコマンドはリモートブランチの内容をトラッキングブランチに反映させるものらしい。一般的にはネットワーク上からローカルにダウンロードしてくるイメージで良いと思うが、公式ドキュメントに "git-fetch - Download objects and refs from another repository" とある通り、コミットのオブジェクトと同時に参照の指す先を更新するという機能も持ち合わせている。
今回利用するfetchの構文はgit fetch [<repository> [<refspec>…]] のものであり、repositoryに対象のgitリポジトリ、refspec(参照仕様)に「書き込み先/書き込み元のパス」を渡す。
#公式ドキュメントより引用(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)に居ても実行可能である。
なお、ファストフォワード可能でない場合は失敗する。
$ git fetch . origin/develop:develop
From .
! [rejected] origin/develop -> develop (non-fast-forward)
この時はsrc側に+を追加することで強制的に上書きできる(すべきか否かは置いておいて)。
$ git fetch . +origin/develop:develop
From .
+ 6c3fe849...22098671 origin/develop -> develop (forced update)
また、HEADが当該ブランチを指している場合(要するにcheckoutしていない場合)も失敗する。この場合はおとなしくpullを使おう。
#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しろ」というエラーが出る。こういう時に便利。