公開:2025年3月14日
3分で読めます
このリリースでは、zlib-ngによるパフォーマンス向上、新しい名前ハッシュアルゴリズム、そして新しいコマンドgit-backfill(1)の導入などが行われています。
Gitプロジェクトは最近、Git 2.49.0をリリースしました。このリリースには、GitLabのGitチームや、より広範なGitコミュニティからのコントリビュートが含まれます。注目すべきハイライトを見てみましょう。
この記事では以下の変更点についてご紹介します。
--revision
を使用した軽量なクローンgit-clone(1)
コマンドでGitリポジトリをクローンする際に、
--filter
オプションを指定することができます。
このオプションを使用すると、部分クローンを作成できます。部分クローンでは、指定されたオブジェクトフィルターに従って、サーバーから到達可能なオブジェクトの一部のみが送信されます。
例えば、--filter=blob:none
を指定してクローンを作成すると、サーバーからblob(ファイルの中身)が一切フェッチされず、_ブロブを含まないクローン_が作成されます。
ブロブを含まないクローンには、すべての到達可能なコミットとツリーは含まれますが、blobは含まれていません。そのため、git-checkout(1)
のような操作を行うと、
Gitは必要なblobをサーバーからダウンロードして、その操作を完了させます。ただし、git-blame(1)
のような一部の操作では、
必要なオブジェクトが1つずつダウンロードされることになり、この処理が非常に遅くなります。
これは、git-blame(1)
がコミット履歴をたどって必要なblobを特定し、
不足している各blobを個別にサーバーへリクエストする必要があるためです。
Git 2.49では、新たにgit-backfill(1)
というサブコマンドが導入されました。これは、ブロブを含まない部分クローンに対して、不足しているblobをダウンロードするために使用できます。
内部的には、git-backfill(1)
は新しいパスウォークAPIを利用しています。これはGitが通常行うコミットのイテレーション処理とは異なります。従来の方法では、コミットを1つずつイテレーションし、それぞれに関連するツリーやblobに再帰的にアクセスしていました。一方、パスウォークAPIはパスごとに処理を行います。各パスについて関連するツリーオブジェクトのリストをスタックに追加し、そのスタックを深さ優先で処理します。つまり、コミット1
のすべてのオブジェクトを処理してからコミット2
へ進むのではなく、ファイルA
のすべてのバージョンを全コミットにわたって処理してからファイルB
に進む、という方式です。このアプローチは、パスごとにグループ化して処理する必要がある場合に、大幅なパフォーマンス向上をもたらします。
gitlab-org/git
リポジトリのブロブを含まないクローンを作成し、その使い方をお見せします。
$ git clone --filter=blob:none --bare --no-tags [email protected]:gitlab-org/git.git
Cloning into bare repository 'git.git'...
remote: Enumerating objects: 245904, done.
remote: Counting objects: 100% (1736/1736), done.
remote: Compressing objects: 100% (276/276), done.
remote: Total 245904 (delta 1591), reused 1547 (delta 1459), pack-reused 244168 (from 1)
Receiving objects: 100% (245904/245904), 59.35 MiB | 15.96 MiB/s, done.
Resolving deltas: 100% (161482/161482), done.
上の例では、Gitが初期ブランチをチェックアウトするためにblobをダウンロードする必要がないよう、--bare
オプションを使用しています。このクローンにblobが含まれていないことは、次のコマンドで確認できます。
$ git cat-file --batch-all-objects --batch-check='%(objecttype)' | sort | uniq -c
83977 commit
161927 tree
もしこのリポジトリ内のファイル内容を確認したい場合、Gitはそのファイルをダウンロードする必要があります。
$ git cat-file -p HEAD:README.md
remote: Enumerating objects: 1, done.
remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 1 (from 1)
Receiving objects: 100% (1/1), 1.64 KiB | 1.64 MiB/s, done.
[](https://github.com/git/git/actions?query=branch%3Amaster+event%3Apush)
Git - fast, scalable, distributed revision control system
=========================================================
Git is a fast, scalable, distributed revision control system with an
unusually rich command set that provides both high-level operations
and full access to internals.
[中略]
ご覧のとおり、Gitはまずリモートリポジトリにアクセスし、blobをダウンロードしてから内容を表示しています。
このファイルに対してgit-blame(1)
を実行したい場合は、さらに多くのデータをダウンロードする必要があります。
$ git blame HEAD 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 | 1.64 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 | 1.64 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 | 1.64 MiB/s, done.
remote: Enumerating objects: 1, done.
[中略]
df7375d772 README.md (Ævar Arnfjörð Bjarmason 2021-11-23 17:29:09 +0100 1) [](https://github.com/git/git/actions?query=branch%3Amaster+event%3Apush)
5f7864663b README.md (Johannes Schindelin 2019-01-29 06:19:32 -0800 2)
28513c4f56 README.md (Matthieu Moy 2016-02-25 09:37:29 +0100 3) Git - fast, scalable, distributed revision control system
28513c4f56 README.md (Matthieu Moy 2016-02-25 09:37:29 +0100 4) =========================================================
556b6600b2 README (Nicolas Pitre 2007-01-17 13:04:39 -0500 5)
556b6600b2 README (Nicolas Pitre 2007-01-17 13:04:39 -0500 6) Git is a fast, scalable, distributed revision control system with an
556b6600b2 README (Nicolas Pitre 2007-01-17 13:04:39 -0500 7) unusually rich command set that provides both high-level operations
556b6600b2 README (Nicolas Pitre 2007-01-17 13:04:39 -0500 8) and full access to internals.
556b6600b2 README (Nicolas Pitre 2007-01-17 13:04:39 -0500 9)
[中略]
出力は省略していますが、ご覧のように、Gitはそのファイルの各リビジョンについて個別にサーバーへアクセスしています。これはとても非効率的です。そこでgit-backfill(1)
を使えば、Gitに対してすべてのblobを一括でダウンロードするよう指示できます。
$ git backfill
remote: Enumerating objects: 50711, done.
remote: Counting objects: 100% (15438/15438), done.
remote: Compressing objects: 100% (708/708), done.
remote: Total 50711 (delta 15154), reused 14730 (delta 14730), pack-reused 35273 (from 1)
Receiving objects: 100% (50711/50711), 11.62 MiB | 12.28 MiB/s, done.
Resolving deltas: 100% (49154/49154), done.
remote: Enumerating objects: 50017, done.
remote: Counting objects: 100% (10826/10826), done.
remote: Compressing objects: 100% (634/634), done.
remote: Total 50017 (delta 10580), reused 10192 (delta 10192), pack-reused 39191 (from 1)
Receiving objects: 100% (50017/50017), 12.17 MiB | 12.33 MiB/s, done.
Resolving deltas: 100% (48301/48301), done.
remote: Enumerating objects: 47303, done.
remote: Counting objects: 100% (7311/7311), done.
remote: Compressing objects: 100% (618/618), done.
remote: Total 47303 (delta 7021), reused 6693 (delta 6693), pack-reused 39992 (from 1)
Receiving objects: 100% (47303/47303), 40.84 MiB | 15.26 MiB/s, done.
Resolving deltas: 100% (43788/43788), done.
これにより、すべてのblobが補完され、ブロブを含まないクローンが完全なクローンになります。
$ git cat-file --batch-all-objects --batch-check='%(objecttype)' | sort | uniq -c
148031 blob
83977 commit
161927 tree
このプロジェクトはDerrick Stoleeによって主導され、e565f37553でマージされました。
.git/
フォルダ内のすべてのオブジェクトは、Gitによってzlib
を使って圧縮されています。zlib
はRFC
1950(ZLIB圧縮データフォーマット)のリファレンス実装であり、1995年に作られました。zlib
は長い歴史を持ち、非常に高い移植性があり、
インターネット以前の多くのシステムもサポートしています。このように幅広いアーキテクチャやコンパイラをサポートしている一方で、
zlibには機能面での制限もあります。
その制限に対応するために生まれたのがzlib-ng
というフォークです。
zlib-ng
は、現代的なシステム向けに最適化されることを目指しており、レガシーシステムのサポートを廃止する代わりに、
Intel向けの最適化パッチや、Cloudflareによる最適化、その他いくつかの小規模なパッチが取り込まれています。
zlib-ng
ライブラリ自体はzlib
との互換レイヤーを提供しており、この互換性により、zlib-ng
をzlib
の代替としてそのまま差し替えて使うことが可能です。
ただし、この互換レイヤーはすべてのLinuxディストリビューションで利用できるわけではありません。Git 2.49では以下の変更が加えられました。
Makefile
およびMesonビルドファイルへのビルドオプションの追加これらの追加により、zlib-ng
によるパフォーマンス向上の恩恵を受けやすくなりました。
ローカルでのベンチマークでは、zlib
ではなくzlib-ng
を使用することで約25%の高速化が確認されています。現在、これらの変更をGitLab.comにも順次導入中です。
zlib-ng
によるパフォーマンス向上の恩恵を受けたい場合は、まずgit version --build-options
コマンドを実行して、お使いのマシンでGitがすでにzlib-ng
を使用しているかどうかを確認してください。
$ git version --build-options
git version 2.47.1
cpu: x86_64
no commit associated with this build
sizeof-long: 8
sizeof-size_t: 8
shell-path: /bin/sh
libcurl: 8.6.0
OpenSSL: OpenSSL 3.2.2 4 Jun 2024
zlib: 1.3.1.zlib-ng
出力の一番下の行にzlib-ng
と表示されていれば、すでにGitは高速なzlib
のバリアントでビルドされています。表示されていない場合は、以下のいずれかの方法で対応できます。
zlib-ng
のサポートを有効にしてもらうよう依頼するなお、これらの変更 はPatrick Steinhardtによって導入されました。
Git 2.48のリリースについて紹介した記事では、Mesonビルドシステムの導入について触れました。Mesonは、Gitプロジェクトで使用されるビルド自動化ツールで、将来的にはAutoconf, CMakeさらには Makeに取って代わる可能性もあります。
今回のリリースサイクルでも、Mesonの利用に関する作業が引き続き進められ、不足していた機能の追加や安定性向上のための以下の修正が行われました。
contrib/
ディレクトリでMesonを使えるようにする作業の一部が、コミット2a1530a953でマージされました
これらの作業はすべてPatrick Steinhardtによって行われました。
おそらく、 .git
ディレクトリやその中身についてはご存じかと思います。
しかし、 .git/branches/
や.git/remotes/
というサブディレクトリの存在についてはどうでしょうか?
ご存じの通り、ブランチへの参照は.git/refs/heads/
に保存されているため、.git/branches/
はそのための場所ではありません。そして、.git/remotes/
の用途は何でしょうか?
2005年、.git/branches/
はリモートの短縮名を保存するために導入されました。そしてその数か月後に、それらは.git/remotes/
に移動されました。
2006年には、git-config(1)
にリモート設定を保存する機能が追加され、
これがリモートを設定する標準的な方法として定着しました。
その後2011年には、.git/branches/
と.git/remotes/
は「レガシー」であることが文書化され、現代のリポジトリでは使用されなくなりました。
そして2024年、Gitの次のメジャーバージョン(v3.0)の破壊的な変更の概要をまとめるドキュメント破壊的な変更が作成されました。
このリリースはすぐに行われる予定はありませんが、このドキュメントにはそのリリースに含まれると予想される変更点について記載されています。
コミット8ccc75c245では、.git/branches/
および.git/remotes/
ディレクトリの使用に関する内容がこのドキュメントに追加され、これにより公式に非推奨とされ、Git 3.0で削除予定であることが明示されました。
この非推奨化を正式に文書化したPatrick Steinhardtに感謝します。
Gitをコンパイルすると、内部ライブラリlibgit.a
が生成されます。このライブラリには、Gitの中核となる機能の一部が含まれています。
このライブラリ(およびGitの大部分)はC言語で書かれていますが、Git 2.49では一部の関数をRustから使えるようにするためのバインディングが追加されました。この実現のために、2つの新しいCargoパッケージ、libgit-sys
とlibgit-rs
が作成されました。これらのパッケージは、GitのSourceTree内のcontrib/
サブディレクトリに配置されています。
他言語関数インターフェイスを使う場合、ライブラリを2つのパッケージに分けるのは一般的な手法です。
libgit-sys
パッケージは、C関数への純粋なインターフェースを提供し、ネイティブのlibgit.a
にリンクします。一方、libgit-rs
パッケージは、libgit-sys
の関数をRustらしいスタイルで扱える高レベルなインターフェースを提供します。
現時点では、これらのRustパッケージで使える機能は非常に限定的で、対応しているのはgit-config(1)
とやり取りするためのインターフェースのみです。
この取り組みはJosh Steadmonさんによって主導され、コミットa4af0b6288でマージされました。
.git/
にあるGitのオブジェクトデータベースは、その大部分のデータを パックファイルに保存しています。また、このパックファイルは、Gitのサーバーとクライアント間でオブジェクトをやり取りする際にも使われます。
パックファイルのフォーマットについては、gitformat-pack(5)
に詳しく書かれていますが、その中でも重要なのが差分圧縮です。差分圧縮では、すべてのオブジェクトがそのまま保存されるわけではなく、一部のオブジェクトは他のオブジェクトとの_差分_として保存されます。つまり、オブジェクトの内容全体を保存するのではなく、他のオブジェクトと比較した変更点だけを記録します。
差分の計算や保存方法についてはここでは詳しく触れませんが、
非常に似ているファイルをグループ化しておくことが重要なのは想像できるかと思います。Git v2.48以前では、Gitは「ファイルパスの最後の16文字」をもとに、blobが似ているかどうかを判断していました。このアルゴリズムはバージョン1
と呼ばれています。
Git 2.49では、バージョン2
のアルゴリズムが追加されました。これはバージョン1
のイテレーションで、親ディレクトリの影響を減らすように調整されたバージョンです。どちらのアルゴリズムを使用するかは、git-repack(1)
の--name-hash-version
オプションで指定できます。
以下は、このプロジェクトを推進したDerrick Stoleeさんによる、git repack -adf --name-hash-version=<n>
を実行した際のパックファイルサイズの比較です。
リポジトリ | バージョン1のサイズ | バージョン2のサイズ |
---|---|---|
fluentui | 440 MB | 161 MB |
Repo B | 6,248 MB | 856 MB |
Repo C | 37,278 MB | 6,921 MB |
Repo D | 131,204 MB | 7,463 MB |
この件に関する詳細は、コミットaae91a86fbにマージされたパッチセットにて確認できます。
Gitが大きなファイルを扱うのに向いていないことは、よく知られています。この問題に対する解決策として、Git LFSのようなツールがありますが、依然として以下のような欠点が残っています。
以前からGitにはプロミサーリモートという概念が存在しており、この機能を大きなファイルに対処するための手段として利用できます。そしてGit 2.49では、この機能がさらに一歩前進しました。
新しい「プロミサーリモート」機能の考え方は比較的シンプルです。Gitサーバーがすべてのオブジェクトを自ら送信する代わりに、「これらのオブジェクトは_XYZ_からダウンロードしてね」とGitクライアントに伝えます。この_XYZ_がプロミサーリモートです。
Git 2.49では、サーバーがプロミサーリモートの情報をクライアントに通知できるようになりました。この変更は、gitprotocol-v2
の拡張として行われています。サーバーとクライアントがデータを送受信している間に、サーバーは自分が知っているプロミサーリモートの名前やURLをクライアントに送信します。
現時点では、Gitクライアントはクローン時にサーバーから受け取ったプロミサーリモートの情報をまだ利用していません。つまり、今のところは、クローン元のリモートからすべてのオブジェクトを取得しています。しかし今後は、サーバーから受け取ったプロミサーリモート情報を活用できるように開発を進め、より簡単に使える仕組みにしていく予定です。
このパッチセットはChristian Couderさんによって提出され、コミット2c6fd30198でマージされました。
--revision
を使用した軽量なクローンgit-clone(1)
に新しく--revision
オプションが追加されました。このオプションを使うことで、指定されたリビジョンの履歴のみを含む、軽量なクローンを作成できます。このオプションは--branch
に似ていますが、refs/heads/main
、refs/tags/v1.0
、refs/merge-requests/123
のような リファレンス名や、16進数のコミットオブジェクトIDを受け取る点が異なります。--branch
との違いは、このオプションでは追跡ブランチが作られず、HEAD
がデタッチ状態になる点です。そのため、このクローンを使ってブランチにコントリビュートするといった用途には向いていません。
--revision
と--depth
を組み合わせて使うことで、非常にミニマルなクローンを作成できます。おすすめの用途は、自動テストです。たとえば、CIシステムが特定のブランチ(またはリファレンス)をチェックアウトして、ソースコードのテストを行うだけでいい場合、この最小限のクローンで十分です。
この変更はToon Claesさんによって推進されました。