Mobile Factory Tech Blog

技術好きな方へ!モバイルファクトリーのエンジニアたちが楽しい技術話をお届けします!

git gcで起きることをトレース情報から追ってみる

この記事はモバイルファクトリー 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章より引用)

https://github.com/git/git/blob/e1cfff676549cdcd702cbac105468723ef2722f4/Documentation/git.txt#L670-L672

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 addgit 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-scm.com

まとめ

  • GIT_TRACE=trueを指定することでgitの各種コマンドの内部処理を垣間見ることができる
  • git gcは様々なサブコマンドの組み合わせで成り立っている
    • ルースオブジェクトをpackファイルに編成するだけではなかった
      • refsも再編成したり
      • rerereなどのデータの削除をしたりしている

参考文献


明日の記事は id:dorapon2000 さんです!