R.A. Epigonos et al.

[git] ブランチの削除とそのブランチから到達できなくなったオブジェクトの削除方法

unreachable objects を削除する方法を乱暴に言うと $ git gc --prune=all;。

ブランチを削除しただけではオブジェクトは削除されない。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)02822740
size (KiB)0119863040
No Yes in-pack (num)2967791450514505
packs (num)111
size-pack (KiB)2006942110780110780
Yes Yes prune-packable (num)000
No No garbage (num)000
size-garbage (KiB)000

先に述べたとおり、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;

リファレンス

  1. How do you stop tracking a remote branch in Git? - Stack Overflow
  2. How to remove unreferenced blobs from my git repo - Stack Overflow
  3. What are the differences between git remote prune, git prune, git fetch --prune, etc - Stack Overflow
  4. Git - Maintenance and Data Recovery
  5. Git - Git Objects
  6. Git - git-init Documentation
  7. Git - git-remote Documentation
  8. Git - git-fetch Documentation
  9. Git - git-branch Documentation
  10. Git - git-gc Documentation
  11. Git - git-prune Documentation
  12. Git - git-config Documentation
  13. Git - git-cat-file Documentation
  14. Git - git-unpack-objects Documentation
  15. Git - git-repack Documentation

ソーシャルブックマーク

  1. はてなブックマーク
  2. Google Bookmarks
  3. del.icio.us

ChangeLog

  1. Posted: 2008-01-29T13:52:57+09:00
  2. Modified: 2008-01-29T13:52:57+09:00
  3. Generated: 2023-08-27T23:09:10+09:00