Git LFSからファイルを削除してStorageを解放する


Git(というかGithub)の機能にGit Large File Storage(以下Git LFS)というものがあります。

GitHubでは、1ファイル100MB(50MBで警告)という制限があります。
ではそれを超過したファイルはどうするのか?というと、Git LFSという場所に保管します。

しかし、Git LFSはストレージ容量1GB、転送量1GB/月という制限があり、それを超えるとデータパックを購入する必要があり、また過去のコミットのファイルはいつまでも保持されるため、すぐに足りなくなってしまいます。

私はちょっと大きいファイルをGit LFSで管理していたのですが、Git LFSで管理する必要がなくなって削除したいな~と思い、方法を調べてみたのですが、かなり面倒だったので、備忘録として書いておきます。

原則削除できない

まず、Githubには、Git LFSのファイルを削除するような機能はありません。
Git LFSのStorageを解放するためには、過去のコミットまで遡ってファイルを削除する必要があります。

しかし

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

Git LFS オブジェクトをリポジトリから削除するには、リポジトリを削除して再作成します。 リポジトリを削除すると、関連する Issue、Star、フォークもすべて削除されます。
ファイルを Git Large File Storage から削除する - GitHub Docs

と公式ドキュメントにあるように、過去のコミットからファイルを削除したとしても、リポジトリがある限り、ファイルは残り続けます。

つまり、Git LFSのStorageを解放するには

  • Git LFSの追跡を無効にして
  • 過去のコミットからGit LFSに保存しているすべてのファイルを削除して
  • Githubのリポジトリを削除・新規作成

する必要があります。

めんどくさ!

ただ、このままずっと使わないLFSにお金を払い続けるのは嫌なので、手順を紹介したいと思います。

Git LFSのStorageを解放する

今回の環境はWindows 11を使用していますが、MacOSやLinuxでも同様にできます。
後述するBFGはMacだとBrewで簡単に入るらしいので、Macの方は調べてみてください。

Git LFSの追跡を無効化する

.gitattributes に書いてある追跡ルールをすべて削除します(ファイルごと消してもOK)
また、Git LFSで管理していたファイルも削除します(100MB以下であれば削除不要)

そしてそれらの変更をコミットし、リモートにpushします。

コミットからファイルを削除する

それでは過去のコミットからファイルを削除します。

git filter-repo というコマンドでもできるのですが、ディレクトリの指定やその他いろんな機能があり便利なBFG Repo-Cleanerを今回は使用します。

(ちなみに公式ドキュメントでもおすすめされています)

BFGはJava 8以降が導入されている必要があるので、入っていなければインストールします。

まずはBFGを公式サイトよりダウンロードします。

適当なフォルダを作り、そこにダウンロードしたBFG(今回は bfg-1.14.0.jar)を入れます。

次に

git clone --mirror https://github.com/hoge/fuga.git

を実行し、mirrorオプションを付けてリポジトリをクローンします。

lfs-clean/
├─ fuga.git/
├─ bfg-1.14.0.jar

それではコミット履歴からファイルを削除します。
以下のコマンドを実行します。

// ファイルを消す場合
java -jar .\bfg-1.14.0.jar --delete-files fileA fuga.git

// フォルダごと消す場合
java -jar .\bfg-1.14.0.jar --delete-folders folderA fuga.git

複数指定する場合は "{fileA,fileB,fileC}" のように指定します。

BFG run is complete! When ready, run: git reflog expire --expire=now --all && git gc --prune=now --aggressive

と表示されれば完了です。

Protected commits

Protected commits
-----------------

These are your protected commits, and so their contents will NOT be altered:

 * commit xxxxxxxx (protected by 'HEAD') - contains 100 dirty files :
        - example/test.webm (132 B )
        - example/test2.webm (132 B )
        - ...

WARNING: The dirty content above may be removed from other commits, but as
the *protected* commits still use it, it will STILL exist in your repository.

Details of protected dirty content have been recorded here :

C:\Users\hoge\Desktop\lfs-clean\fuga.git.bfg-report\xxxx-xx-xx\xx-xx-xx\protected-dirt\

If you *really* want this content gone, make a manual commit that removes it,
and then run the BFG on a fresh copy of your repo.

このように、Protected commitsにファイルが表示された場合、削除されずに残ったファイルがあります。

Git LFSの追跡を無効化する の項で削除対象のファイルを削除していなかった場合、GitのHEADにファイルが残っているためこのような警告が出ます。

BFGでは、HEADにあるファイルは削除されない(Protectされる)仕様になっているためです。

意図してファイルを削除しなかった場合は、この警告は無視して大丈夫です。

新しいリポジトリにpushする

あらかじめGithubで新しいリポジトリを作成しておきます。
今回は https://github.com/hoge/piyo.git が新しいリポジトリとします。

cd fuga.git
git remote remove origin
git remote add origin https://github.com/hoge/piyo.git
git reflog expire --expire=now --all | git gc --prune=now --aggressive
git push --set-upstream origin master

これで新しいリポジトリに、Git LFSが削除されたコミットがpushされます。

古いリポジトリを削除する

古いリポジトリは、必要なIssueやWikiなどを手動で移行し削除します。
これでGit LFSのStorageが解放されます。

そもそも

Git LFSは安易に使うもんじゃないですね。
デカいファイルは社内サーバーやDropboxとかのストレージで管理しましょう。

今回の場合は100MBもないファイルをLFSにあげてたみたいで、普通にアホでしたね。

Git BucketなどにはLFSからファイルを消す機能があるみたいで、Githubもつけてくれよ、とは思いました。

おわり

悲しいね