みーのぺーじ

みーが趣味でやっているPCやソフトウェアについて.Python, Javascript, Processing, Unityなど.

Git LFS でバイナリファイルをローカルで管理する

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