過去のコミットでgit LFSを使うように歴史改変をする
「Subversionからgitに移行したはいいが、履歴があまりにでかいのでPDFを別管理にしたい」という状況を考える。でかいデータやバイナリを別管理にするといえばgit LFS, 昔の履歴に遡って変更を加えるにはgit filter-branchであるが、これらを組み合わせる方法を以下に述べる。
全体の手順としては以下の通り:
- 使っているサーバーがLFSに対応しているかどうか確認する(GitHubなら無課金では1Gまでしか使えない、など)
- Git LFSはgitとは別なので、別途インストールする (Ubuntuならpackagecloudからインストールできる)
- filter-branchを炸裂させる
- リポジトリを掃除する
filter-branchを炸裂させる
# 全てのタグやブランチに対して、 *.pdfや*.keyをLFSで管理するように歴史改変する git filter-branch --tree-filter "git lfs track \"*.pdf\" \"*.key\" >/dev/null" --index-filter "rm .gitattributes" --tag-name-filter cat -- --all
これの動作を解説する。まず git lfs track
は .gitattributes
を書き換える。このファイルに書かれている種類のファイルだけがgit LFSの管理下に置かれる。中身が同じでも、gitの管理下のファイルとgit LFSの管理下のファイルは扱いが異なるため、 git add
にてそれらのファイルを追加する必要がある。
そもそもgit filter-branchは、元のコミットをそれぞれ取り出して変更を加え、それらを繋ぎあわせて新しい歴史を作るというものである。このとき、コミットの変更処理のうち今回使う部分を抜き出すと以下のようになる。
- 専用の
t
というディレクトリに、コミットの中身をぶち撒く。 (git checkout-index) - 逆に、コミットに入っていないファイルが残っていたら、それを削除する。 (git clean)
--tree-filter
で指定されたコマンドを実行する。t
に入っているファイルからインデックスを再構成する。 (git update-index)--index-filter
で指定されたコマンドを実行する。- コミットする。 (git write-tree, git commit-tree)
つまり、 tree-filter
で用意された箇所で .gitattributes
を変更するだけでうまくいきそうな感じがあるが、これだけだと Pointer file error: Unable to parse pointer
という謎のエラーが出てくる。
これは、 t
ディレクトリに .gitattributes
が残った状態で次の処理が走るからである。このcheckout-index処理で、git LFSは取り出されたポインタファイルを中身のあるファイルに変換する処理を行う。このとき、正しくない .gitattributes
が残っているためにこのエラーが発生する。
そこで、 index-filter
で .gitattributes
を削除している。本来の index-filter
の使い方ではないが、うまく動作する。なお、元のコミットに .gitattributes
がある場合も、 git checkout-index
のときにそれが取り出されるので特に問題はない (はず) 。
末尾にある --tag-name-filter cat -- --all
は変換対象を指定している。 --tag-name-filter cat
はタグ名をcatで変換する、つまり何もしないように見えるが、これを指定することでタグも変換対象になる。 --all
は git rev-list
のオプションで、全てのrefを指定している。
特定のブランチだけを書き換えるなら、例えば -- mybranch
のようにすればよい。
リポジトリを掃除する
LFSがない状況で一番手っ取り早いのは file:///path/to/repository
を別の場所にcloneすることであった。ここで file://
をつけることでgitは .git/objects
の中身をハードリンクするなどの横着なしに必要なオブジェクトを取得する。これをしないとpackされたでかいファイルなどがそのままついてきてしまう。
ただLFSの場合はLFSのオブジェクトがついてこないので意味がない。どうせLFSを使うときはサーバーがある状況なので、そのサーバーにpushしてしまうのがよいと思う。
まとめ
頑張って調べたが結局今回は使わないことになった。ぜひ誰か役立ててほしい。