🐔

git で特定の branch / tag だけを clone してイライラを減らす

2024/07/16に公開

はじめに

git を使ってると、特定の branch や tag だけを clone したくなることが稀によくある。たとえば docker コマンドを使っていてちょっと挙動が不可思議だったりするとソースを見に行くと思うが、そんな時にソースを根こそぎ clone すると結構時間がかかってイライラする。イライラどころか何ならリポジトリがデカすぎて clone に失敗する事すらある。(我が家のようにネットワーク環境が貧弱な場合

と言う訳で、そんな時にイライラを減らすために有用なコマンドやオプションをまとめてみた。(イライラしなくなるとは言ってない

まずは普通に clone してみる

比較のため、まずは普通に clone してみる。

$ time git clone https://github.com/docker/cli.git
Cloning into 'cli'...
remote: Enumerating objects: 423695, done.
remote: Counting objects: 100% (8959/8959), done.
remote: Compressing objects: 100% (1596/1596), done.
remote: Total 423695 (delta 8086), reused 7714 (delta 7346), pack-reused 414736
Receiving objects: 100% (423695/423695), 181.86 MiB | 17.60 MiB/s, done.
Resolving deltas: 100% (219767/219767), done.

real    0m21.476s
user    0m30.640s
sys     0m4.145s

なかなかに遅い。

いや、実はこれでも我が家の環境よりは断然速い。
最初はくっそ遅い我が家の環境で比較して差を強調しようと思ったのだがのだが、ネットワークがあまりにもダメ過ぎて結果が安定しないので仕方なく EC2 上で試している関係で、それなりの速度は出ているんだと思う。

話が逸れた。以降これを改善していこうと思う。

branch を指定する

git clone には -b オプションがあるのでこれを使えば良さそうだ。
今回は手元の環境にインストールされている docker cli のバージョンが 24.0.7 だったので、それに合わせて 24.0 branch を取ってくる。

$ time git clone -b 24.0 https://github.com/docker/cli.git
Cloning into 'cli'...
remote: Enumerating objects: 423695, done.
remote: Counting objects: 100% (8959/8959), done.
remote: Compressing objects: 100% (1596/1596), done.
remote: Total 423695 (delta 8086), reused 7714 (delta 7346), pack-reused 414736
Receiving objects: 100% (423695/423695), 181.86 MiB | 15.34 MiB/s, done.
Resolving deltas: 100% (219767/219767), done.

real    0m23.687s
user    0m29.470s
sys     0m4.030s

ぜんぜん良さそうじゃなかった…

よくよくマニュアルを読むと、-b では clone そのものの挙動は変わらなくて、最後に checkout する branch が変わるだけだった。そりゃあ速くはならんわな…

他の branch を clone しない

--single-branch と言うオプションがある。これなら特定の branch のみを clone してくれるので、-b と併用してみる。

$ time git clone -b 24.0 --single-branch https://github.com/docker/cli.git
Cloning into 'cli'...
remote: Enumerating objects: 82325, done.
remote: Counting objects: 100% (7856/7856), done.
remote: Compressing objects: 100% (1091/1091), done.
remote: Total 82325 (delta 7340), reused 6797 (delta 6765), pack-reused 74469
Receiving objects: 100% (82325/82325), 45.84 MiB | 19.02 MiB/s, done.
Resolving deltas: 100% (49599/49599), done.

real    0m6.311s
user    0m7.370s
sys     0m1.011s

それなりに速くなった。

履歴を持ってこない

--single-branch だと、特定の branch の全ての過去履歴を持ってくる。過去の経緯とかまで調べたい時には役立つのだが、場合によってはそこまで必要ない。たとえば、手元の 24.0.7 に対応するソースだけ見られれば良い、といったような場合もあるだろう。

-b には tag も指定できるんだがそれでもそこまでの履歴を持ってこようとするので、そんな時には --depth オプションが役に立つ。

$ time git clone -b v24.0.7 --depth=1 https://github.com/docker/cli.git
Cloning into 'cli'...
remote: Enumerating objects: 3845, done.
remote: Counting objects: 100% (3845/3845), done.
remote: Compressing objects: 100% (3183/3183), done.
remote: Total 3845 (delta 584), reused 1926 (delta 361), pack-reused 0
Receiving objects: 100% (3845/3845), 6.29 MiB | 12.42 MiB/s, done.
Resolving deltas: 100% (584/584), done.
Note: switching to 'afdd53b4e341be38d2056a42113b938559bb1d94'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

real    0m2.273s
user    0m0.796s
sys     0m0.301s

めちゃめちゃ速い。履歴を一切持ってきてないのだから当たり前かもしれないが、とにかく速い。

見れば分かると思うが、履歴がちょっとだけ欲しい場合には --depth=10 とか指定すれば履歴を 10 件だけ持ってきてくれる。

また、上記では tag を指定したがもちろん branch の時に --depth=1 とか指定しても良い。

ちなみに、--depth を指定すると --single-branch がデフォルトになるので、わざわざ --single-branch を指定する必要は無い。

出力がウザいんだが?

先程めっさ速かったのだが、出力がウザいと思った方も多いだろう。

tag を指定して clone したので親切にも「branch が無いから detached HEAD だよ、危ないよ」と教えてくれているのだが、「そんなん分かっとるがな、大きなお世話や」と言う方もいるかもしれない。

あるいは、このコマンドはシェルスクリプトや Dockerfile の中から実行されていて、ビルドし終わったらリポジトリ自体要らない、なんて場合もあるかもしれない。(割と見かける

そんな場合には advice.detachedHead オプションを false にすると良い。(実はさっきのログにも出ているが…

$ time git -c advice.detachedHead=false clone -b v24.0.7 --depth=1 https://github.com/docker/cli.git
Cloning into 'cli'...
remote: Enumerating objects: 3845, done.
remote: Counting objects: 100% (3845/3845), done.
remote: Compressing objects: 100% (3183/3183), done.
remote: Total 3845 (delta 584), reused 1926 (delta 361), pack-reused 0
Receiving objects: 100% (3845/3845), 6.29 MiB | 12.10 MiB/s, done.
Resolving deltas: 100% (584/584), done.

real    0m2.427s
user    0m0.979s
sys     0m0.328s

速さはそのままに、ウザさが軽減された。

あのウザい出力は常に要らない

オレ自身はあの出力はありがたいと思っているのだが(ウザいことによって detached HEAD であることを忘れずに済む)、あれは常に要らんと思っている方もいるかもしれない。

そんな方は config を変えてしまおう。(お勧めはしないが

$ git config --global advice.detachedHead false
$ time git clone -b v24.0.7 --depth=1 https://github.com/docker/cli.git
Cloning into 'cli'...
remote: Enumerating objects: 3845, done.
remote: Counting objects: 100% (3845/3845), done.
remote: Compressing objects: 100% (3183/3183), done.
remote: Total 3845 (delta 584), reused 1926 (delta 361), pack-reused 0
Receiving objects: 100% (3845/3845), 6.29 MiB | 10.31 MiB/s, done.
Resolving deltas: 100% (584/584), done.

real    0m2.469s
user    0m0.859s
sys     0m0.315s

これで毎回オプションを指定しなくてもウザくない。

ところで、git config のマニュアルには advice.detachedHeadgit switchgit checkout に効くぜ的な事が書いてあるが、git switch で例のメッセージを出力させるのは無理なんじゃないかと思う。知らんけど…

追加の branch が欲しくなった

最初 --single-branch で持ってきたけど、他の branch も欲しくなる場合はあるだろう。たとえば、今使ってるのは 24.0 系列だから 24.0 branch だけ取ってきたけど最新の 27.0 系列だったらどうなってるか知りたいとか。

そんな時は remote set-branches コマンドを実行すればよい。

$ git remote set-branches --add origin 27.0

--add オプションに注意しよう。これで指定した branch も「追加で」認識されるので、これ以降普通に fetch すると 27.0 branch も一緒に落ちてくる。

$ time git fetch
remote: Enumerating objects: 10586, done.
remote: Counting objects: 100% (5893/5893), done.
remote: Compressing objects: 100% (800/800), done.
remote: Total 10586 (delta 5467), reused 5208 (delta 5093), pack-reused 4693
Receiving objects: 100% (10586/10586), 6.09 MiB | 8.04 MiB/s, done.
Resolving deltas: 100% (7238/7238), completed with 1365 local objects.
From https://github.com/docker/cli
 * [new branch]          27.0       -> origin/27.0
 * [new tag]             v25.0.0    -> v25.0.0
...量が多いので略...
 * [new tag]             v27.0.2    -> v27.0.2
 * [new tag]             v27.0.3    -> v27.0.3

real    0m3.009s
user    0m1.532s
sys     0m0.209s

あとはいつも通り git switch して作業すればよい。

$ git switch 27.0
branch '27.0' set up to track 'origin/27.0'.
Switched to a new branch '27.0'

追加の tag が欲しくなった。

追加で欲しいのが tag であれば、fetch で直接取ってこれる。

$ time git fetch --depth=1 origin tag v27.0.3
remote: Enumerating objects: 4023, done.
remote: Counting objects: 100% (4023/4023), done.
remote: Compressing objects: 100% (1669/1669), done.
remote: Total 2464 (delta 1453), reused 1319 (delta 645), pack-reused 0
Receiving objects: 100% (2464/2464), 2.66 MiB | 15.28 MiB/s, done.
Resolving deltas: 100% (1453/1453), completed with 1066 local objects.
From https://github.com/docker/cli
 * [new tag]         v27.0.3    -> v27.0.3

real    0m2.228s
user    0m0.893s
sys     0m0.118s

見ての通り、fetch コマンドに origin tag v27.0.3 と言う引数を付けることで当該 tag のみ取ってこれる。

ちなみに、--depth=1 を忘れると指定した tag に関連する履歴を根こそぎ拾ってこようとして時間がかかるので注意が必要だ。

やっぱり全部の branch が欲しくなった

最初 --single-branchclone したけどやっぱり全 branch 欲しくなる、なんてこともあるだろう。

そんな時も remote set-branches コマンドを実行すればよい。

$ git remote set-branches origin "*"

今度は --add オプションは指定していないが、branch の名前としてワイルドカード "*" を指定したので全ての branch が根こそぎ認識される。

これ以降普通に fetch すると全ての branch が落ちてくる。

$ time git fetch
remote: Enumerating objects: 18089, done.
remote: Counting objects: 100% (10022/10022), done.
remote: Compressing objects: 100% (1220/1220), done.
remote: Total 18089 (delta 9234), reused 9260 (delta 8785), pack-reused 8067
Receiving objects: 100% (18089/18089), 8.83 MiB | 22.44 MiB/s, done.
Resolving deltas: 100% (12382/12382), completed with 2265 local objects.
From https://github.com/docker/cli
 * [new branch]          18.06                                               -> origin/18.06
 * [new branch]          18.09                                               -> origin/18.09
...ものっそい量なので略...
 * [new tag]             v27.0.2                                             -> v27.0.2
 * [new tag]             v27.0.3                                             -> v27.0.3

real    0m4.012s
user    0m2.619s
sys     0m0.398s

やっぱり全部の履歴が欲しくなった

最初 --depth=1clone したけどやっぱり全履歴欲しくなる、なんてこともあるだろう。

そんな時は fetch --unshallow コマンドを実行すればよい。

$ time git fetch --unshallow
remote: Enumerating objects: 80591, done.
remote: Counting objects: 100% (80589/80589), done.
remote: Compressing objects: 100% (27277/27277), done.
remote: Total 78155 (delta 49881), reused 74743 (delta 46555), pack-reused 0
Receiving objects: 100% (78155/78155), 38.09 MiB | 20.45 MiB/s, done.
Resolving deltas: 100% (49881/49881), completed with 1734 local objects.
remote: Enumerating objects: 33, done.
remote: Total 33 (delta 0), reused 0 (delta 0), pack-reused 33
Unpacking objects: 100% (33/33), 4.08 KiB | 2.04 MiB/s, done.
From https://github.com/docker/cli
 * [new tag]             v18.06.0-ce-rc1 -> v18.06.0-ce-rc1
 * [new tag]             v18.09.0-ce-tp0 -> v18.09.0-ce-tp0
...量が結構多いので略...
 * [new tag]             v24.0.5    -> v24.0.5
 * [new tag]             v24.0.6    -> v24.0.6

real    0m9.216s
user    0m7.064s
sys     0m0.870s

これで、元々持ってきていた branch や tag に関する全ての履歴が取ってこられる。

おわりに

イライラ軽減コマンド/オプションについてまとめてみた。これが皆さんのイライラ軽減に役立てば幸いである。

それでは、よい git ライフを。

Discussion