Gitリポジトリのメンテナンス

GitはSVNのように部分的にチェックアウトすることができないので、Gitリポジトリが肥大化するとリモート操作に時間がかかってしまう。またうっかりごみデータをコミットしてしまったときも含めて、肥大化したリポジトリを圧縮する方法の備忘録を残す。

注意点

  • 以下の方法をとると、コミットIDが書き換わってしまい、リモートリポジトリとの
    通常のやりとり(push, pull, fetch etc..)ができなくなる。
    必要な更新があれば、先に済ませておく必要がある。

  • 最終的に、git push -fで強制的かつ強引にリモートリポジトリを書き換えることになる。なので、やる前にgit cloneでバックアップを取っておく。

Gitオプションfilter-branch

このオプションを使うと、指定したファイルやディレクトリを機械的に履歴をさかのぼって削除することができる。

> git filter-branch --index-filter "git rm -rf --cached --ignore-unmatch <ファイル名orフォルダ名>" --prune-empty -- --all

まず最初に削除前のサイズを調べる。
61MBほどのリポジトリであることがわかる。

> du -sh .git
61M     .git

ためしに6.8MBほどのファイル(hoge_list.xlsx)を削除してみる。
ちなみにこのコマンドは.gitフォルダがある最上位ディレクトリで実行する必要がある。

> git filter-branch -f --index-filter "git rm -rf --cached --
ignore-unmatch docs/hoge/hoge_list.xlsx" --prune-empty -- --all

Rewrite 8d00f87c1744c7aa28b53819f47b738149b2a186 (1/5) (0 seconds passed, remaining 0 predicted)    rm 'docs/hoge/hoge_list.xlsx'
Rewrite 8d081511cc0224cefcd6fc0df8cf0aaf1ade9e27 (2/5) (1 seconds passed, remaining 1 predicted)    rm 'docs/hoge/hoge_list.xlsx'
Rewrite 0541f257b1182f2349ee47eac303cae3e2498902 (2/5) (1 seconds passed, remaining 1 predicted)    rm 'docs/hoge/hoge_list.xlsx'
Rewrite 76a9327f3f7bc2b6fe979793d42e4d2c5704f985 (2/5) (1 seconds passed, remaining 1 predicted)    rm 'docs/hoge/hoge_list.xlsx'
Rewrite ae179c8cb33c852f6d1bbf04155cea9748f68e58 (5/5) (4 seconds passed, remaining 0 predicted)    rm 'docs/hoge/hoge_list.xlsx'

Ref 'refs/heads/master' was rewritten
Ref 'refs/remotes/origin/master' was rewritten
WARNING: Ref 'refs/remotes/origin/master' is unchanged

これだけでは完璧ではなく、git gcでゴミとなった履歴を削除する。

> git gc --aggressive --prune=now

Counting objects: 245, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (241/241), done.
Writing objects: 100% (245/245), done.
Total 245 (delta 46), reused 187 (delta 0)

最後に強制的にリモートリポジトリへ反映する

> git push --all --force
Counting objects: 195, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (161/161), done.
Writing objects: 100% (195/195), 10.10 MiB | 390.00 KiB/s, done.
Total 195 (delta 32), reused 188 (delta 31)
remote: Resolving deltas: 100% (32/32), done.
To project.usagi1975.com:work/pic
 + ae179c8...97630a1 master -> master (forced update)

GitLabだと以下のエラーメッセージが表示されて強制pushできないケースがある。この場合、GitLabにログインして、Protected Branchesの設定でmasterをUnprotectしておく。

remote: GitLab: You are not allowed to force push code to a protected branch on this project.

これで、削除後のリポジトリを強制pushが完了する。

ちなみにこの状態で、ローカルの.gitリポジトリのサイズを確認してもほとんど減らないかもしれない。サイズの大きいファイルを探すと、.git/objects/packディレクトリ下にあるファイルであることが原因。どんなファイルかはよくわかってないが、リモートリポジトリからクローンしたときに作成されるオリジナルファイルの塊のようなもの。たぶんこの中に不要なファイルが残っているからと思われる。

仕方がないので、もう一度、リモートリポジトリからgit cloneする必要があり、このクローンされたリポジトリは削減後のリポジトリになっている。

ちなみに以下は、ほかのファイルも上記手順で削除&強制push後のクローンになる。

> git clone hoge.com:hoge/repo
:
> cd repo
> du -sh .git
11M   .git

いろいろ調査したものの、どの方法も上記のような手順が多い。しかもさくっと消えないのがちょっと腑に落ちない。

  • GITはSVNのように差分管理しない。更新されると丸ごとコピーしたものが履歴として残る。なので、そもそも大きいファイルをむやみにコミットしない。

  • どうしても大きいファイルを管理する場合、更新頻度の高いリポジトリ以外で管理する。

  • リポジトリは関連するファイル群でまとめたくなるが、更新頻度とサイズを踏まえてリポジトリ構成を考える。

  • リポジトリのネスト化サブモジュールをリポジトリ設計として検討する。

次回、上記を踏まえて、Gitリポジトリの構成をどうしたらいいかをもう少し検討する予定。