本記事は サムザップ Advent Calendar 2022 の12/3の記事です。
Unityでの開発では画像や3Dモデルなど、多くのバイナリファイルを扱います。
そういったサイズの大きいリポジトリを効率的に扱うコツについて紹介したいと思います。
まずメインのリポジトリでUnityプロジェクトを管理しながら、サブモジュールでアセットを管理し、
それぞれでLFSで管理するような大きなファイルがあるような構成を想定します。
large-size-unity-project
├── Assets
│ ├── External
│ │ └── submodule # サブモジュールで参照している別リポジトリ
│ │ └── large-file.bin # LFSで管理している 32MBくらいの大きなファイル
│ ├── Scenes
│ ├── Scripts
│ ├── StreamingAssets
│ │ └── large-file.bin # LFSで管理している 32MBくらいの大きなファイル
│ ├── Plugins
│ └── Resources
├── Packages
└── ProjectSettings
大規模なUnityプロジェクトのブランチ切り替えは、アセットインポートなどで時間がかかってしまうので、
予めプラットフォーム別や、開発するアプリのバージョン別にディレクトリを分けてクローンするのはよくあることかと思います。
これを単純に、同じリポジトリを別々にクローンすると、
$ git clone <REPO_URL> ./large-size-unity-project-a --recurse-submodules
$ git clone <REPO_URL> ./large-size-unity-project-b --recurse-submodules
当然ながらGitのオブジェクトやLFSのローカルキャッシュは全く同じものが2つ存在することになります、ストレージが勿体ないですね。
$ du -d 1 -BM .
129M ./large-size-unity-project-a
129M ./large-size-unity-project-b
257M .
git worktree を利用して複数のワークツリーを作成する
git worktree
を利用すれば同じローカルリポジトリから複数のワークツリーを作成することができます。
リポジトリを共有しているので、ローカルブランチ・コミットを相互に参照できる、などの効果がありますが、LFSのローカルキャッシュも共有してくれるのが大きなメリットです。
$ git clone <REPO_URL> ./large-size-unity-project-a --recurse-submodules
$ cd ./large-size-unity-project-a
$ git worktree add ../large-size-unity-project-b
$ cd ../large-size-unity-project-b
$ git submodule update --init
$ du -d 1 -BM .
161M ./large-size-unity-project-a
65M ./large-size-unity-project-b
225M .
ただ残念ながら、git worktree
ではサブモジュールの中身までは共有されないので、
容量の削減効果も、Assets/StreamingAssets/large-file.bin
一つ分(32MB)の削減と限定的なものになっています。
LFSローカルキャッシュの保存先を共有する
ワークツリー内のサブモジュールがそれぞれ個別にローカルキャッシュ保持しないように、
git config lfs.storage
を設定して、LFSローカルキャッシュの保存先を共有させます。
# 後でLFSの保存先を指定するため、LFSファイルのダウンロードをスキップしてクローンする
$ GIT_LFS_SKIP_SMUDGE=1 git clone <REPO_URL> ./large-size-unity-project-a --recurse-submodules
# 保存先を指定してからLFSファイルをダウンロードする
$ pushd ./large-size-unity-project-a
$ git config lfs.storage C:/PATH_TO_REPO_ROOT/lfs && git lfs pull
# サブモジュールも同様に、保存先を指定してからLFSファイルをダウンロードする
$ pushd ./Assets/External/submodule
$ git config lfs.storage C:/PATH_TO_REPO_ROOT/lfs && git lfs pull
$ popd
$ git worktree add ../large-size-unity-project-b
$ popd
# ワークツリーは設定を共有しているので、サブモジュールだけ保存先を指定する
$ pushd ../large-size-unity-project-b
$ GIT_LFS_SKIP_SMUDGE=1 git submodule update --init
$ pushd ./Assets/External/submodule
$ git config lfs.storage C:/PATH_TO_REPO_ROOT/lfs && git lfs pull
$ popd
$ popd
コマンドを見ていただくと分かる通り、サブモジュールに関しては一つ一つ指定する必要があって煩雑です。
$ du -d 1 -BM .
65M ./large-size-unity-project-a
65M ./large-size-unity-project-b
65M ./lfs
193M .
しかしながら、その甲斐があってストレージ使用量をさらに抑えることが出来ました。
またキャッシュを共有しているので、サブモジュール更新時のLFSファイルのダウンロードも一度だけで済ませられることが期待できます。
.gitconfig の includeIf を使って特定ディレクトリ以下に一括で lfs.storage を設定する
とはいえ流石に手順が煩雑なので、もっと楽が出来る方法を取りたいと思います。
.gitconfig
の includeIf
を使えば、マッチしたディレクトリ以下に読み込まれる設定ファイルを指定することが出来ます。
これを利用して、特定ディレクトリ以下のリポジトリすべてで lfs.storage
を共有するようにしてみましょう。
[includeIf "gitdir:C:/PATH_TO_REPO_ROOT/"]
path = C:/PATH_TO_REPO_ROOT/gitconfig
[lfs]
storage = C:/PATH_TO_REPO_ROOT/lfs
このように設定しておけば、サブモジュールも含む、設定したディレクトリ以下のリポジトリへ一括で lfs.storage
を指定出来ます。
複数リポジトリで lfs.storage を共有する場合の注意点
参照されなくなったLFSローカルキャッシュを削除する git lfs prune
コマンドですが、
これはコマンドを実行したリポジトリ基準で参照がチェックされるため、lfs.storage
を共有した別のリポジトリからの参照は考慮されません。
そのため、参照が生きているファイルも誤って削除されてしまうため、このコマンドは実行しないようにしましょう。
Note: you should not run git lfs prune if you have different repositories sharing the same storage directory.
環境
本記事は以下の環境のもと作成しました。
$ git --version
git version 2.38.1.windows.1
$ git-lfs --version
git-lfs/3.2.0 (GitHub; windows amd64; go 1.18.2)
参考