公開:2024年7月29日

2分で読めます

Git 2.46.0の新機能

ここでは、参照バックエンド移行ツールやトランザクションシンボリック参照の更新など、GitLabのGitチームやより広範なGitコミュニティがリリースにコントリビュートしたハイライトをご紹介します。

Gitプロジェクトは最近Gitバージョン2.46.0をリリースしました。GitLabのGitチームやより広範なGitコミュニティからのコントリビュートを含む、本リリースの主なハイライトをご覧ください。

参照バックエンド移行ツール

前回のGit 2.45.0リリースでは、reftablesフォーマットが参照を保存するための新しいバックエンドとして導入されました。この新しい参照フォーマットは、参照数の増加に伴って大規模なリポジトリが直面する複数の課題を解決します。reftableバックエンドについての詳細は、前回のGitリリースに関するブログ記事をお読みください。機能の紹介や、初心者向けガイドが掲載されているため、reftablesの仕組みについて詳しく確認できます

reftableバックエンドのオンディスクフォーマットは、既存のファイルバックエンドとは異なります。したがって、既存のリポジトリでreftableを使用するにはさまざまなフォーマット間の変換が必要となります。これを達成するため、参照バックエンド移行の実行時に利用可能なmigrateサブコマンドを含む新しいgit-refs(1)コマンドが導入されました。以下は、このコマンドの使用方法の例です。

# reflogが含まれないようにするために、新規リポジトリを「bare」として初期化します。
$ git init --bare .
$ git commit --allow-empty -m "init"
# リポジトリに、バックエンドのファイルへの参照を投入します。
$ git branch foo
$ git branch bar
$ tree .git/refs
.git/refs
├── heads
│   ├── bar
│   ├── foo
│   ├── main
└── tags
# reftableフォーマットへの参照移行を実行します。
$ git refs migrate --ref-format=reftable
# reftableバックエンドが使用されていることを確認します。
$ tree .git/reftable
.git/reftable
├── 0x000000000001-0x000000000001-a3451eed.ref
└── tables.list
# リポジトリ設定をチェックして、`refstorage`フォーマットが変更されていることを確認します。
$ cat config
[core]
        repositoryformatversion = 1
        filemode = true
        bare = true
        ignorecase = true
        precomposeunicode = true
[extensions]
        refstorage = reftable

リポジトリが移行されるとオンディスクフォーマットが変更され、reftableバックエンドを使用するようになります。リポジトリに対するGit操作は引き続き機能し、以前と同様にリモートでやり取りを行います。移行はリポジトリ内での参照の保存方法にのみ影響します。ファイル参照バックエンドに戻したい場合は、代わりに--ref-format=filesを指定して同じコマンドで実行します。

移行ツールには現在いくつか留意すべき制限があります。リポジトリ内のreflogは参照バックエンドのコンポーネントであり、フォーマット間の移行が必要となります。残念ながら、現時点ではツールを使用してファイルとreftablesバックエンド間でreflogを変換することはできません。また、worktreeが含まれるリポジトリには基本的に複数のrefストアがありますが、移行ツールはまだこのようなシナリオに対応していません。したがって、リポジトリにreflogまたはworktreeが含まれている場合、現時点では参照移行を行えません。こうした制限は、今後のバージョンでなくなる可能性があります。

bare Gitリポジトリにはreflogが含まれないため、移行は簡単です。標準的なnon-bareリポジトリを移行するには、まずreflogを削除する必要があります。そのため、reflogやworktreeのないリポジトリであれば、すべて移行できます。こうした制限はありますが、このツールを使うことで既存のリポジトリでreftablesバックエンドを活用し始められます。

このプロジェクトはPatrick Steinhardtが主導しました。

トランザクションシンボリック参照の更新

git-update-ref(1)コマンドは、Gitリポジトリ内で参照更新を実行します。こうした参照更新は、git update-ref --stdinを使用してupdate-ref命令をstdinに渡すことで、トランザクションで一括してアトミックに実行することもできます。こちらは実行方法の例です。

$ git init .
$ git branch -m main
$ git commit --allow-empty -m "foo" && git commit --allow-empty -m "bar"
# 作成された2つのコミットのオブジェクトIDを取得します。
$ git rev-parse main~ main
567aac2b3d1fbf0bd2433f669eb0b82a0348775e
3b13462a9a42e0a3130b9cbc472ab479d3ef0631
# トランザクションを開始し、update-ref命令を出し、コミットします。
$ git update-ref --stdin <<EOF
> start
> create refs/heads/new-ref 3b13462a9a42e0a3130b9cbc472ab479d3ef0631
> update refs/heads/main 567aac2b3d1fbf0bd2433f669eb0b82a0348775e
> commit
> EOF
$ git for-each-ref
567aac2b3d1fbf0bd2433f669eb0b82a0348775e commit refs/heads/main
3b13462a9a42e0a3130b9cbc472ab479d3ef0631 commit refs/heads/my-ref

この例では、トランザクションがコミットされると「bar」コミットを指す新しいブランチが作成され、メインブランチは以前の 「foo」コミットを指すよう更新されます。トランザクションをコミットすると、指定された参照更新がアトミックに実行されます。個々の参照更新が失敗した場合、トランザクションは中止され、参照更新は実行されません。

ここで注目すべきは、こうしたトランザクションでシンボリック参照更新をサポートする命令がないことです。ユーザーが、同一のトランザクション内で他の参照とともにシンボリック参照をアトミックに更新したい場合でも、それに対応できるツールはありません。今回のリリースでは、この機能を提供するためにsymref-createsymref-updatesymref-deletesymref-verify命令が導入されました。

# 次の操作で更新されるシンボリック参照を作成します。
$ git symbolic-ref refs/heads/symref refs/heads/main
# シンボリック参照自体が更新されるようにするには、--no-derefフラグが必要です。
$ git update-ref --stdin --no-deref <<EOF
> start
> symref-create refs/heads/new-symref refs/heads/main
> symref-update refs/heads/symref refs/heads/new-ref
> commit
> EOF
$ git symbolic-ref refs/heads/symref
refs/heads/new-ref
$ git symbolic-ref refs/heads/new-symref
refs/heads/main

上記の例ではトランザクション内で新しいシンボリック参照が作成され、別のシンボリック参照が更新されています。こうした新しいsymref命令を既存の命令と組み合わせて使用することで、あらゆる種類の参照更新を単一のトランザクションで実行できます。新しい命令に関する詳細はこちらのドキュメントをご覧ください。

このプロジェクトはKarthik Nayakが主導しました。

git-config(1)のUXの改善

git-config(1)コマンドを使用すると、リポジトリやグローバルオプションの表示や設定を行えます。設定を行う際に使用するモードは、フラグを使用して明示的に選択したり、コマンドに指定した引数の数に基づいて暗黙的に決定したりすることができます。例:

$ git config --list
# ユーザー名の設定を明示的に取得
$ git config --get user.name
# ユーザー名の設定を暗黙的に取得
$ git config user.name
# ユーザー名の設定を明示的に行う
$ git config --set user.name "Sidney Jones"
# ユーザー名の設定を暗黙的に行う
$ git config user.name "Sidney Jones"
# 3つ目の引数を指定することも可能です。これは何の役に立つのでしょうか?
$ git config <name> [<value> [<value-pattern>]]

全体的にgit-config(1)のユーザーインターフェイスは、サブコマンドを使うのが一般的な、他のより現代的なGitコマンドの動作とは一致していません(例:git remist)。このリリースではconfigコマンドで使用するサブコマンドとしてlistgetsetunsetrename-sectionremove-sectioneditが導入されましたが、以前の形式の構文も引き続き使用できます。今回の変更はconfigコマンドをよりUIに沿ったものにし、Git内の他のコマンドとの整合性を高めてユーザーエクスペリエンスを向上させることを目的としています。以下は新しいコマンドの使用例です。

$ git config list
$ git config get user.name
$ git config set user.name "Sidney Jones"

このプロジェクトはPatrick Steinhardtによって主導されました。

パフォーマンスのリグレッションへの対応

属性を使用するGit操作は、リポジトリのworktreeにある.gitattributesファイルの読み取りに依存しています。bare Gitリポジトリの場合は定義上worktreeが欠如しているため、問題となります。これを回避するためGitにはattr.treeという設定があり、SourceTreeを指定して属性を参照できます。

Gitリリース2.43.0では、bareリポジトリのGit属性のソースとしてHEADのツリーをデフォルトで使用するようになりました。残念なことに、Gitの属性ファイルのスキャンによる負荷の増加は、パフォーマンスに深刻な影響を及ぼす結果となっています。これはattr.treeが設定されている場合、属性を検索するたびにSourceTreeを開いて関連する.gitattributesファイルを確認する必要があるためです。リポジトリのSourceTreeが大きく深くなればなるほど、性能のリグレッションも顕著なものとなります。たとえば、linux.gitリポジトリで実行されるベンチマークでは、git-pack-objects(1)完了までに1.68倍の時間がかかっています。これにより、クローンやフェッチを実行する際に速度が低下するおそれがあります。

# Gitバージョン2.43.0では、デフォルトでattr.treeがHEADに完了済みとして設定されています。
Benchmark 1: git -c attr.tree=HEAD pack-objects --all --stdout </dev/null >/dev/null
  Time (mean ± σ):     133.807 s ±  4.866 s    [User: 129.034 s, System: 6.671 s]
  Range (min … max):   128.447 s … 137.945 s    3 runs

# 2.43.0より前のバージョンのGitでは、属性検索を無効にするためにattr.treeが空のツリーに設定されていました。
Benchmark 2: git -c attr.tree=4b825dc642cb6eb9a060e54bf8d69288fbee4904 pack-objects --all --stdout </dev/null >/dev/null
  Time (mean ± σ):     79.442 s ±  0.822 s    [User: 77.500 s, System: 6.056 s]
  Range (min … max):   78.583 s … 80.221 s    3 runs

影響を受けた重要なGitコマンドには、前述のように大規模または深いツリーのあるリポジトリで使用された場合のclonepullfetchdiffなどがあります。そのため、パフォーマンスのリグレッションに対処するために、'attr.tree設定は部分的に元に戻され、デフォルトでHEAD`に設定されることはなくなりました。詳細についてはこちらのメールスレッドをご覧ください。

ユニットテストの移行

これまでGitプロジェクトでのテストは、シェルスクリプトとして実装されたE2Eテストを通じて行われてきました。Gitプロジェクトでは比較的最近、Cで書かれたユニットテストフレームワークが導入されました。この新しいテストフレームワークにより、個々の関数呼び出しレベルで低レベルの実装の詳細をより詳しくテストする機会がもたらされたほか、既存のE2Eテストが補完されます。既存のE2Eテストにはユニットテストにより適しているものがあり、移行に適しています。

本年度も、GitLabはGoogle Summer of Code(GSoC)のGitプロジェクトに参加するコントリビューターのメンターを務めています。進行中のGSoCプロジェクトとより広範なGitコミュニティの貢献により、既存のテストの一部がリファクタリングされ、ユニットテストフレームワークに移行されつつあります。前回のリリースサイクルでは、Gitプロジェクトのテストを改善するという目標に向けて複数のコントリビュートがありました。上述のGSoCコントリビューターのプロジェクトの詳細については、ChandraおよびGhanshyamのブログをご確認ください。

バンドルURIの修正

通常、クライアントがリモートリポジトリからフェッチする場合、必要なすべてのオブジェクトはリモートサーバーによって計算されたパックファイルで送信されます。こうした計算の一部を回避するため、サーバーは、リモートサーバーとは別に保存され、クライアントが必要とする可能性のある参照とオブジェクトのセットを含む事前構築の「バンドル」のアドバタイズを選択できます。クライアントは、bundle-uriと呼ばれるメカニズムを介して最初にこうしたバンドルを取得できます。

Xing Xinさんのコントリビュートにより、いくつかのバンドルをダウンロードしたにもかかわらず、Gitがバンドルがないかのようにリモートからすべてをダウンロードし続けるという問題が特定・修正されました。これは、Gitがダウンロードしたすべてのバンドルを正しく検出できなかったために、リモートから連続したバンドルを取得する必要があったことが理由でした。この問題が修正されたことで、bundle-uriメカニズムを使用するリモートは冗長な作業を実行する必要がなくなったため、パフォーマンスが向上します。

補足情報

今回の記事では、最新リリースでGitLabとGitコミュニティが行ったコントリビュートのほんの一部をご紹介しました。Gitプロジェクトの公式リリースのお知らせではさらに詳しい情報をご覧いただけます。また、以前のGitリリースのブログ記事をチェックしてGitLabチームメンバーによる過去の主なコントリビュートをご覧ください。

ご意見をお寄せください

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

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

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

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

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