基礎

Git LFS: Git リポジトリで大きなファイルを扱う方法

更新日: · Skillaria.Top

Git LFS(Large File Storage)は、大きなバイナリファイルを専用のサーバーに保存し、リポジトリにはごく小さなポインタファイルだけを入れる Git の拡張機能です。クローンが速くなり、履歴が肥大化せず、git add、commit、push といった通常の操作は変わりません。

Git LFS とは何か、なぜ必要か

Git LFS(Large File Storage)は、大きなファイルを扱うための Git の公式拡張機能です。動画、PSD のデザインデータ、データセット、ニューラルネットワークのモデル、ゲームのアセットなどが対象になります。コミットには実際のファイルではなく、約130バイトのテキストのポインタが入り、中身は LFS 用のストレージに保存されます。チェックアウトの際には拡張機能が実際のファイルを作業ディレクトリに展開するので、利用者にとっては通常の Git と変わりません。

Git LFS が解決する問題は、Git の仕組みそのものにあります。ファイルのすべてのバージョンが履歴に永久に残り、クローンではその履歴全体がダウンロードされます。200 MB の動画を10回差し替えると、リポジトリは約2 GB になり、新しい開発者は最新版だけが必要な場合でも10個すべてのバージョンをダウンロードします。テキストには効果的な圧縮や差分が、バイナリファイルではほとんど効きません。

LFS を使うと、クローン時には現在のコミットに対応する大きなファイルのバージョンだけがダウンロードされます。古いバージョンはサーバーに残っており、古いコミットに切り替えたときに取得されます。

Git LFS の内部の仕組み

この仕組みは、Git のフィルター(clean と smudge)の上に成り立っています。LFS で追跡しているファイルを git add すると、clean フィルターが動きます。内容をローカルのキャッシュ .git/lfs/objects に保存し、インデックスにはポインタを渡します。チェックアウトの際には smudge フィルターが逆の処理を行います。ハッシュでキャッシュ内のオブジェクトを探し(なければサーバーからダウンロード)、実際のファイルを書き出します。

ポインタは次のような内容です。

version https://git-lfs.github.com/spec/v1
oid sha256:4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1258daaa5e2ca24d17e2393
size 209715200

3行には、仕様のバージョン、内容の SHA-256 ハッシュ、バイト数が書かれています。Git の履歴に残るのはこれだけなので、履歴は軽いままです。git push の際には pre-push フックが動き、まず LFS API を通じて新しいオブジェクトをアップロードし、その後で通常のプロトコルによってコミットを送信します。

どのファイルを LFS に任せるかは、リポジトリのルートにある .gitattributes ファイルで決まります。これはコードと一緒にコミットされるため、チーム全体で同じルールが共有されます。

Git LFS のインストール

Git for Windows のインストーラーには、この拡張機能がすでに含まれています。macOS と Linux では、パッケージとしてインストールします。

# macOS (Homebrew)
brew install git-lfs

# Debian / Ubuntu
sudo apt install git-lfs

# Fedora
sudo dnf install git-lfs

インストール後、ユーザーごとに一度だけ次を実行します。

git lfs install

このコマンドは、LFS のフィルター設定をグローバルな ~/.gitconfig に書き込み、リポジトリの中にいれば、そのリポジトリにフックを設定します。git lfs version で導入の確認ができ、git lfs env で詳しい設定(サーバーのアドレス、キャッシュのパスなど)を表示できます。

大きなファイルの追跡を始める

新しいプロジェクトでの典型的な流れは次のとおりです。

git lfs track "*.psd"
git lfs track "*.mp4"
git lfs track "models/**"

git add .gitattributes
git add design/banner.psd
git commit -m "バナーのデザインデータを LFS で追加"
git push origin main

パターンは必ず引用符で囲んでください。囲まないと、シェルが *.psd をカレントフォルダのファイル一覧に展開してしまい、ルールが意図どおりになりません。git lfs track を実行すると、.gitattributes に次の行が追加されます。

*.psd filter=lfs diff=lfs merge=lfs -text

-text は改行コードの変換を無効にします。バイナリファイルが壊れるのを防ぐためです。順序も重要です。まず track のルールを設定し、その後でファイルを git add します。ルールを作る前に通常の方法でファイルを追加してしまうと、そのファイルは通常の blob として履歴に残ります。

ファイルが本当に LFS にあるか確認する

git lfs ls-files
4d7a214614 * design/banner.psd
9c1e0b7a3f * video/intro.mp4

git lfs status

git lfs ls-files は、現在のコミットに含まれる LFS のファイルを表示します。アスタリスクは内容がダウンロード済みであることを、ハイフンは作業ディレクトリにポインタだけがあることを意味します。追跡しているパターンの一覧は、引数なしの git lfs track で表示でき、git lfs untrack "*.psd" でルールを外せます。

クローンとファイルの取得

通常の git clone は、LFS があれば透過的に動作し、大きなファイルは自動でダウンロードされます。同僚の環境に LFS がインストールされていないと、作業ディレクトリには画像の代わりにテキストのポインタが現れます。「PSD の代わりに謎のテキストがある」という典型的な症状です。対処法は、インストールしてから次を実行することです。

git lfs install
git lfs pull

git lfs pull は、現在のコミットに対応するオブジェクトをダウンロードし、作業ディレクトリに展開します。git lfs fetch は、ファイルには触れず、キャッシュにダウンロードするだけです。--all を付けると、すべての参照のオブジェクトを取得します。完全なバックアップや別のサーバーへの移行に役立ちます。

大きなファイルを取得せずにクローンする

CI や、コードだけが必要な場合は、ギガバイト単位のアセットをダウンロードする必要はありません。環境変数で smudge フィルターを無効にできます。

GIT_LFS_SKIP_SMUDGE=1 git clone https://example.com/team/game.git
cd game
git lfs pull --include="textures/ui/**"

リポジトリはポインタの状態でクローンされ、その後 --include(--exclude もあります)で必要なファイルだけを選んで取得します。パイプラインでのビルドが大幅に速くなります。

既存のファイルを Git LFS に移行する

よくある状況は、リポジトリがすでに大きくなっていて、大きなファイルが履歴の中にある場合です。git lfs track は過去のコミットには作用せず、新しいコミットにだけ影響します。過去を書き換えるには git lfs migrate を使います。

まず、どれが容量を占めているかを確認します。

git lfs migrate info --everything --top=10

次に、すべてのブランチで対象の種類を移行します。

git lfs migrate import --include="*.psd,*.mp4" --everything
git push --force --all
git push --force --tags

移行は、これらのファイルが含まれていたすべてのコミットを書き換えるため、すべてのハッシュが変わります。これは公開済みのブランチに rebase を行うのと同じ状況です。全員に事前に知らせ、強制 push の後は、再度クローンするか、ローカルのブランチを強制的にリセットしてもらう必要があります。移行の前には、必ずバックアップを作成してください(git clone --mirror)。

履歴を書き換えられない場合は、穏やかな方法があります。git lfs migrate import --no-rewrite パス/ファイル は、その時点から LFS に移すための新しいコミットを作ります。古いバージョンは履歴に残りますが、リポジトリの肥大化は止まります。

ファイルのロック: バイナリの衝突を防ぐ

2人のデザイナーが同じ PSD を編集すると、Git はそれらの変更をマージできず、一方の作業が失われてしまいます。こうしたファイルには LFS のロックが使えます。種類をロック可能として設定します。

git lfs track "*.psd" --lockable

.gitattributes に lockable 属性が追加され、そのファイルは、ロックするまで作業ディレクトリで読み取り専用になります。

git lfs lock design/banner.psd
git lfs locks
design/banner.psd	anna	ID:123

# 変更を push した後
git lfs unlock design/banner.psd

ファイルがロックされている間、別の人がそのファイルへの変更を push しようとすると拒否されます。ロックはサーバーが管理するため、主要な Git ホスティングサービスでは利用できますが、自作のサーバーには機能がない場合があります。管理者は git lfs unlock --force で他人のロックを解除できます。

LFS のオブジェクトの保存場所と費用

LFS のオブジェクトは、LFS API に対応したサーバーに保存されます。主要な Git ホスティングサービスや、自前のサーバーで利用できます。クラウドのホスティングには、LFS のストレージ容量と転送量の上限があり、サービスやプランによって異なります。数百ギガバイトのデータセットを置く前に、必ず最新の条件を確認してください。

転送量は、ダウンロードのたびに消費されることに注意してください。すべてのコミットで LFS ファイルを含むリポジトリをクローンする CI は、人間よりも早く上限に達する可能性があります。ビルドの間は .git/lfs をキャッシュするか、GIT_LFS_SKIP_SMUDGE を使ってください。

ローカルのキャッシュも大きくなります。git lfs prune は、サーバーにすでにあり、現在および直近のコミットに必要ない古いオブジェクトを削除します。

Git LFS が向くケース、向かないケース

状況LFS が向くかコメント
ゲームのアセット、デザインデータ、音声、動画向く典型的なケース。頻繁に変わるバイナリ
ML のモデルやコードと一緒にある中規模のデータセット向くが、容量の上限に注意非常に大きなデータには、データのバージョン管理ツールが適している
コンパイル済みのビルド成果物向かないアーティファクトのレジストリやリリースに保存するべき
依存関係(node_modules、vendor)向かないパッケージマネージャーで取得するもの。.gitignore に入れる
大きなテキストファイル(SQL ダンプ、CSV)ケースによるGit はテキストを差分で効率よく保存する。LFS が必要になるのは、ファイルが非常に大きく、差分がほとんど読めない場合
秘密情報、秘密鍵決して使わないLFS はデータを暗号化しない。秘密情報の保管場所ではない

よくある問題と解決法

作業ディレクトリにファイルの代わりにテキストのポインタがある

LFS がインストールされていないか、GIT_LFS_SKIP_SMUDGE=1 を付けてクローンしています。git lfs install と git lfs pull を実行してください。

ルールを作る前に追加したファイルが通常の Git に入ってしまった

track のルールを git add の後に設定しました。コミットがまだ push されていなければ、ファイルをインデックスから外し(git rm --cached ファイル)、.gitattributes にルールがあることを確認してから、もう一度追加してください。すでに push 済みなら git lfs migrate import を使います。こうしたファイルは git lfs fsck --pointers で見つけられます。

ファイルサイズの制限で push が拒否される

多くのホスティングサービスは、通常の Git での1オブジェクトのサイズを制限しています。「this exceeds the file size limit」というメッセージは、そのファイルを LFS に入れる必要があったことを意味します。上で説明したように、最後のコミットや履歴を書き換えてください。単に「ファイルを削除する」コミットを作っても、以前のコミットには残ったままです。

git lfs migrate の後で同僚の環境が壊れた

同僚のローカルブランチは古いハッシュに基づいています。確実なのは、リポジトリを再度クローンすることです。未 push の作業がある場合は、cherry-pick で新しいブランチに移してください。

Git LFS のコマンド一覧

コマンド動作
git lfs installフィルターとフックを設定する
git lfs track "*.拡張子"パターンを .gitattributes に追加する
git lfs untrack "*.拡張子"パターンを削除する
git lfs ls-files現在のコミットにある LFS のファイルを表示する
git lfs pullオブジェクトをダウンロードして展開する
git lfs fetch --allすべての参照のオブジェクトをキャッシュに取得する
git lfs migrate import --include="..." --everything履歴のファイルを LFS に移す
git lfs lock / unlock / locksロックを操作する
git lfs pruneローカルキャッシュから古いオブジェクトを削除する
git lfs env設定を確認する

まとめ

Git LFS は、大きなバイナリファイルを履歴から切り離し、別のストレージに置いて、コミットには軽いポインタだけを残します。基本は、コミットの前に git lfs track を設定し、.gitattributes をコミットして、ホスティングの容量上限を監視することです。ファイルがすでに履歴に入っている場合は git lfs migrate で移行できますが、履歴の書き換えを伴うため、その影響を理解したうえで行ってください。履歴、rebase、その他の高度な Git の使い方は、Gitコースで実践的に扱っています。

よくある質問

Git LFS は無料ですか?

拡張機能そのものはオープンソースで無料です。ホスティングサービスの無料枠を超えて、ストレージや転送量を使った場合にのみ料金がかかります。

どのくらいのサイズからを大きいとみなして LFS に入れるべきですか?

明確な境界はありません。一般的には、頻繁に変わる数メガバイト以上のバイナリを LFS に入れます。テキストファイルは、大きくても LFS を使わずに Git が効率よく保存します。

Git LFS をやめて、ファイルを通常の Git に戻せますか?

はい。git lfs migrate export --include="*.psd" --everything を使うと、履歴を書き換えて元に戻せます。その後、.gitattributes からルールを削除し、強制 push を行います。

LFS のファイルの git diff は機能しますか?

Git が表示するのはファイルの内容ではなくポインタの変更です。バイナリファイルのため、画像や文書の比較には外部のツールを使います。

チームの全員に LFS が必要ですか?

はい。LFS がないメンバーの作業ディレクトリにはポインタだけが現れ、そのメンバーのコミットが知らないうちに大きなファイルを LFS の外に入れてしまうこともあるためです。

動画でこのテーマを学ぶ

このテーマはコース「Git:初心者から自信を持って使える開発者へ」で実践とともに詳しく解説しています。

コースへ