Git でバージョン管理しているプロジェクトにおいて,大きなバイナリファイルを一緒に扱おうとすると,GitHub のファイルサイズの 100 MB 制限でエラーが発生したり*1,バイナリファイルを更新する度にレポジトリが肥大化していって処理に時間がかかるようになり,厄介です.
解決方法としては git-lfs
がしばしば用いられます*2.バイナリファイルの代わりにファイルの情報を Git で管理する仕組みです.一見素晴らしいですが,バイナリファイルの管理が Git レポジトリから切り離されるため,バイナリファイルそのものをどこに保存するかという悩ましい問題が発生します.
git-lfs
保存先の検討
GitHub
GitHub が全部管理してくれるので楽かもしれません.GitHub における Git Large File Storage のお値段は 50 GB の帯域と容量で 1ヶ月 5ドルだそうです *3.1 GB の無料枠が設定されているものの,すぐに使い切ってしまう容量なので,実質有料のような印象です.
Amazon S3
AWS に git-lfs
サーバーを作って, S3 に保存するように設定できるらしいです*4.GitHub よりは安いかもしれませんが,転送量を考慮するとそうでもないかもしれません.サーバーを自分で管理するのが,セキュリティや手間を考慮すると面倒かもしれません.
ローカル
たくさんのバイナリファイルを扱いながら,Git でバージョン管理をする方法を検討したところ, git-lfs-standalone-file
を使って git-lfs
の保存先をローカルに設定すればよいことに気づきました*5.保存先を MacOS の TimeMachine などを用いてバックアップすれば安心です.レポジトリを共有する時は git レポジトリとともに git-lfs
の保存先をコピーしてお渡しすればよさそうです.最も安い方法だと思いますが,ローカルのファイルの管理が面倒かもしれません.
メリットとデメリットを比較して,よろしければgit-lfs-standalone-file
を使ってみましょう.
git-lfs
のインストール
https://git-lfs.com/ に移動してダウンロードをクリックします.指示に従います.
git-lfs
の使い方
例として,"b" という名前のレポジトリを作成し,PNG形式のファイルを git-lfs
で管理して,$HOME/lfs-contents
に保存するよう設定します.
% mkdir b % cd b % git init % git lfs install Updated Git hooks. Git LFS initialized. % git config -f .lfsconfig lfs.url 'file://$HOME/lfs-contents' % git lfs track "*.png" Tracking "*.png" % git lfs track Listing tracked patterns *.png (.gitattributes) Listing excluded patterns
設定ファイルが2個作成されますので,commit します.
.gitattributes
*.png filter=lfs diff=lfs merge=lfs -text
.lfsconfig
[lfs] url = file:///Users/xxxxxxx/lfs-contents
lfs.url
が追加されました*6.
ファイルをいくつか追加します.1.txt はテキストファイル,2.png は画像ファイルです.
% ls 1.txt 2.png README.md % git add * % git commit -am "init" [main 541fc84] init 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 1.txt create mode 100644 2.png
保存先の $HOME/lfs-contents
を作成して,Git 初期化をします *7.
% cd lfs-contents % git init Initialized empty Git repository in /Users/xxxxxxx/lfs-contents/.git/
push します.
% git push origin main Uploading LFS objects: 100% (1/1), 0 B | 0 B/s, done. Enumerating objects: 5, done. Counting objects: 100% (5/5), done. Delta compression using up to 8 threads Compressing objects: 100% (3/3), done. Writing objects: 100% (4/4), 428 bytes | 428.00 KiB/s, done. Total 4 (delta 0), reused 0 (delta 0), pack-reused 0 To https://github.com/xxxxxxx/b.git 2575e31..0435c3c main -> main
どのような処理が行われたのか確認してみましょう.
% cd lfs-contents % cd .git/lfs/objects/c6/76 % ls c67693d4100f880d7175c49c5bda50281d33e456f2f232b67c5052a6c3a605ed
Git レポジトリの中で,2.png
はテキストファイルになっていて,上記の object への参照となっています.
2.png
version https://git-lfs.github.com/spec/v1 oid sha256:c67693d4100f880d7175c49c5bda50281d33e456f2f232b67c5052a6c3a605ed size 7834
git-lfs の保存先フォルダを別のパソコンにコピーしてから clone してみます.
% git clone https://github.com/xxxxxxxx/b.git Cloning into 'b'... remote: Enumerating objects: 7, done. remote: Counting objects: 100% (7/7), done. remote: Compressing objects: 100% (4/4), done. remote: Total 7 (delta 0), reused 4 (delta 0), pack-reused 0 Receiving objects: 100% (7/7), done. % cd b % ls 1.txt 2.png README.md
2.png
は画像ファイルになっており,正常に clone できました.
バイナリファイルの変更
git-lfs
の設定ができていれば,いつも通り commit すればよいです.例えば上記のレポジトリ "b" の例で,2.png
を別の画像に差し替えて,push してみると,以下のように保存先に "24..." で始まる新しい object が追加されました."c6..." で始まる直前の object も残っており,過去の履歴が保持されいることが分かりました.
% cd lfs-contents % cd .git/lfs/objects/ % ls 24 c6
想定される困りごと
git-lfs
の保存先の管理方法はあるか.
保存先の lfs-contents
フォルダの中の .git/lfs/objects
フォルダに全てのバージョンのバイナリファイルが格納されていますので,これを大切に管理します.TimeMachine などで別のメディアに定期的にバックアップをしておくと安全だと思います.
レポジトリの名前を変更しても大丈夫か.ファイルを別のレポジトリに移動してもよいか.
ファイルの中身のハッシュをもとに参照しているだけなので,バイナリファイルを別のレポジトリに移動したり,レポジトリの名前を変更しても,git-lfs
の保存先に存在する限り参照され続けます.
実際に git-lfs
を導入したレポジトリについて,名称変更してから clone してみたところ,問題なく動作しました.
git-lfs
の保存先が肥大化した場合どうするか
git-lfs
の保存先も単なる git レポジトリであり, .git/lfs/objects
にファイルとして保存されています. git-lfs
は必要なファイルのみを取得するよう設計されているので*8,上記のフォルダから不要なファイルを削除するだけでよいと思われます.
実際に git-lfs
を導入したレポジトリに含まれるバイナリファイルを,Git レポジトリと git-lfs
保存先の両方から削除して,clone してみたところ,問題なく動作しました.ただし過去のバージョンは当然ながら復元できませんでした.
そもそもデータが肥大化したために過去のバージョンを廃棄して良いならば,バージョン管理をしなくてもよいファイルかもしれません.
複数のレポジトリで同じ git-lfs
の保存先を使用できるか.
ハッシュが衝突しない限り保存先を共有して問題ないはずですが,データが肥大化したときの整理が大変かもしれません.それぞれのレポジトリの保存先を最初に作成しておけばよいだけなので分離しておくのが望ましいと思います.
既存のレポジトリに git-lfs
を導入するのは安全か.
git lfs migrate
コマンドや git reflog expire --expire-unreachable=now --all
コマンドを駆使して導入できるらしいです*9.しかし,既存のレポジトリはそのままで,新しいレポジトリを作るほうが,きれいで速くて簡単だと思います.
脚注
*1:GitHub blocks files larger than 100 MiB. https://docs.github.com/en/repositories/working-with-files/managing-large-files/about-large-files-on-github
*2:To track files beyond this limit, you can use Git Large File Storage. https://docs.github.com/en/repositories/working-with-files/managing-large-files/about-git-large-file-storage
*3:One data pack costs $5 per month, and provides a monthly quota of 50 GiB for bandwidth and 50 GiB for storage. https://docs.github.com/en/billing/managing-billing-for-git-large-file-storage/about-billing-for-git-large-file-storage
*4:Git LFS: S3 agent など,実装がいくつか公開されている https://github.com/nicolas-graves/lfs-s3
*5:git-lfs-standalone-file(1) git-lfs/docs/man/git-lfs-standalone-file.adoc at main · git-lfs/git-lfs · GitHub
*6:git-lfs-config(5) https://github.com/git-lfs/git-lfs/blob/main/docs/man/git-lfs-config.adoc
*7:git-lfs の保存先は Git レポジトリでなければなりません.fatal: not a git repository (or any of the parent directories): .git とエラーが発生します
*8:Download less data. This means faster cloning and fetching from repositories that deal with large files. https://git-lfs.com/
*9:https://github.com/git-lfs/git-lfs/wiki/Tutorial#migrating-existing-repository-data-to-lfs