[GitHub] Git LFSで巨大なファイルを扱う

Gitでバイナリや巨大なファイルを扱う場合に活躍するのが「LFS(Large File Storage)」。 詳細な説明は様々なページで行われていますので、今回はGitHubの利用を前提とした基本的な使い方についてだけまとめておきます。

簡単に言うとLFSを利用するとファイルの実際のデータは専用のストレージに、リポジトリ内部には200byteにも満たないハッシュ値等を保存することでリポジトリを軽量化することができるという仕組みです。

Gitはバイナリファイルに更新がある度に全データがリポジトリに保管されるため油断しているとあっという間にリポジトリが肥大化してしまいます。またGitHubでは通常100Mbyte以上のファイルをpushすることができません。それに加えリポジトリのサイズは1G程度が推奨されていることから「LFS(Large File Storage)」が必要とされているというわけです。

GitHub LFSでの制約

無料枠の範囲

本気で使ったらあっという間に1Gは使い切るので無料枠は本当にお試し用ですね。

ストレージ容量
1GByteまで
転送量(月間)
1GByteまで

ストレージ・転送量ともに50GBを5ドル/月での購入が可能です。例えば月間15ドル払えば150GBの利用ができます。

最大ファイルサイズ

プランによって1ファイルあたるの最大容量が変わります。執筆時点では以下の通り。動画ファイルなどを扱う場合はうっかり超えちゃうかもしれませんね。

プラン ファイルサイズ
GitHub Free 2 GB
GitHub Pro 2 GB
GitHub Team 4 GB
GitHub Enterprise Cloud 5 GB

最新の情報はドキュメントなどでご確認を。

インストール

Git本体はすでにインストールされているとします。この状態でまずは「Git LFS」を入れます。macOSであればHomeBrewなら一発です。WindowsやLinuxをお使いの場合は公式サイトからダウンロード後、インストールを行ってください。

$ brew install git-lfs

ここではv3.1.2が入りました。Git LFSってGo言語製なんですね。知らんかったw

$ git lfs --version   
git-lfs/3.1.2 (GitHub; darwin amd64; go 1.17.6)

新規リポジトリに導入する

ローカルリポジトリを作成

適当な名前のディレクトリを作成し、いつも通りgit initを実行します。

$ mkdir foo; cd foo
$ git init                           
Initialized empty Git repository in /Users/katsube/foo/.git/

続けざまに今度はGitLFSをこのリポジトリに適用します。詳細は後述しますが~/.gitconfigに設定が追加され、.git/hooksにファイルが追加されます。これにより普通にGitを使うだけで、裏側では自動的にLFSが採用されるという環境になります。

$ git lfs install
Updated Git hooks.
Git LFS initialized.

LFSで管理する対象を設定

どのファイルをLFSに保存するか設定を行います。ここでは拡張子がPDFのファイルを対象としようとしています。特定のディレクトリ配下のみを対象としたい場合はdocuments/*.pdfなどのようにパスを記述することも可能です(拡張子を省略しすべてのファイルを対象とすることも可)。複数回実行することで複数のパターンを追加できます。

$ git lfs track '*.pdf'
Tracking "*.pdf"

git lfs trackを実行するとファイル「.gitattributes」が新たに作成されます。このファイルを元にLFSに入れるかどうか判定されますので自分以外のユーザーも同様の環境で開発を行うことができるようになります。というわけでこのファイルもリポジトリへcommit/pushします。

$ cat .gitattributes
*.pdf filter=lfs diff=lfs merge=lfs -text

リモートリポジトリを作成

今回はGitHub上に「lfstest」という名前のリポジトリを作成しました。特別な作業や設定は不要です。普通にリポジトリを新規作成してください。

先ほどローカルに作成したリポジトリのリモートとして登録しておきます。

$ git remote add origin git@github.com:katsube/lfstest.git

基本的な設定はここまでで終了です。

実際に使ってみる

めちゃめちゃ簡単です。

commitからのpush

LFSが正常に動くかどうかの実験を行いますので、git lfs trackで設定した拡張子のファイルを準備します。ファイルを適当な場所に配置したらstageへaddからのcommitを行います。

$ git add .
$ git commit -m '1st commit'

あとは普通にリモートへpushするだけ。LFSが機能している場合はUploading LFS objectsの表示がされます(転送中は転送状況が表示されます)。最初にLFSの転送が発生している以外はいつもと変わらないですね。

$ git push
Uploading LFS objects: 100% (1/1), 4.0 MB | 351 KB/s, done.  
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 595 bytes | 595.00 KiB/s, done.
Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:katsube/lfstest.git
 * [new branch]      main -> main

GitHub上で確認する

GitHub上では通常のファイルと変わらない感じで表示されていますが、LFSが適用されている場合はその旨の表示がされます。ちなみに今回登録したのは確定申告の準備中だったこともありAWSの請求書のPDFですw

現在LFSの容量をどの程度使っているかは画面右上のユーザーアイコンから「Settings」→「Billing and Plans」とたどることで確認できます。

その他

LFSで管理中のファイル一覧を確認

ls-filesで一覧表示が可能です。抜け漏れなどの確認に使えそうですね。

$ git lfs ls-files
a8783c0130 * meisai.pdf

LFSで管理するファイルのパターンを削除

untrackで削除することができます。.gitattributesからも内容が削除されていることを確認できます。

$ git lfs untrack '*.pdf'

LFSで使用している容量を削減したい

GitHubのLFSでダイエットするにはリポジトリを丸ごと削除するしか無いようです。

Git LFS からファイルを削除した後でも、Git LFS オブジェクトはそのままリモートストレージに存在し、Git LFS ストレージ容量に対するカウントも継続します。 Git LFS オブジェクトをリポジトリから削除するには、リポジトリを削除して再作成します。

※公式ドキュメントより

つまり以下の手順を取ることになります。

  1. Gitのfilter-branchなどを利用し過去のcommitから特定のファイルを削除
  2. GitHub上に新規でリポジトリを作成、リモートとして登録
  3. 新規リポジトリへpushする

地獄!\(^o^)/

必要とする容量にもよりますが、追加のストレージは50GBで月5ドル程度なので、お仕事の場合は素直に課金した方が幸せになれそうですね。

すでにGitへ登録しているファイルをLFSで管理したい

こちらも一度消して改めて登録し直す必要があります。

Git LFS で追跡する必要があるファイルがすでにリポジトリにある場合は、まずそれをリポジトリから削除する必要があります。

※公式ドキュメントより

対象のファイルが多い場合は、許されるのであればダイエットと同様に新規にリポジトリを作った方が早いかもしれませんね。

git lfs install実行後の変化

install後にどのファイルが加わったのかメモしておきます。

.git/hooks

hooksには合計4つのファイルpost-checkout, post-commit, post-merge, pre-pushが追加されています。ファイルの中身を覗くとわかりますがGitでcommitやpushをした際にgit lfsが自動的に呼び出される設定が行われています。

$ ls -la .git/hooks
total 152
drwxr-xr-x@ 19 katsube  staff   608  3 19 19:13 ./
drwxr-xr-x@ 10 katsube  staff   320  3 19 19:13 ../
-rwxr-xr-x   1 katsube  staff   478  3 19 19:12 applypatch-msg.sample*
-rwxr-xr-x   1 katsube  staff   896  3 19 19:12 commit-msg.sample*
-rwxr-xr-x   1 katsube  staff  4655  3 19 19:12 fsmonitor-watchman.sample*
-rwxr-xr-x   1 katsube  staff   282  3 19 19:13 post-checkout*
-rwxr-xr-x   1 katsube  staff   278  3 19 19:13 post-commit*
-rwxr-xr-x   1 katsube  staff   276  3 19 19:13 post-merge*
-rwxr-xr-x   1 katsube  staff   189  3 19 19:12 post-update.sample*
-rwxr-xr-x   1 katsube  staff   424  3 19 19:12 pre-applypatch.sample*
-rwxr-xr-x   1 katsube  staff  1643  3 19 19:12 pre-commit.sample*
-rwxr-xr-x   1 katsube  staff   416  3 19 19:12 pre-merge-commit.sample*
-rwxr-xr-x   1 katsube  staff   272  3 19 19:13 pre-push*
-rwxr-xr-x   1 katsube  staff  1374  3 19 19:12 pre-push.sample*
-rwxr-xr-x   1 katsube  staff  4898  3 19 19:12 pre-rebase.sample*
-rwxr-xr-x   1 katsube  staff   544  3 19 19:12 pre-receive.sample*
-rwxr-xr-x   1 katsube  staff  1492  3 19 19:12 prepare-commit-msg.sample*
-rwxr-xr-x   1 katsube  staff  2783  3 19 19:12 push-to-checkout.sample*
-rwxr-xr-x   1 katsube  staff  3650  3 19 19:12 update.sample*

~/.gitconfig

ホームディレクトリの.gitconfigにfilter設定が追加されます。filterはcheckoutとaddする際に任意の処理を挟むことができる機能です。

$ cat ~/.gitconfig
(中略)
[filter "lfs"]
process = git-lfs filter-process
required = true
clean = git-lfs clean -- %f
smudge = git-lfs smudge -- %f

参考ページ