サイズの大きいUnityプロジェクトのGitリポジトリを効率的に扱うコツ

本記事は [サムザップ Advent Calendar 2022](https://qiita.com/advent-calendar/2022/sumzap) の12/3の記事です。

Unityでの開発では画像や3Dモデルなど、多くのバイナリファイルを扱います。
そういったサイズの大きいリポジトリを効率的に扱うコツについて紹介したいと思います。

----
まずメインのリポジトリでUnityプロジェクトを管理しながら、サブモジュールでアセットを管理し、
それぞれでLFSで管理するような大きなファイルがあるような構成を想定します。

```shell
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プロジェクトのブランチ切り替えは、アセットインポートなどで時間がかかってしまうので、
予めプラットフォーム別や、開発するアプリのバージョン別にディレクトリを分けてクローンするのはよくあることかと思います。

これを単純に、同じリポジトリを別々にクローンすると、

```shell
$ 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つ存在することになります、ストレージが勿体ないですね。

```shell
$ du -d 1 -BM .
129M    ./large-size-unity-project-a
129M    ./large-size-unity-project-b
257M    .
```

### git worktree を利用して複数のワークツリーを作成する
`git worktree` を利用すれば同じローカルリポジトリから複数のワークツリーを作成することができます。  
リポジトリを共有しているので、ローカルブランチ・コミットを相互に参照できる、などの効果がありますが、LFSのローカルキャッシュも共有してくれるのが大きなメリットです。

```shell
$ 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
```

```shell
$ 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ローカルキャッシュの保存先を共有させます。


```shell
# 後で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
```

コマンドを見ていただくと分かる通り、サブモジュールに関しては一つ一つ指定する必要があって煩雑です。

```shell
$ 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` を共有するようにしてみましょう。

```ini:~/.gitconfig
[includeIf "gitdir:C:/PATH_TO_REPO_ROOT/"]
    path = C:/PATH_TO_REPO_ROOT/gitconfig
```

```ini:C:/PATH_TO_REPO_ROOT/gitconfig
[lfs]
    storage = C:/PATH_TO_REPO_ROOT/lfs
```

このように設定しておけば、サブモジュールも含む、設定したディレクトリ以下のリポジトリへ一括で `lfs.storage` を指定出来ます。

### :warning:複数リポジトリで lfs.storage を共有する場合の注意点
参照されなくなったLFSローカルキャッシュを削除する `git lfs prune` コマンドですが、  
これはコマンドを実行したリポジトリ基準で参照がチェックされるため、`lfs.storage` を共有した別のリポジトリからの参照は考慮されません。  
そのため、参照が生きているファイルも誤って削除されてしまうため、このコマンドは実行しないようにしましょう。


[git-lfs-config.5.ronn](https://github.com/git-lfs/git-lfs/blob/v3.2.0/docs/man/git-lfs-config.5.ronn)

> Note: you should not run git lfs prune if you have different repositories sharing the same storage directory.



## 環境
本記事は以下の環境のもと作成しました。

```shell
$ git --version
git version 2.38.1.windows.1

$ git-lfs --version
git-lfs/3.2.0 (GitHub; windows amd64; go 1.18.2)
```

## 参考
https://github.com/git-lfs/git-lfs/blob/v3.2.0/docs/man/git-lfs-config.5.ronn

https://git-scm.com/docs/git-config/2.38.1#_includes