公開:2025年3月14日

3分で読めます

Git 2.49.0の新機能

このリリースでは、zlib-ngによるパフォーマンス向上、新しい名前ハッシュアルゴリズム、そして新しいコマンドgit-backfill(1)の導入などが行われています。

Gitプロジェクトは最近、Git 2.49.0をリリースしました。このリリースには、GitLabのGitチームや、より広範なGitコミュニティからのコントリビュートが含まれます。注目すべきハイライトを見てみましょう。

この記事では以下の変更点についてご紹介します。

git-backfill(1)と新しいpath-walk API

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.

[![Build status](https://github.com/git/git/workflows/CI/badge.svg)](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) [![Build status](https://github.com/git/git/workflows/CI/badge.svg)](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でマージされました。

zlib-ngの導入

.git/フォルダ内のすべてのオブジェクトは、Gitによってzlibを使って圧縮されています。zlibRFC 1950(ZLIB圧縮データフォーマット)のリファレンス実装であり、1995年に作られました。zlibは長い歴史を持ち、非常に高い移植性があり、 インターネット以前の多くのシステムもサポートしています。このように幅広いアーキテクチャやコンパイラをサポートしている一方で、 zlibには機能面での制限もあります。

その制限に対応するために生まれたのがzlib-ngというフォークです。 zlib-ngは、現代的なシステム向けに最適化されることを目指しており、レガシーシステムのサポートを廃止する代わりに、 Intel向けの最適化パッチや、Cloudflareによる最適化、その他いくつかの小規模なパッチが取り込まれています。

zlib-ngライブラリ自体はzlibとの互換レイヤーを提供しており、この互換性により、zlib-ngzlibの代替としてそのまま差し替えて使うことが可能です。 ただし、この互換レイヤーはすべてのLinuxディストリビューションで利用できるわけではありません。Git 2.49では以下の変更が加えられました。

これらの追加により、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のバリアントでビルドされています。表示されていない場合は、以下のいずれかの方法で対応できます。

  • お使いのGitパッケージのメンテナーに、zlib-ngのサポートを有効にしてもらうよう依頼する
  • Gitをソースコードから自分でビルドする

なお、これらの変更Patrick Steinhardtによって導入されました。

Mesonの継続的なイテレーション

Git 2.48のリリースについて紹介した記事では、Mesonビルドシステムの導入について触れました。Mesonは、Gitプロジェクトで使用されるビルド自動化ツールで、将来的にはAutoconf, CMakeさらには Makeに取って代わる可能性もあります。

今回のリリースサイクルでも、Mesonの利用に関する作業が引き続き進められ、不足していた機能の追加や安定性向上のための以下の修正が行われました。

これらの作業はすべてPatrick Steinhardtによって行われました。

.git/branches/および.git/remotes/の非推奨化

おそらく、 .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に感謝します。

libgitに対するRustバインディング

Gitをコンパイルすると、内部ライブラリlibgit.aが生成されます。このライブラリには、Gitの中核となる機能の一部が含まれています。

このライブラリ(およびGitの大部分)はC言語で書かれていますが、Git 2.49では一部の関数をRustから使えるようにするためのバインディングが追加されました。この実現のために、2つの新しいCargoパッケージ、libgit-syslibgit-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 LFSでは、どのファイルをLFSに入れるかをユーザーが自分で設定する必要があり、サーバー側ではそれについて制御できず、すべてのファイルを提供する必要があります。
  • リポジトリにファイルがコミットされると、履歴を書き換えない限り、それを取り除く方法がありません。これは特に大きなファイルでは厄介で、一度入れると永遠に残ってしまいます。
  • ユーザーは、Git LFSに入れるファイルを後から変更することはできません。
  • 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/mainrefs/tags/v1.0refs/merge-requests/123のような リファレンス名や、16進数のコミットオブジェクトIDを受け取る点が異なります。--branchとの違いは、このオプションでは追跡ブランチが作られず、HEADがデタッチ状態になる点です。そのため、このクローンを使ってブランチにコントリビュートするといった用途には向いていません。

--revision--depthを組み合わせて使うことで、非常にミニマルなクローンを作成できます。おすすめの用途は、自動テストです。たとえば、CIシステムが特定のブランチ(またはリファレンス)をチェックアウトして、ソースコードのテストを行うだけでいい場合、この最小限のクローンで十分です。

この変更Toon Claesさんによって推進されました。

もっと詳しく

ご意見をお寄せください

このブログ記事を楽しんでいただけましたか?ご質問やフィードバックがあればお知らせください。GitLabコミュニティフォーラムで新しいトピックを作成して、ご意見をお聞かせください。

フォーチュン100企業の50%以上がGitLabを信頼

より優れたソフトウェアをより速く提供

インテリジェントなDevSecOpsプラットフォームで

チームの可能性を広げましょう。