GitHubは2025年3月14日、「Git 2.49の主な新機能」に関するブログエントリを公開した。Git 2.49では、リポジトリのパック処理を効率化する「name-hash v2」や、部分クローン環境での履歴BLOBの取得を最適化する「git backfill」などが導入された。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
GitHubは2025年3月14日(米国時間)、「Git 2.49の主な新機能」に関するブログエントリを公開した。同社は以下のように説明している。
Git 2.49では、ファイルパスの計算でディレクトリ構造全体を考慮する新しいハッシュ関数が導入された。ディレクトリ階層の各レイヤーに対して個別のハッシュを計算し、それを下位ビットシフトした上でXOR演算を実施し、全体のハッシュ値に統合する。この仕組みによって、ハッシュ値がパス全体に対してより“敏感”になり、従来のように末尾の16文字にのみ依存することなく、ファイルの実際の配置をより適切に反映できるようになった。
この改良によって、パッキングのパフォーマンスだけでなく、生成されるパックの全体的なサイズも大幅に改善される。例えば、新しいハッシュ関数を使用することで、「microsoft/fluentui」を再パックするのにかかる時間は約96秒から約34秒へ短縮され、パックのサイズも439MiBから160MiBへ削減された。
この機能は現時点ではGitの到達可能なビットマップ機能(reachability bitmaps)とは互換性がないが、最新リリースを使用して「git repack」または「git pack-objects」の「--name-hash-version」フラグを使って試すことができる。
部分クローン(partial clone)を使用していて、以下のような不親切な出力を見たことはないだろうか?
$ git blame README.md remote: Enumerating objects: 1, done. remote: Counting objects: 100% (1/1), done. remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0) Receiving objects: 100% (1/1), 1.64 KiB | 8.10 MiB/s, done. remote: Enumerating objects: 1, done. remote: Counting objects: 100% (1/1), done. remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0) Receiving objects: 100% (1/1), 1.64 KiB | 7.30 MiB/s, done.
何が起こっているのか? その答えを理解するために、具体的なシナリオを考えてみよう。
例えば、「--filter=blob:none」を指定して部分クローンを作成したとする。この場合、ローカルリポジトリには全てのツリー、コミット、注釈付きタグ(annotated tag)オブジェクトが含まれるが、ブロブ(BLOB)オブジェクトはHEADから直接到達可能なものしか含まれない。言い換えれば、最新のリビジョンを完全にチェックアウトするのに必要なブロブのみが取得されるが、過去バージョンのブロブ(履歴ブロブ)を参照しようとすると、そのたびに必要なオブジェクトを取得する必要がある。
上記の例では、「git blame README.md」を実行している。しかし、「git blame」を正しく動作させるには、そのファイルの全ての履歴バージョンを取得し、変更ごとの差分を計算して、どのリビジョンがどの行を変更したのかを特定する必要がある。この処理をするたびに、Gitは欠落しているオブジェクトを1つずつ取得している。その結果、ストレージが増加し、パフォーマンスも大幅に低下してしまう。
Git 2.49では、新しいツール「git backfill」が導入された。これは、「--filter=blob:none」を指定して部分クローンを作成した場合に、不足している履歴ブロブを少数のバッチで一括取得できる機能だ。
このリクエストでは、Git 2.49で追加された新しいAPI「path-walk API」を使い、同じパスに存在するオブジェクトをグループ化することで、パックファイルのデルタ圧縮率を大幅に改善する。オブジェクトを1つずつ取得するのではなくバッチ処理でまとめて取得するため、従来のように「1つのブロブごとに1つのパックファイルを作成する」という非効率な動作を避け、少数のパックで必要なオブジェクトを一括取得できるようになった。
「git backfill」を実行すると、以下のような流れで不足している履歴ブロブを補完できる。
# `--filter=blob:none` を指定してリポジトリをパーシャルクローン $ git clone --sparse --filter=blob:none git@github.com:git/git.git [...] # 履歴のコミット / ツリー / タグを取得 $ cd git # スパースチェックアウトで `builtin/` ディレクトリを追加 $ git sparse-checkout add builtin [...] # `builtin/` の最新バージョンのファイルを取得 # `git backfill --sparse` で不足している履歴ブロブを補完 $ git backfill --sparse [...] # `builtin/` ディレクトリの履歴ブロブを一括取得 # `git blame` を実行し、履歴情報を確認 $ git blame -- builtin/backfill.c 85127bcdeab (Derrick Stolee 2025-02-03 17:11:07 +0000 1) /* We need this macro to access core_apply_sparse_checkout */ 85127bcdeab (Derrick Stolee 2025-02-03 17:11:07 +0000 2) #define USE_THE_REPOSITORY_VARIABLE 85127bcdeab (Derrick Stolee 2025-02-03 17:11:07 +0000 3)
ただし、リポジトリを「--filter=blob:none」でクローンした直後に「git backfill」を実行しても、あまり意味がない。なぜなら、最初からオブジェクトフィルターなしでクローンすれば済む話だからだ。
しかし、「git backfill」の「--sparse」オプション(スパースチェックアウト機能が有効な場合は初期設定されている)を使うことで、スパースチェックアウトの対象に含まれるブロブだけを取得し、それ以外の不要なオブジェクトをダウンロードしないようにできる。この機能を活用することで、必要最小限のストレージ使用量を維持しながら、履歴データを効率的に取得できるようになる。
この新機能はGit 2.49を導入した環境で、「--filter=blob:none」を指定してクローンしたリポジトリ内で「git backfill」を実行すれば、すぐに試すことができる。
Gitはオブジェクトを保存する際に「zlib」で圧縮している。zlibは非常に広く使われている圧縮ライブラリで、移植性を重視している。長年使われている中で、「intel/zlib」や「cloudflare/zlib」のような派生バージョンが幾つか登場しており、オリジナルの「zlib」にはない最適化がされているものもある。
「zlib-ng」は、そうした派生バーションの最適化を統合したフォークで、不要なコードや古いコンパイラ向けの回避策を削除し、パフォーマンスを重視した設計となっている。例えば、「SSE2」や「AVX2」などの「SIMD」命令セットのサポートが組み込まれており、マルチコア環境での圧縮処理が大幅に高速化されている。
「zlib-ng」は「zlib」のドロップインリプレースメント(そのまま置き換え可能な互換ライブラリ)として動作するが、従来のGitでは互換性レイヤーの調整が必要だった。Git 2.49では、「GNU Make」でビルドする場合は「zlib-ng」を、「Meson」を使用してビルドする際には「zlib_backend」オプションを指定することで、「zlib-ng」を利用できるようになった。初期の実験結果では、Gitリポジトリ内の全てのオブジェクトの内容を出力する処理が、52.1秒から40.3秒へと約25%の高速化を達成している。
今回のリリースは、Gitプロジェクトにとって大きな節目となる。Gitのコードベースに初めて「Rust」のコードが追加されたからだ。具体的には、このリリースで「libgit-sys」と「libgit」という2つのRustクレート(crate)が導入された。「libgit-sys」はGitのライブラリコードの一部を低レベルでラップする機能を持ち、一方の「libgit」はより高レベルなラッパーとして機能する。
Gitは長年にわたり、コードをライブラリ指向に進化させる取り組みを続けてきた。プログラムを強制終了する関数を、終了コードを返す関数に置き換えることで、呼び出し側が終了処理を決められるようにしたり、メモリリークの修正や管理の最適化を進めたりしてきた。今回のリリースではこうした改善の成果を生かし、Gitの「config.h API」の一部をRustでラップするRustクレートがPoC(概念実証)として導入された。
ただし、今回の導入は“GitのRust化”に向けた第一歩にすぎない。Gitの全ライブラリをRustでラップするには、今後さらに多くの作業が必要となる。しかし、今回の試みはGitの進化にとって重要な一歩であり、非常に意義のある進展と言える。
Copyright © ITmedia, Inc. All Rights Reserved.
Test & Tools 記事ランキング