ブランチを削除しただけではオブジェクトは削除されない。git は適当なタイミングで unreachable objects を削除する努力をしてくれるので、何もしなくても到達できなくなったオブジェクトは自動的に削除される。しかしながらこれを手作業で行うとgitのオブジェクトの状態とそれを操作する方法の理解が深まるような気がする。
ケーススタディ
リポジトリを作成。
$ git init Initialized empty Git repository in /*****************************/.git/
github という名前でリモートリポジトリを登録。
$ git remote add github https://github.com/**********************************.git
github リポジトリの内容を取得。取得されたデータサイズは 1.90 GiB、ブランチは開発用のmasterと成果物の配布用にgh-pagesがある。
$ git fetch github remote: Counting objects: 296779, done. remote: Total 296779 (delta 0), reused 0 (delta 0), pack-reused 296779 Receiving objects: 100% (296779/296779), 1.90 GiB | 9.10 MiB/s, done. Resolving deltas: 100% (288419/288419), done. From https://github.com/********************************** * [new branch] gh-pages -> github/gh-pages * [new branch] master -> github/master $ git branch --remotes --verbose --verbose remotes/github/master ******* remotes/github/gh-pages ******* ***************
git count-objects の出力は KiB 単位なので、du --block-size=1K で du の出力も KiB 単位にして .git ディレクトリのサイズを確認。リポジトリのオブジェクトサイズ確認。loose objects (packfile 化されていないオブジェクト) に関する値の count、size、prune-packable がゼロであり、garbage files に関する値の garbage、size-garbage がゼロであることを確認。この状態で、git prune (unreachable objects になっている loose objects を削除) や git prune-packed (packfile 化の完了している loose objects を削除) しても削除されるオブジェクトはないはず。これも確認。さらに、git fsck で unreachable object が存在しないことも確認 (リポジトリのサイズに依存するものの多少時間がかかることを覚悟した方がいい。今回の場合6分程度)。
$ du --block-size=1K --summarize .git 2009304 .git $ git count-objects --verbose count: 0 size: 0 in-pack: 296779 packs: 1 size-pack: 2006942 prune-packable: 0 garbage: 0 size-garbage: 0 $ git prune --dry-run $ git prune-packed --dry-run $ git fsck --unreachable notice: HEAD points to an unborn branch (master)
ここで、手元のリポジトリから remotes/github/gh-pages ブランチおよびそれを構成するオブジェクトを削除したい。まずは remotes/github/gh-pages ブランチを削除。この時に削除されるのは、******* というコミットハッシュに remotes/github/gh-pages ブランチという名前を与えていたという対応関係だけなので、オブジェクトは削除されない。
$ git branch --delete --remote github/gh-pages Deleted remote branch github/gh-pages (was *******). $ git branch --remotes --verbose --verbose remotes/github/master *******
ブランチ削除後のサイズ確認。ブランチを削除したことによる重要な変化は unreachable objects ができて、それらがまだ packfile の中に含まれていること。git prune が削除するのは loose objects かつ unreachable objects の両方を満たすオブジェクト。すなわち、packfile 内の unreachable objects は git prune では削除されない。また、git prune-packed が削除するのは packfile 内外の両方に含まれる loose objects。
$ du --block-size=1K --summarize .git 2009296 .git $ git count-objects --verbose count: 0 size: 0 in-pack: 296779 packs: 1 size-pack: 2006942 prune-packable: 0 garbage: 0 size-garbage: 0 $ git prune --dry-run $ git prune-packed --dry-run $ git fsck --unreachable notice: HEAD points to an unborn branch (master) (snip) unreachable blob **************************************** (snip) unreachable commit **************************************** (snip)
packfile 内に含まれる unreachable objects を削除するには git gc を使う (git unpack-objects、git prune、git repack でもいい?)。git gc は unreachable objects を packfile から削除するものの、loose objects の形で残すので、git gc のあとに loose objects にされた unreachable objects を削除するために git prune するか、 --prune=all を付けて git gc する必要がある。ここでは前者の方針を採る(git gc にかかった時間は今回の場合44分程度)。
$ git gc Counting objects: 13569 Counting objects: 14505, done. Delta compression using up to 2 threads. Compressing objects: 100% (2817/2817), done. Writing objects: 100% (14505/14505), done. Total 14505 (delta 11686), reused 14495 (delta 11679) Removing duplicate objects: 100% (256/256), done.
git gc したことによる素性変化を確認。git gc による重要な変化は git prune で削除される予定のオブジェクトがリストアップされるようになった。すなわち、削除予定のオブジェクトは loose objects になっている (この時の git fsck --unreachable は19分程度)。
$ du --summarize --block-size=1K .git 12117884 .git $ git count-objects --verbose count: 282274 size: 11986304 in-pack: 14505 packs: 1 size-pack: 110780 prune-packable: 0 garbage: 0 size-garbage: 0 $ git prune --dry-run --verbose (snip) **************************************** blob (snip) **************************************** commit (snip) **************************************** tree (snip) $ git prune-packed --dry-run $ git fsck --unreachable notice: HEAD points to an unborn branch (master) (snip) unreachable blob **************************************** (snip) unreachable commit **************************************** (snip)
git gc は unreachable objects を loose objects にするので、git gc 直後にできた loose objects を削除したい。これを行うコマンドが git prune。
$ git prune
git prune したことによるオブジェクトの変化を確認。これで巨大なオブジェクトを削除することが完了した。
$ du --block-size=1K --summarize .git 111220 .git $ git count-objects --verbose count: 0 size: 0 in-pack: 14505 packs: 1 size-pack: 110780 prune-packable: 0 garbage: 0 size-garbage: 0 $ git prune --dry-run --verbose $ git prune-packed --dry-run $ git fsck --unreachable notice: HEAD points to an unborn branch (master)
オブジェクト | $ git \ count-objects \ --verbose; |
直前コマンド | ||||
---|---|---|---|---|---|---|
loose | inpack | 項目名 | $ git \ branch \ --delete \ --remote \ github/gh-pages; |
$ git \ gc; |
$ git \ prune; |
|
Yes | No | count (num) | 0 | 282274 | 0 | |
size (KiB) | 0 | 11986304 | 0 | |||
No | Yes | in-pack (num) | 296779 | 14505 | 14505 | |
packs (num) | 1 | 1 | 1 | |||
size-pack (KiB) | 2006942 | 110780 | 110780 | |||
Yes | Yes | prune-packable (num) | 0 | 0 | 0 | |
No | No | garbage (num) | 0 | 0 | 0 | |
size-garbage (KiB) | 0 | 0 | 0 |
先に述べたとおり、git gc と git prune の代わりに git gc --prune=all を実行することも可能。これはかなりの時間節約になる (今回の場合だと前者にかかる時間は約1時間、後者にかかる時間は約10秒、すなわち 1/360 程度になる)。最終結果は同じ。
$ git gc --prune=all Counting objects: 14505, done. Delta compression using up to 2 threads. Compressing objects: 100% (2817/2817), done. Writing objects: 100% (14505/14505), done. Total 14505 (delta 11686), reused 14495 (delta 11679) $ du --block-size=1K --summarize .git 111220 .git $ git count-objects --verbose count: 0 size: 0 in-pack: 14505 packs: 1 size-pack: 110780 prune-packable: 0 garbage: 0 size-garbage: 0 $ git prune --dry-run $ git prune-packed --dry-run $ git fsck --unreachable notice: HEAD points to an unborn branch (master)
ブランチごとのサイズを測る
開発用ブランチ (master) は必要だけど、配布用ブランチ (gh-pages) は必要ないようなケースでは、gh-pages に対して行われたコミットを保持しておくのはリソースがもったいない。以下のコマンドを使うことで、配布用ブランチのサイズを計算すると 1.80 GiB ということがわかる。配布用ブランチと開発用ブランチは別の root commit を持っているので、両者の間に親子関係はない。すなわち、開発用ブランチのサイズは 0.1 GiB 程度だとわかる。
$ git bundle create ./github.gh-pages.bundle remotes/github/gh-pages Counting objects: 282396, done. Delta compression using up to 2 threads. Compressing objects: 100% (3683/3683), done. Writing objects: 100% (282396/282396), 1.80 GiB | 19.63 MiB/s, done. Total 282396 (delta 276731), reused 282395 (delta 276731)
ブランチの削除はオブジェクトの削除と対応しない
ブランチ削除後に github リポジトリを取得してもすぐに終わる。
$ git branch --delete --remote github/gh-pages Deleted remote branch github/gh-pages (was *******). $ git branch --remotes --verbose --verbose remotes/github/master ******* $ git fetch github From https://github.com/********************************** * [new branch] gh-pages -> github/gh-pages $ git branch --remotes --verbose --verbose
オブジェクトの状態とそれを削除する方法
オブジェクト | 削除コマンド | |||
---|---|---|---|---|
reachable | unreachable | loose | in-pack | |
No | Yes | No | Yes | $ git gc --prune=all; |
No | Yes | Yes | No | $ git prune; |
No | Yes | Yes | Yes | $ git prune-packed; |