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を使う場合は、addupdate--depth 1を指定すればよい。

Shallow cloneの解除

git clone --depth 1によるshallow cloneを解除する場合、git fetchgit 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あたりだろうか。このあたりも必要になったら続きとして整理したい。

コメントを残す

メールアドレスが公開されることはありません。