Gitのshallow cloneの基本
概要
Gitでcloneやfetch、pullをそのまま使用するとリポジトリ―の履歴全体を取得してしまう。リポジトリ―が小さければ問題ないが、巨大なリポジトリ―の場合、非常に時間がかかるし、ストレージ容量も圧迫してしまう。単に最新コードを使いたいだけならば、全履歴を取得する必要はない。
Gitではshallow clone (浅いクローン) を作ることができ、リポジトリ―の取得を最新コミットのみに限定して、ダウンロードサイズを大幅に減らすことが可能な機能がある。これに焦点を当てて、必要なコミットだけを取得する方法を整理する。
以下のGitのマニュアルを参考にした。
結論としては以下のコマンド群で特定コミット・ブランチ・タグのみを取得したり、shallow cloneを解除できる。
git clone --depth 1 $URL
git config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*'
git fetch --unshallow
git fetch --depth 1 origin $BRANCH
git fetch --depth 1 origin tag $TAG
順番に説明する。
git clone –depth 1
まず基本となるのがgit clone --depth <depth>
だ。<depth>
にコミット数を指定することで、取得する履歴を新しい順に限定できる。<depth>=1を指定することで、最新コミットだけを取得できる。これが基本となる。
git clone --depth 1 $URL
ただし、--depth
を指定してcloneすると、--no-single-branch
を指定しない限り、--single-branch
を指定したとみなされ、.git/config
内のfetchが以下のようにmain/masterブランチのみとなる。このままでは他のブランチをfetchできず、fetchしても上流追跡ブランチにはならず、使い勝手が悪い。
[remote "origin"]
url = <repository>
fetch = +refs/heads/main:refs/remotes/origin/main
コマンドだと以下で確認できる。
git config remote.origin.fetch
+refs/heads/master:refs/remotes/origin/master
これは、以下のコマンドでrefspecを修正するか、同じ内容で.git/config
を手動で編集すれば回避できる。
git config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*'
--depth
指定時に--no-single-branch
を指定すると、最初から上記のようになっている。ただし、--no-single-branch
を指定してしまうと、全ブランチの最新コミットと最新のタグまで取得してしまう。ローカルの上流追跡ブランチが大量にできる可能性があり、ダウンロードに時間がかかるという欠点がある。
そのため、初回クローン直後に上記コマンドでrefspecを更新するのがよい。なお、refspecを編集せずにfetch時に直接指定する方法もあるが、これは手間がかかるのでやめたほうがいい。
また、git clone
時にブランチとタグを指定できる。-b/--branch
オプションの引数に対象ブランチかタグを指定する。ブランチだけでなくタグも指定できる。
git clone --depth 1 --no-single-branch -b $BRANCH $URL
調べた限り、git clone
ではコミット単体を指定できないので注意する。特定コミットだけが欲しい場合、一度--depth
1
で最新メインブランチを取得後、fetch/pullで指定して取得することになる。ブランチやタグも同じ手順で後から取得することもできる。
そもそも、git clone
はリポジトリ―取得の初回しか使えず、応用が利かない。ブランチ・タグが事前にわかっていて、それしか使わない場合はgit clone
で直接指定するのが早い。それ以外は、-b
を指定せずにメインブランチを取得して、fetch
で取得して、git checkout
で切り替えるのが汎用性が高い。手順として共通化できて覚えやすいだろう。
なお、submoduleを一緒に取得する場合、git clone
のオプションに--recurse-submodule
に加えて--shallow-submodules
を一緒に指定すると、submoduleのリポジトリ―を--depth 1
で取得できる。
ただし、submoduleの取得をclone時に行うと、clone時間が長くなってしまう。submoduleの更新はメインリポジトリ―の取得完了後に分けて行ったほうがよいだろう。
git submodule
を使う場合は、add
とupdate
に--depth 1
を指定すればよい。
Shallow cloneの解除
git clone --depth 1
によるshallow cloneを解除する場合、git fetch
かgit pull
に--unshallow
オプションを指定する。
git fetch --unshallow
git pull --unshallow
上記コマンドだと、全ブランチの全履歴を取得する。引数にブランチを指定することで、特定ブランチの全履歴に限定できる。
git fetch --unshallow origin branch
git pull --unshallow origin branch
リポジトリ―全体のバックアップや、他のGitのホスティングサービスに登録する際などには使うことになるだろう。
fetch/pull
先にも記した通り、git clone
はリポジトリ―取得の初回しか使えない。初回以外の取得済みのリポジトリ―に対して、新規にブランチ・タグ・コミットを取得する場合はgit fetch/pull
を使う。fetchもcloneと同じく、--depth 1
を指定するのが基本となる。
fetchとpullはデータ取得後にマージするかしないかの違いだけのため、マージをしないfetchをベースに説明する。なお、refspecは更新済みであることを前提とする。
全ブランチの最新コミットを取得する場合は以下のコマンドを実行する。
git fetch --depth 1 --all
特定のブランチ・タグ・コミットを取得する場合、以下のように指定する。
git fetch --depth 1 origin $BRANCH
ブランチ・タグ・コミット共通でorigin (リモートリポジトリ―名) の後に指定すればよい。
ただし、タグの場合、ローカルにタグが作成されない。自分で作ってもいいのだが、以下のコマンドにより指定タグも作成される。
git fetch --depth 1 origin tag $TAG
タグを全て取得したい場合は-t/--tags
オプションを指定する。
git fetch --depth 1 -t
取得後は、checkout
で該当ブランチ・コミット・タグに切り替えることができる。
git checkout $BRANCH
refsを更新していれば、上流追跡ブランチになっており、そのままリモートブランチの更新をfetch/pullで取り込める。
初回の取得後、リモートリポジトリ―で複数のコミットがなされた場合、--depth 1
を指定しなければ、まとめて取得できる。開発目的の場合は--depth 1
は初回だけの指定になるだろう。
git pull
git pull origin $BRANCH
fetch/pullには、--depth
の他に--deepen
オプションがある。–depthが取得対象の先端が基準 (新しい順) であるのと異なり、--deepen
は現在のコミットが基準 (古い順) となり、取得対象コミット数の方向が逆となる。--depth
を使うと、場合によってはリモートで開発が一気に進んで、途中のコミットが抜けてしまい、特にpullを使っている場合はマージできない可能性がある。--deepen
だと途中の抜けがないので、マージコンフリクトを防げる。開発速度が早くて大きな変化がある場合などに使うと便利だろう。
refspecを更新しない場合
git clone –depth 1の実行後にrefspecを更新することを記した。refspecを更新しない場合の対応についても記載しておく。
refspecを更新しない場合、fetch/pullの引数でrefspec相当の内容を記載してある程度対応する。
ポイントとなるのが、最初に以下のコマンドでrefspecに該当ブランチの追加だ。
git remote set-branches --add origin $BRANCH
このコマンドで以下のようにrefspecに該当ブランチが追加される。
git config --get-all remote.origin.fetch
+refs/heads/main:refs/remotes/origin/main +refs/heads/$BRANCH:refs/remotes/origin/$BRANCH
この状態で、以下のコマンドでリモートブランチを取得して、ブランチをチェックアウト (自動で上流追跡ブランチ作成) する。
git fetch --depth 1 origin $BRANCH:remotes/origin/$BRANCH
git checkout $BRANCH
あるいは、以下のコマンドでブランチを取得後、上流追跡ブランチを設定する。
git fetch --depth 1 origin $BRANCH:$BRANCH
git branch -u origin/$BRANCH $BRANCH
この方法であれば、ブランチがきちんと上流追跡ブランチになる。
最初のrefspecの追加をしないと、例えばgit branch -u
で以下のエラーが出る。
fatal: cannot set up tracking information; starting point 'origin/vue3' is not a branch
「git – Cannot setup tracking information; starting point ‘origin/master’ is not a branch – Stack Overflow」に対策があり解決できた。
refspecに情報がないので、自動で関連付けできないのが問題なのだと思われる。
他にもfetchの--refmap
オプションなども試してみたが、うまくいかなかった。複雑になるだけなので、結局git clone
直後にrefspecを更新するのが一番良いと判断した。
なお、上流追跡ブランチにこだわりがなければ、refspecの更新も特に必要ない。
結論
Gitのshallow cloneの基本について整理した。
今までなんとなく使ってきていたが、シャロークローン後にfetch/pullがうまくできなくて、改めて整理した。refspecの更新がポイントだった。
ブランチ、タグ、コミットの取得方法を一通り整理できた。通常使用ではこれで問題ないだろう。
後は、応用的な内容としてsubmoduleと特定ディレクトリーの取得方法としてgit svnやsparse checkout、後はshallow-sinceとshallow-commitあたりだろうか。このあたりも必要になったら続きとして整理したい。