My Note Pad

Qiitaに書くほどでもない技術的なことや日々の雑感を書いていきます

BFGを使用するにあたって色々調べてみた

BFGとは

rtyley.github.io

git-filter-branchの代わりに使うことができる(事もある)

概要

  • クレイジーな巨大なファイルをgitリポジトリから削除できる
  • パスワード、資格情報、プライベートな情報も削除できる
  • scalaで実装されており、実行にはjavaが必要

git-filter-branchと比較して

  • 10 - 720倍高速に動作する
  • よりシンプル
  • コードが公開されているのでカスタマイズして使用できる

使い所

  • git-filter-branchのオプションが結構複雑なのでシンプルに実行したいとき
  • git-filter-branchでは時間がかかりすぎて終りが見えないとき

といった感じ。

各オプションの詳細はこちら

https://repository.sonatype.org/service/local/repositories/central-proxy/content/com/madgag/bfg/1.12.14/bfg-1.12.14.txt

使用してみる

BFGの実行ファイルダウンロード

# Homebrewでも入れられるらしい
$ wget http://repo1.maven.org/maven2/com/madgag/bfg/1.12.14/bfg-1.12.14.jar

$ java -version
java version "1.8.0_102"
Java(TM) SE Runtime Environment (build 1.8.0_102-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.102-b14, mixed mode)

まずは検証用に適当なリポジトリを作成。

空のリポジトリを作成

# bareリポジトリ
$ mkdir hoge_bare.git
$ cd hoge_bare.git
$ git init --bare
Initialized empty Git repository in /Users/USER_NAME/Documents/bfg_test/hoge_bare.git/


# gitリポジトリ
$ cd ..
$ mkdir hoge
$ cd hoge
$ git init
Initialized empty Git repository in /Users/USER_NAME/Documents/bfg_test/hoge/.git/


# bareをremoteとして登録
$ git remote add origin ../hoge_bare.git
$ git remote -v
origin  ../hoge_bare.git (fetch)
origin  ../hoge_bare.git (push)


# 適当な画像を置いてcommit push
$ git add .
$ git commit -m "initial commit"
[master (root-commit) 14668fc] initial commit
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 1.png
 
$ git push origin master                                                                                                        (master)
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 54.77 KiB | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To ../hoge_bare.git
 * [new branch]      master -> master

適当にブランチを切ったりしてワークツリーを作成し、remoteにpushしておく
ここでは1コミットにpngファイルを1つコミットしている。

f:id:yuki10k:20170113193403p:plain

実際にやってみる

パターン①:特にオプションを付けない(png指定のみ)

# bareからmirrorでcloneする
$ git clone --mirror hoge_bare.git hoge_test1.git
$ java -jar bfg-1.12.14.jar --delete-files '*.png' hoge_test1.git
$ cd hoge_test1.git
$ git reflog expire --expire=now --all && git gc --prune=now --aggressive

$ cd ..
$ git clone hoge_test1.git hoge_result1

オプションなしの場合、HEADが指しているブランチのみプロテクトの対象となり、最新のコミットが保持される。

masterでは最初のコミットと、5番目のコミットで追加されたpngファイルが残っており、一番最初に追加した画像が5番目のコミットで追加したことになっている。

f:id:yuki10k:20170113193458p:plain

hogeブランチやfugaブランチは何も無かったことに。

f:id:yuki10k:20170113193507p:plain

masterの1番最初のコミットでの画像追加が無かったことに。

f:id:yuki10k:20170113193514p:plain

パターン②:--no-blob-protectionオプションを付けてみる

このオプションは対象ファイルを履歴上からも全て消し去る。

# bareからmirrorでcloneする
$ git clone --mirror hoge_bare.git hoge_test2.git
$ java -jar bfg-1.12.14.jar --no-blob-protection --delete-files '*.png' hoge_test2.git
$ cd hoge_test2.git
$ git reflog expire --expire=now --all && git gc --prune=now --aggressive

$ cd ..
$ git clone hoge_test2.git hoge_result2

消え去っている。
master以外のブランチからも削除されている。

f:id:yuki10k:20170113194056p:plain

完全に消し去りたい時はこれで良さそう。

パターン③:--protect-blobs-fromオプションを付けてみる

--protect-blobs-fromでは、プロテクトするブランチを指定できる。
master, hogeブランチをプロテクト対象にする。

# bareからmirrorでcloneする
$ git clone --mirror hoge_bare.git hoge_test3.git
$ java -jar bfg-1.12.14.jar --protect-blobs-from master,hoge --delete-files '*.png' hoge_test3.git
$ cd hoge_test3.git
$ git reflog expire --expire=now --all && git gc --prune=now --aggressive

$ cd ..
$ git clone hoge_test3.git hoge_result3

masterは、パターン①と同様で、masterブランチで追加したファイルが最新のコミットにまとめらている。

f:id:yuki10k:20170113194304p:plain

hogeブランチは、masterからの派生時に存在した1.pnghogeブランチで追加した画像2つが最新のコミットでaddされたことになっている。

f:id:yuki10k:20170113194307p:plain

当然、2.pngを追加した時点のコミットは空コミットに。

f:id:yuki10k:20170113194315p:plain

fugaブランチはプロテクトの対象にしなかったので、画像は追加されない。

f:id:yuki10k:20170113194310p:plain

masterにhogeブランチとfugaブランチをそれぞれマージしてみる。

fugaブランチのコミット履歴上、何も追加していないことになっているので、fugaブランチのマージ履歴は空に。

f:id:yuki10k:20170113194318p:plain

hogeブランチのマージでは、2.png, 4.pngが正しくマージされる。
1.pnghogeの最新コミット履歴で追加したことになっていたけど、ここでコンフリクトなどは発生しない。

f:id:yuki10k:20170113194322p:plain

最終的なmasterブランチでは、fugaブランチで追加した画像ファイル以外の画像がちゃんと残っている。

f:id:yuki10k:20170113194326p:plain

その他注意点

  • --delete-foldersはパスの区切り文字を使えず、フォルダ名のみで探して削除するので、同名のフォルダが違うパスで存在する場合、両方とも削除されてしまう。(未検証)
  • 特定のフォルダ以下を対象にするといった事はできない。(未検証)
  • bfg自体はかなり高速に動作するものの、その後のgit gc --prune=now --aggressiveはファイル数やサイズによっては結構時間がかかる。
  • それでもgit-filter-branchより速い
  • CPUコア数でかなり差が出るのでMac Book Pro 13inchとかだと結構辛い

雑感

  • filter-branchと比べると実際にやっていることはシンプル。
  • バイナリファイルをまとめて消し去りたいときには、--no-blob-protectionを付けて実行するときれいにできるのでgit lfsに移行したい時なんかには重宝しそう。
  • 対象ファイルをプロテクトした場合、最新のコミット履歴で追加されたように見えるので、後で履歴を見直したときには何だこれ?となりそう。