要約
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
しろ」というエラーが出る。こういう時に便利。