この記事はモバイルファクトリー Advent Calendar 2020 11日目の記事です。
エンジニアのid:toricorです。巨大なリポジトリを操作しているとgit gc
で待たされることがたまにありますが、一体どんな処理をしているんでしょうか。
git gcとは
git gc --help
または man git-gc
でどんなコマンドか見てみましょう
Cleanup unnecessary files and optimize the local repository
git gcはリポジトリ内を掃除してくれるコマンドで、pull操作などのタイミングで実行されます。 日々の開発で蓄積したコミットなどを表すオブジェクト(ルースオブジェクト) のファイルを、変更の差分のみを保存した1つのバイナリファイル(packファイル)に詰め込んだり、不要になったオブジェクトのファイルを削除したりします。
git内部で起きることを知るにはどうすればいいか
gitでは環境変数を指定することにより挙動を変えたりパフォーマンス情報が得られたりします。
今回はGIT_TRACE
を有効にするとよさそうです。
GIT_TRACE は、どの特定のカテゴリにも当てはまらない、一般的なトレースを制御します。 これには、エイリアスの展開や、他のサブプログラムへの処理の引き渡しなどが含まれます (https://git-scm.com/book/ja/v2 第10章より引用)
GIT_TRACE=trueでgit gcを実行してみる
数年開発している、とあるプロジェクトでの実行結果は以下のようになりました
% git --version git version 2.29.0 % GIT_TRACE=true git gc 17:34:30.544544 git.c:444 trace: built-in: git gc 17:34:30.545152 run-command.c:663 trace: run_command: git pack-refs --all --prune 17:34:30.546356 git.c:444 trace: built-in: git pack-refs --all --prune 17:34:30.548071 run-command.c:663 trace: run_command: git reflog expire --all 17:34:30.549320 git.c:444 trace: built-in: git reflog expire --all 17:34:30.867648 run-command.c:663 trace: run_command: git repack -d -l -A --unpack-unreachable=2.weeks.ago 17:34:30.868943 git.c:444 trace: built-in: git repack -d -l -A --unpack-unreachable=2.weeks.ago 17:34:30.869516 run-command.c:663 trace: run_command: GIT_REF_PARANOIA=1 git pack-objects --local --delta-base-offset .git/objects/pack/.tmp-21955-pack --keep-true-parents --honor-pack-keep --non-empty --all --reflog --indexed-objects --unpack-unreachable=2.weeks.ago 17:34:30.870871 git.c:444 trace: built-in: git pack-objects --local --delta-base-offset .git/objects/pack/.tmp-21955-pack --keep-true-parents --honor-pack-keep --non-empty --all --reflog --indexed-objects --unpack-unreachable=2.weeks.ago Enumerating objects: 1167941, done. Counting objects: 100% (1167941/1167941), done. Delta compression using up to 4 threads Compressing objects: 100% (221298/221298), done. Writing objects: 100% (1167941/1167941), done. Total 1167941 (delta 930254), reused 1167941 (delta 930254), pack-reused 0 17:35:07.547672 run-command.c:663 trace: run_command: git prune --expire 2.weeks.ago 17:35:07.549012 git.c:444 trace: built-in: git prune --expire 2.weeks.ago Checking connectivity: 1168688, done. 17:35:11.150454 run-command.c:663 trace: run_command: git worktree prune --expire 3.months.ago 17:35:11.151686 git.c:444 trace: built-in: git worktree prune --expire 3.months.ago 17:35:11.152271 run-command.c:663 trace: run_command: git rerere gc 17:35:11.153440 git.c:444 trace: built-in: git rerere gc
trace: run_command:
として表示されているのがgitのサブコマンドのようですね。
いくつかのサブコマンドを組み合わせてgit gc
が成り立っているようです。
『UNIXという考え方』に通じるものを感じます。
見慣れないサブコマンドもあったのでそれぞれ簡単にどういったものかを見てみましょう。
サブコマンド
gitにはユーザーが使う前提のaddやcommitのようなサブコマンド(磁器コマンド)と、内部で使われることを前提としたサブコマンド(配管コマンド)があります。
サブコマンドの細かいオプションの詳細にはあまり立ち入らず簡単に紹介していきます。
git pack-refs
git pack-refs --all --prune
git-pack-refs - Pack heads and tags for efficient repository access
たとえば .git/refs/heads/
にローカルブランチの参照先のコミットハッシュが格納されているファイルが多数ありますが、このコマンドを使うとそれらを削除して .git/packed-refs
にまとめます。
git reflog
git reflog expire --all
git-reflog - Manage reflog information
git reflog
自体は日々のgit操作でも過去の自分のブランチ操作を調べるときなどに使いますが、git reflog expire
でこれらの操作歴を消すことができます。 ここでは--expire=<time>
が指定されていないのでデフォルトの90日分のみを残すような設定になっています。
git repack
git repack -d -l -A --unpack-unreachable=2.weeks.ago
git-repack - Pack unpacked objects in a repository
packされていなかったオブジェクトはpackされ、すでにあるpackファイルも再編成して1つのファイルに組み直します。
git pack-objects
GIT_REF_PARANOIA=1 git pack-objects --local --delta-base-offset .git/objects/pack/.tmp-21955-pack --keep-true-parents --honor-pack-keep --non-empty --all --reflog --indexed-objects --unpack-unreachable=2.weeks.ago
git-pack-objects - Create a packed archive of objects
git pack-objects
がpackファイルを書き出し(そしてpackファイルに高速にランダムアクセスするためのpack indexファイルも書き出し)してくれるサブコマンドです。
git prune
git prune --expire 2.weeks.ago
git-prune - Prune all unreachable objects from the object database
--expire
オプションをつけることで2週間と指定してそれより古い、どこからも到達できないルースオブジェクトを削除してくれます
git worktree
git worktree prune --expire 3.months.ago
git-worktree - Manage multiple working trees
git worktree
を使うと複数の作業ツリーを持てます。複数のブランチの内容を同時に複数の場所に展開できます。
prune
で$GIT_DIR/worktrees
にある情報を消してくれます。
https://git-scm.com/docs/git-worktree
git worktree
は普段の開発でも便利に使えそうです (git gcからではなく自分でgit-worktreeを使うときはgit worktree add
と git worktree remove
のペアで使うのが基本的な使い方になるでしょう)。
git rerere
git rerere gc
git-rerere - Reuse recorded resolution of conflicted merges
git rerere
は開発者が作業したconflict解消を覚えてauto merging時に支援してくれるそうです。
git rerere gc
で古いmergeのデータをunresolved conflictsで15日より古いもの、resolved conflictsで60日より古いものをデフォルトで削除してくれるようです。
まとめ
- GIT_TRACE=trueを指定することでgitの各種コマンドの内部処理を垣間見ることができる
- git gcは様々なサブコマンドの組み合わせで成り立っている
- ルースオブジェクトをpackファイルに編成するだけではなかった
- refsも再編成したり
- rerereなどのデータの削除をしたりしている
- ルースオブジェクトをpackファイルに編成するだけではなかった
参考文献
明日の記事は id:dorapon2000 さんです!