gitリポジトリに入れた巨大なファイルの履歴を削除する
前回のエントリ時にはかなり余裕のあるクラウドにリモートを移動したので,ほかの関連ファイルも一括でgit管理してしまおうと思っていたのだが,割と面倒なことになった.関連ファイルには10GBを超える動画ファイルがいくつか含まれており,それをプッシュしたところ,40GBを超える巨大なpackファイルなるものが生成された.リモートリポジトリの容量も巨大化した.
問題は,巨大な動画ファイル自体をローカルから削除してコミットしてプッシュしても,packファイルはほとんど変化せず,容量もまったく減らなかったことである.考えてみれば,任意の時点のファイルを復元できるということはどこかにその情報が保持されているわけで,いかに圧縮しようと,可逆性を保っている限りある程度のサイズになるのはやむをえないはずである.
巨大な過去のコミットたちと,それらを保持していると思われるpackファイル.コミットにもファイルとしての実体があるようだ.これまで考えたこともなかった.
$ ls -laR /path/to/repo.git/| awk '{if ($5 > 100000000) print $0}' -r--r--r-- 1 lingvisticae staff 106954354 Oct 17 11:06 e8c69bbf05a87871c5819e80ad3ef1e6e20f37 -r--r--r-- 1 lingvisticae staff 104546325 Oct 17 11:06 327871bad21d092f5096216943c5aec5ad4e6e -r--r--r-- 1 lingvisticae staff 693927392 Oct 17 11:07 27d5f7983a2d56b236b0828392d3e57aaac58f -r--r--r-- 1 lingvisticae staff 162961037 Oct 17 11:06 7e30e6b07a5d3163b3d00c40b711b8783085e4 -rwxr-xr-x@ 1 lingvisticae staff 2039711683 Oct 13 10:25 pack-22a545fc7ddb1c86f7b43a24a8afe6bc1f99e8c7.pack -rwxr-xr-x@ 1 lingvisticae staff 2059296845 Oct 17 10:53 pack-3c10cef7a46302b019aeeaad360b77c3aaef9b0f.pack -rwxr-xr-x@ 1 lingvisticae staff 2042554513 Oct 16 12:32 pack-663d10973d4894882a90b44f9f36a9fae6a75083.pack -rwxr-xr-x@ 1 lingvisticae staff 2042555488 Oct 16 12:49 pack-a3d364ff5d91f08a7658dd9cc9d3f6f2b5070472.pack -r--r--r-- 1 lingvisticae staff 48236415702 Oct 17 13:55 pack-b77d47905579c9d82cabcaed2a81c200ce82f28e.pack
直接の問題としては単一のファイルが巨大化したことによりクラウドのアップロード上限に達した(OneDriveは15GBまで)ためにデバイス間共有ができなくなったことだが,ローカルとリモートでの合計がもとの動画のサイズの3倍超に膨れ上がったことによるディスク容量の圧迫も無視できない問題になった.
ローカルにも巨大なpackファイルがある.tmp_pack_7fYZna
というのはコミットかプッシュの過程で生成されるファイルで,packファイルと同等のサイズがあり,このファイルの生成途中でハードウェアのディスク容量の上限に達してしまい,操作が完了できなくなった.
$ ls -laR repo/| awk '{if ($5 > 100000000) print $0}' -r--r--r-- 1 lingvisticae staff 51481809799 Oct 17 17:25 pack-89968939d94286ec10a24a9a112f8e1eee02e842.pack -r--r--r-- 1 lingvisticae staff 19198967808 Oct 17 16:49 tmp_pack_7fYZna
調べてみると,Githubにも単一ファイルのアップロード上限があるらしく,大きいファイルの対策に迫られることは割とあることのようだった.とはいえ10GB超のファイルを差分管理しようとする愚か者は見当たらなかったが…
qiita.com
sutara79.hatenablog.com
confluence.atlassian.com
ということでこれらの記事を参考に,適当にファイルを逃がして容量を空け,巨大なファイルの履歴を消していく.
手順1
まず上記のAtlassianのサイトから,git_find_big.sh
というシェルスクリプトをダウンロードする.どのファイルが問題なのか明らかな場合には必ずしもやらなくていい.ダウンロードしたら,ローカルリポジトリの一番上に移動して実行する.
cd /path/to/repo bash /path/to/git_find_big.sh
おそらくpackファイルが巨大であればあるほど走査に時間がかかるが,しばらくすると大きい方から上位10件が表示される.
$ bash /path/to/git_find_big.sh All sizes are in kB's. The pack column is the size of the object, compressed, inside the pack file. size pack SHA location 7996160 7985664 7e163546b1039166bc677445efb8a5018b46907b DATA/151205/20151205132024_1.mpeg 7992388 7981966 f8029a9997d9ac6e36dafc09b748bde9819ee99e DATA/151205/20151205132024_2.mpeg 7991920 7981168 7adbbf4a00c4490b1ceeb51919cd79af073e8a0c DATA/151205/20151205132024_3.mpeg 6902880 6709183 cbb79aa49d554d61456f8bc92b8cd99a4ef54534 DATA/151205/20151205193243.mpeg 3908014 3908504 db8bb6f781757ca620949b5803845d4fe63a6f31 DATA/運転練習データ/GP010006.MP4 2707417 2694039 f4f1b570d8458e3dbec285ef1bdc2ffaaca49e69 DATA/task/T014_018_eaf/T014_018_MIX.mp4 2072616 2072176 4bb8a6c45e727b5c36852d463593ec43b065b510 DATA/task/T001_002_eaf/T001_002_MIX.mp4 828483 827771 7c6004efcb3546b4fcc4685c6626f35fa268d29c DATA/割り込み?/割り込み.mov 727311 620069 03c97ee54cce7f0e3cf790936fdff8947a75439b DATA/task/T014_018_eaf/T014_018_IC0A.wav 727311 239959 225fd98a5874a46a26270794f092ed75ca984e70 DATA/task/T014_018_eaf/T014_018_IC02.wav
どのファイルを歴史から抹消すべきか判明したので,消去のコマンドを実行する.ローカルレポジトリの一番上のままで,
$ git filter-branch --index-filter 'git rm --ignore-unmatch DATA/151205/20151205132024_1.mpeg' --tag-name-filter 'cat' -- --all
詳しいコマンドの解説はこちらにあるが,git filter-branch <command>
で対象となるコミットにcommandを実行,そのコマンドがgit rm --ignore-unmatch <file>
である.その他のオプションは,コミット名やタグを上書きするなど.
実行後しばらくはファイル数と時間が書き換わって進捗が表示されるが,終わり際には行が増えるようになる.ちなみにファイルが巨大で,かつ自動コミットのせいでコミットの数も膨大なので,一つのファイルの履歴を消すのに何時間もかかった.
$ git filter-branch --index-filter 'git rm --ignore-unmatch DATA/151205/20151205132024_1.mpeg' --tag-name-filter 'cat' -- --all Rewrite 550f5fb201c4e0e9b3f896e3a9140247b528ecf5 (2062/2098) (6483 seconds passed, remaining 113 predicted) rm 'DATA/151205/20151205132024_1.mpeg' Rewrite 4e50c358332cdb1a989ff88aa3361a175a47326c (2063/2098) (6485 seconds passed, remaining 110 predicted) rm 'DATA/151205/20151205132024_1.mpeg' Rewrite 661bf83edaf4815b88dc1e815ebd7e4509aff8d5 (2064/2098) (6488 seconds passed, remaining 106 predicted) rm 'DATA/151205/20151205132024_1.mpeg' Rewrite c55a58ecd3389ffc353c58815e681d6afdf5cfb7 (2065/2098) (6489 seconds passed, remaining 103 predicted) rm 'DATA/151205/20151205132024_1.mpeg' Rewrite e54f6f2dfac36563828575c53875840582637746 (2066/2098) (6491 seconds passed, remaining 100 predicted) rm 'DATA/151205/20151205132024_1.mpeg' Rewrite 02e20a9328284b658e43b31b905a8de6b271e19b (2067/2098) (6493 seconds passed, remaining 97 predicted) rm 'DATA/151205/20151205132024_1.mpeg' Rewrite 52943f3967932981a7a3f3e296fb6ebd8567d5b8 (2068/2098) (6495 seconds passed, remaining 94 predicted) rm 'DATA/151205/20151205132024_1.mpeg' Rewrite 48c1de8c0c30f3115aa80a1c82bdf3ad589280fb (2069/2098) (6496 seconds passed, remaining 91 predicted) rm 'DATA/151205/20151205132024_1.mpeg' Rewrite fcdcf918dfa5dae27ffe3263664cd790f04cad2e (2070/2098) (6498 seconds passed, remaining 87 predicted) rm 'DATA/151205/20151205132024_1.mpeg' Rewrite ce3759980539c0faf6769309ea2463b0e94320d0 (2071/2098) (6501 seconds passed, remaining 84 predicted) rm 'DATA/151205/20151205132024_1.mpeg' Rewrite d3ccc268289bcb5a79e204126cf42ed14031ec25 (2072/2098) (6503 seconds passed, remaining 81 predicted) rm 'DATA/151205/20151205132024_1.mpeg' Rewrite 6008b5c9ee5771156dbaac889e60536388ba34dd (2073/2098) (6505 seconds passed, remaining 78 predicted) rm 'DATA/151205/20151205132024_1.mpeg' Rewrite 7f9dd3109bc228cba0b7bbf430500aa5e470d39a (2074/2098) (6506 seconds passed, remaining 75 predicted) rm 'DATA/151205/20151205132024_1.mpeg' Rewrite 52ef1320e5f1bd71b97141ba6a4a6d2a3cf4716c (2075/2098) (6508 seconds passed, remaining 72 predicted) rm 'DATA/151205/20151205132024_1.mpeg' Rewrite 6928570b778c18d3eec45ceeb7bc308c4bd2e9a0 (2076/2098) (6510 seconds passed, remaining 68 predicted) rm 'DATA/151205/20151205132024_1.mpeg' Rewrite 2f8f04150059810abe68238851906e0bd0be093b (2077/2098) (6512 seconds passed, remaining 65 predicted) rm 'DATA/151205/20151205132024_1.mpeg' Rewrite 62175087c45ad4020bf8593625cd0265a141243d (2078/2098) (6514 seconds passed, remaining 62 predicted) rm 'DATA/151205/20151205132024_1.mpeg' Rewrite dab1ba1509d113e652b34ba242318e9d5ecadc86 (2098/2098) (6545 seconds passed, remaining 0 predicted) Ref 'refs/heads/master' was rewritten Ref 'refs/remotes/origin/master' was rewritten
ついで,当該ファイルのすべての参照を消し,無効なreflogを消し,ガベージコレクションする.
$ git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d $ git reflog expire --expire=now --all $ git gc --prune=now
ちなみに1度目には,この操作を実行しながら,自動コミットを止めず,このレポジトリの中で別の作業をしていたため,レポジトリの歴史改変と新規コミットが同時に行われることになった.何がどう悪かったのかはわからないが,結果として以下のようなメッセージが出て,履歴消去は成功しなかった.自動コミットを止め,作業ディレクトリをレポジトリ外に退避し,また何時間もかかる操作を再実行する羽目になった.
WARNING: Ref 'refs/heads/master' is unchanged WARNING: Ref 'refs/remotes/origin/master' is unchanged
成功するとgit_find_big.sh
には消したファイルが出なくなる.
最終的に操作が完了したら,git push --all --force
git push --tag --force
でリモートに改変した歴史を反映する.うまくいかなければリモートを作り直す.
手順2
原理的には上記の方法で,対象とするファイルをひとつひとつ消していけばいい.金曜の夜に適当にシェルスクリプトを回して帰宅すれば,きっと月曜の朝には終わっているに違いない.
for i in `bash git_find_big.sh| awk '{print $4}'` do git filter-branch --index-filter "git rm --ignore-unmatch $i" --tag-name-filter 'cat' -- --all git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d git reflog expire --expire=now --all git gc --prune=now done
だが,上記のQiitaでも紹介されているように,このくらいの単純な条件での消去であればもっと素早く終える方法がある.
ダウンロードしたbfg-1.13.0.jar
を実行する.実行場所はどこでもよく,ローカルを引数に取る.オプション--strip-blobs-bigger-than
にメガバイト単位で削除対象ファイルのサイズ下限を指定する.
$ java -jar /path/to/bfg-1.13.0.jar --strip-blobs-bigger-than 400M /path/to/repo/ Using repo : /Users/lingvisticae/repo/../repo/.git Scanning packfile for large blobs: 16924 Scanning packfile for large blobs completed in 532 ms. Found 15 blob ids for large blobs - biggest=8184205312 smallest=457097216 Total size (unpacked)=35647620530 Found 3184 objects to protect Found 3 commit-pointing refs : HEAD, refs/heads/master, refs/remotes/origin/master Protected commits ----------------- These are your protected commits, and so their contents will NOT be altered: * commit a017320a (protected by 'HEAD') Cleaning -------- Found 2098 commits Cleaning commits: 100% (2098/2098) Cleaning commits completed in 441 ms. Updating 2 Refs --------------- Ref Before After ------------------------------------------------ refs/heads/master | a017320a | d765a52f refs/remotes/origin/master | ee062ba0 | b1563980 Updating references: 100% (2/2) ...Ref update completed in 14 ms. Commit Tree-Dirt History ------------------------ Earliest Latest | | ..........................................................DD D = dirty commits (file tree fixed) m = modified commits (commit message or parents changed) . = clean commits (no changes to file tree) Before After ------------------------------------------- First modified commit | 46ac3103 | a1c7efed Last dirty commit | 8257f140 | 289b5708 Deleted files ------------- Filename Git id ------------------------------------------- 20151205132024_2.mpeg | f8029a99 (7.6 GB) 20151205132024_3.mpeg | 7adbbf4a (7.6 GB) 20151205132024_4.mpeg | cfb88c78 (435.9 MB) 20151205193243.mpeg | cbb79aa4 (6.6 GB) C001_002_MIX.mp4 | b127d5f7 (661.9 MB) GP010006.MP4 | db8bb6f7 (3.7 GB) T001_002_IC01.wav | ee84c96c (543.9 MB) T001_002_IC02.wav | ad898d79 (543.9 MB) T001_002_IC0A.wav | a6b8e1b5 (543.9 MB) T001_002_MIX.mp4 | 4bb8a6c4 (2.0 GB) T014_018_IC01.wav | 3a2e8a8b (710.3 MB) T014_018_IC02.wav | 225fd98a (710.3 MB) T014_018_IC0A.wav | 03c97ee5 (710.3 MB) T014_018_MIX.mp4 | f4f1b570 (2.6 GB) 割り込み.mov | 7c6004ef (809.1 MB) In total, 72 object ids were changed. Full details are logged here: /Users/lingvisticae/repo/../repo.bfg-report/2019-10-17/21-32-11 BFG run is complete! When ready, run: git reflog expire --expire=now --all && git gc --prune=now --aggressive -- You can rewrite history in Git - don't let Trump do it for real! Trump's administration has lied consistently, to make people give up on ever being told the truth. Don't give up: https://www.theguardian.com/us-news/trump-administration --
ものの1秒もかからず終了した.
終了したら同様に参照の処理をする.
$ git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d $ git reflog expire --expire=now --all $ git gc --prune=now