はじめに
これは Git Advent Calendar 2016 の4日目の記事です。
今回の記事が対象とする大規模なレポジトリは、何年間も開発が続けられ、ファイル数、履歴、ブランチ、タグなど、全般的に肥大化してしまったようなレポジトリです。肥大化したレポジトリを何も気にせず扱った場合、以下のような不幸な自体に見舞われます。
- 終わらない git clone
- 止まらない disk full
- 帰ってこない git status
これらは貴重な時間や資源だけでなく、エンジニアや周りの人の精神エネルギーまで持っていきます。
この記事においては、これらの原因を解決するための実践テクニックのうち、明日から利用できるものをまとめます。歴史を改ざんする系のもの、レポジトリ構成を変えるもの、アプリケーション層に入り込んだ改修などは今回の記事の範囲から割愛させていただきます。
また、巨大なレポジトリを扱うための記事は、Atlassianさんの記事が体系的であり、網羅性も高く、非常に良いので、そちらをぜひご参照ください。
How to handle big repositories with git | Atlassian Blogs
Shallow Clone を利用した履歴の削減
リモートからのcloneの際に、 --branch <name>
オプションで取得したいブランチを指定し、 --depth <depth>
オプションで取得する履歴の長さを制限します。
例えば、Railsの最新のmasterのみを利用したいのであれば、以下のようなコマンドとなります。
$ git clone --branch master --depth 1 git@github.com:rails/rails.git shallow-rails
過去の膨大な履歴を参照しないことで、通信およびローカルでのデータ量が削減されます。また、git管理下のオブジェクト数も削減される為、各種コマンドの応答性も向上します。
このShallow Cloneを利用したレポジトリで、別ブランチに切り替えて作業をしたい場合は、clone しなおすのではなく、 git fetch --depth=1 origin <name>
で FETCH_HEAD
にリモートブランチを fetch して、 git checkout -b <branch> FETCH_HEAD
でブランチを取り出すのがオススメです。こうすることで、現在持っているオブジェクトを受信せず、最小限の通信で済みます。
長いので、お好みに応じてエイリアスに登録すると捗るかもしれません。
$ git config --local alias.co-remote '! f() { git fetch --depth=1 origin $1 && git checkout -b $1 FETCH_HEAD; }; f '
git 1.9 以降であれば、 push / pull のサポートが手厚くなり、 Pull Requestを利用した開発フローであれば基本的な問題は生じないはずです。
Refspec を利用したfetch対象の限定
ある程度構造化された大規模なプロジェクトにおいては、fetchする必要の無い branchが存在する場合があります。例えば、QA環境では、 qa/$date_of_release
という名前のブランチしか利用しない。本番環境では、 release/$release_version
というブランチしか利用しないというような場合です。この場合であれば、git fetch の際の Refspec の設定をきちんと行うことで、指定された名前空間のブランチのみを fetch することができ、データ量の削減が行えます。
何も指定せず git clone
したレポジトリに対して、引数なしの git fetch
は暗黙的に git fetch origin +refs/heads/*:refs/remotes/origin/*
を指す形になります。これは、リモートに存在するすべてのブランチをローカルにfetchする設定です。これを、明示的に指定してあげることで、特定の名前空間以下のブランチのみを fetch対象とすることができ、データ量が削減できます。
git fetch origin +refs/heads/<namespace>/*:refs/remotes/origin/<namespace>/*
また、最新のブランチの状態しか必要ないのであれば、 --depth=1
オプションをつける。使い終わったブランチが自動的に削除されるように、 --prune
オプションをつけるというのもデータ量の削減に貢献します。
sparse-checkout を利用したワークツリーのファイル数削減
ファイル数が多いレポジトリであっても、特定の環境において、必要の無いファイルが存在する場合があります。その場合は、 sparse-checkout
の仕組みを利用して、必要なファイルだけ取り出すことでワークツリーのファイル数を削減し、容量の削減および、各種コマンドの応答性の向上を図る事ができます。
$ git config core.sparsecheckout true # まずは、 sparse-checkout オプションを有効に
$ echo 'lib/' > .git/info/sparse-checkout # lib以外のデータは必要ない
$ git read-tree -m -u HEAD # worktreeを最新の状態に従って読み込み直す
worktree を利用したレポジトリの共有
1レポジトリあたりの容量が非常に大きく、リソースの少ないマシン上に複数環境を構築する場合、 git worktree add <path> <branch>
を行い、単一レポジトリに対して、複数のワークツリーを作成する手法がとれます。
欠点としては、複数の worktree で同一のブランチを checkout できないということです。とはいえ、PRをベースとした開発ではそこまで困らないはずなので、一考の余地はあります。
usamik26 さんの 複数の作業ディレクトリを作成する git worktree で worktree の仕組みが詳しく紹介されていますので、詳しく知りたい方は、こちらを参照してください。
まとめ
ということで、重いレポジトリを軽く扱う方法を紹介しました。
submodule に切り出す手法や、 git lfs
などを利用する方法、 歴史を改変する方法など、他にも手法はたくさんありますが、ひとまず明日から出来るという点で、軽いものをまとめてみました。
これで、少しでも重いGitにストレスを感じる人が少なくなれば幸いです。
また、これらは抜本的対策ではないので、この対策では不十分な方は、アプリやレポジトリの構成変更や、歴史改変といった、もっと攻めた手法を取っていただくのが良いと思います。
参考
以下に参考としたサイトや、本文中で触れたサイトを載せます。