Git 2.37 の新機能

Tim Petterson
Tim Petterson
リストに戻る

2 ヶ月半前の git 2.0.0 のリリースに続き、素晴らしい新機能が数多く含まれたマイナーバージョン、git 2.1.0 がもうすぐ入手可能です。

完全版のリリース ノートはこちらから入手できますが、Git コミュニティに深く関わっていない人にとっては、少し説明が足りないかもしれません。このブログでは、アトラシアンで私たちをワクワクさせたリリースのいくつかの側面について私自身が解説しています。

ページャーの初期設定の改善

以下はリリースノートからの引用です。そしてその下にコメントしています。

Git の初代リリースから、ページャー "less" を生成するときの規定値として LESS 環境に "FRSX" が設定されていました。"S" (長い行を折り返さずに切り捨てる) は、この既定のオプション セットから削除されました。これは、正当な理由がある他のオプションとは対照的に、多かれ少なかれ個人的な好みであるためです (つまり、"R" は、私たちが作成する多くの種類の出力はカラーであるため正当化され、"FX" は、私たちが作成する出力が多くの場合 1 ページより短いため正当化されます)。git pager の既定値をまだオーバーライドしていない場合、この変更により、git コマンドからのページ出力は、ターミナルの幅で切り捨てられるのではなく、折り返されます。以下の例は、左側が git 2.1.0 (折り返し)、右側が 2.0.3 (切り捨て) です。

git 2.1.0 と git 2.0.3 のページャースタイル

これは、狭いターミナルを使用していたり、コミット メッセージに切れ目のない長い行があったりする場合にのみ、ログ出力に影響する可能性があります。一般的に Git ではコミット メッセージの幅を 72 文字以下に抑えるのが良いとされていますが、折り返しが気になる場合は、次のようにして元の動作を復元することで無効にすることができます。

$ git config core.pager" less-S"

もちろん、ページャーは git blame などの他の出力を表示するためにも使用されます。作成者名の長さやコーディング スタイルによっては、行が非常に長くなることがあります。2.1.0 リリース ノートでは、次のように blame ページャーに対してのみ-S フラグを有効にできることも指摘されています。

$ git config pager.blame "less -S"

git でまだ使用されている、既定の less オプションに関して知りたい方のために、以下のようにまとめました。

  • -F は、アウトプットが 1 ページ未満である場合に less から exit します。
  • -R は、ANSI カラー エスケープ シーケンスが raw 形式で出力されるようにします (したがって、git コンソールの色が機能します)。
  • -X は、less が起動した際に画面が消されるのを防止します (これもやはり、長さが 1 ページ未満のログに便利)です。

より良い bash 補完機能

bash (contrib/) の補完スクリプトが更新され、複雑なコマンド シーケンスを定義するエイリアスの処理が改善されました。これは大変便利です! 私はカスタム Git エイリアスの大ファンです。git の bash 補完を複雑なエイリアスにプラグインできるので、コマンド ラインからより強力で便利に使用できます。たとえば、Jira スタイルの課題キー (例: STASH-123) をログから grep するエイリアスを定義していたとします。

issues = !sh-c 'git log --oneline $@ | egrep -o [A-Z]+-[0-9]+ | sort | uniq' -

すべての引数が git log コマンドに渡されるため、課題キーを取得するコミットの範囲を制限できます。たとえば、git issues-n 1 は現在のブランチの最新のコミット メッセージに付与された課題キーを表示します。現在の 2.1.0 の時点では、git の bash 補完が改善された結果、git 課題のエイリアスをまるで git log コマンドであるかのように補完できるようになりました。

git 2.0.3 では、git issues mと入力すると、既定の bash 補完動作にフォールバックして、カレント ディレクトリのmで始まるファイルが一覧表示されていました。git 2.1.0 では、main を正しく補完します。これが git log コマンドの本来の動作です。また、「:」で始まる null コマンドをエイリアスの頭に付けることで、bash 補完を利用できます。これは、補完したい git コマンドがエイリアスの最初のコマンドではない場合に便利です。たとえば、エイリアス:

issues = "!f(){ echo 'Printing issue keys'; git log --oneline $@ | egrep -o [A-Z]+-[0-9]+ | sort | uniq; }; f"

このエイリアスでは、git が echo コマンドを補完対象として認識できないため、うまく補完できません。しかし、': git log;' を頭に付ければ、再び正しく補完できるようになります。

issues = "!f(){ : git log ; echo 'Printing issue keys'; git log --oneline $@ | egrep -o [A-Z]+-[0-9]+ | sort | uniq; }; f"

これは、複雑な git エイリアスのスクリプトを書くのが好きな人にとってかなり便利です。これは git の中核ではなく、contrib/ にあるため、必要であれば bash プロファイルを更新して contrib/completion/git-completion.bash の新バージョンを参照するのを忘れないでください。

approxidate が git commit で利用可能に

"git commit ‐‐date=" は、さらに多くのタイムスタンプ フォーマットに対応しました ("--date=now" など)。git commit の --date フラグは、そのより厳格な従兄弟的存在である parse_date() が特定の文字列を解析できない場合に、git のすばらしい (少しクセがありますが) approxidate パーサーを利用できるようになりました。approxidate--date=now のような明白なものを処理でき、--date="midnight the 12th of october, anno domini 1979"--date=teatime のようなもう少し難解な形式にも対応しています。さらに詳しく知りたい方は、Alex Peattie の git の優れた日付処理に関するすばらしいブログ投稿をご覧ください。

grep.fullname によるパスの向上

"git grep" では grep.fullname 設定変数を使用できるようになったため、"‐full-name" を既定とするよう強制できるようになりました。この結果、この新しい作用を予期していないスクリプト ユーザーは過去になかった不具合に直面する可能性があります。man git-grep を呼び出さずに済むよう、--full-name について説明します。

コマンドをサブディレクトリから実行すると、通常、カレント ディレクトリからの相対パスが出力されます。このオプションを指定すると、プロジェクトの最上位ディレクトリからの相対パスが出力されます。親切ですね! これは私のワークフローにとってすばらしい既定です。私はファイル パスを見つけるために git grep を実行して、どこかの XML ファイルにコピーして貼り付けることがよくあるのです (これは私が Java 開発者であるがゆえかもしれません)。あなたにとってもこの機能が便利なら、実行すればいいだけです:

$ git config --global grep.fullname true

実行すると、設定で有効になります。

--global フラグによってこの設定が私の $HOME/.gitconfig に適用されます。そのため、私のシステム上のあらゆる git リポジトリの既定の動作になります。必要に応じて、いつでもリポジトリ レベルで上書きできます。

git replace の改善

ちょっと待ってください。少し戻ってみましょう。そもそも、この git replace にはどのような機能があったでしょうか?

簡単に言うと、git replace を使用すると、対応するツリーを変更したり、SHA をコミットしたりすることなく、Git リポジトリ内の特定のオブジェクトを書き換えることができます。もしも git replace を耳にするのが初めてでも、git データ モデルには馴染みがある方には、これは冒涜的行為のように感じるかもしれません! 私もそうでした。私は、このような機能をいつ、どのような理由で使用するかについて別のブログ記事を執筆中です。それまでの間にもっと学びたい方は、ユース ケースの少ない man ページよりもこの記事の方がはるかに参考になります。

さて、git replace はどのように改善されたのでしょうか?

"git replace" では、"‐‐edit" サブコマンドを使用して、既存のオブジェクトを編集して代わりのオブジェクトを作成できるようになりました。--edit オプションを使用すると、特定のオブジェクトの内容を一時ファイルにダンプし、お好みのエディタを起動することで、簡単にコピーして置き換えることができます。main の最新のコミットを置き換えるには、次のコマンドを実行するだけです。

 $ git replace --edit main

また、main の最新のコミットが jira-components/pom.xml ファイル内にあると認識している blob を編集するには、次のコマンドを実行します。

 $ git replace --edit main:jira-components/pom.xml

この方法でもいいのでしょうか? 恐らく、適切ではないでしょう。多くの場合、オブジェクトの書き換えには git rebase を使用した方が、コミット SHA を正しく書き換えるためまともな履歴が残ります。

"git replace" では、コミットの親を書き換える "‐‐graft" オプションが使えるようになりました。--graft オプションは、同一であるものの、親が異なるコミットを交換するショートカットの方法です。これは、git 履歴を短くするという git replace を使ったわずかながらまともなユース ケースを実現する上で便利な方法です。main ブランチの先端の親を置き換えるには、次のコマンドを実行すればよいだけです。

 $ git replace main --graft [new parent]..

また、自分の履歴を特定のポイントで切り落とす場合は、新たな親をそのまま削除する事でコミットを親のない状態にできます:

 $ git replace main --graft

繰り返しになりますが、特に正当な理由がない限り、これはやらない方がいいでしょう。一般的に、自分の履歴を書き換える際は git rebase を慎重に使用する方が望ましいです。

tag.sort を使用した合理的なタグの順序付け

"git tag" では、‐‐sort= オプションが与えられていない場合の既定のソート順序として "tag.sort" が設定できるようになりました。バージョン名をタグ名に使用しているのであれば、これは朗報です。恐らく 99.9% の人が使用していると思います。2 桁以上のセグメントを含むバージョン (例、v10 または v1.10) をリリースすると、git の既定の辞書式ソートでは正しくソートされません。例として、Atlassian Stash (現 Bitbucket Server) の git リポジトリにおけるデフォルトのタグ順序付けを見てみましょう:

src/stash $ git tag -l *.*.0
..
stash-parent-2.0.0
stash-parent-2.1.0
stash-parent-2.10.0
stash-parent-2.11.0
stash-parent-2.12.0
stash-parent-2.2.0
stash-parent-2.3.0
stash-parent-2.4.0
stash-parent-2.5.0
stash-parent-2.6.0
stash-parent-2.7.0
stash-parent-2.8.0
stash-parent-2.9.0
stash-parent-3.0.0
..

駄目ですね! 2.10.0 は年代順では 2.3.0 のに来るので、この既定のタグ順序付けは間違っています。git 2.0.0 以降、数値バージョンを正しくソートする上で、私たちは --sort フラグを利用しています。

src/stash $ git tag --sort="version:refname" -l *.*.0
..
stash-parent-2.0.0
stash-parent-2.1.0
stash-parent-2.2.0
stash-parent-2.3.0
stash-parent-2.4.0
stash-parent-2.5.0
stash-parent-2.6.0
stash-parent-2.7.0
stash-parent-2.8.0
stash-parent-2.9.0
stash-parent-2.10.0
stash-parent-2.11.0
stash-parent-2.12.0
stash-parent-3.0.0
..

うまくいきました。git 2.1.0 では、次のコマンドを実行して、この順序を既定にすることができます。

$ git config --global tag.sort version:refname

ちなみに、上記の git tag の例において使用されている便利な -l フラグは、表示されるタグ名を特定のパターンに制限します。-l *.*.0 は、Stash (現 Bitbucket Server) のメジャーおよびマイナー リリースのみを表示するために使用されています。

署名済みコミットのよりシンプルな確認

署名済みタグのチェックに使用される "git verify-tag" と同様の方法で署名済みコミットの GPG 署名をチェックする新しい "git verify-commit" コマンドが追加されました。コミット署名を使用してコミットの作者を認証する場合、verify-commit コマンドを使用すると、署名の検証が非常に簡単になります。git log --show-signature の出力を解析する独自のスクリプトを書く代わりに、git verify-commit に一連のコミットを渡して署名をチェックすることができます。とはいえ、署名済みコミットを使用すると、主な管理者と開発者に負担がかかるため、現在みなさんは使用していないのではないでしょうか (アトラシアンでは使用していません)。一般的に、利便性とセキュリティの中間をいった署名済みタグの方が、ほとんどのプロジェクトには受け入れやすくなっています。なぜ署名付きコミットを使用するプロジェクトがあるのか気になる方は、それらが非常に役立っている仮説シナリオを紹介した Mike Gerwitz の git ホラー ストーリーを読んでみるとよいでしょう。極めて機密性の高い業界で働いている場合は、これらをワークフローの一部として実装することを検討してください。

パフォーマンスの向上

Git 2.1.0 には、いくつかの優れたパフォーマンス上の改善点も見られます。

2 つのファイル (ベースとなるファイルと、そこから派生するインクリメンタルな変更) を利用してインデックスを作成する実験的フォーマットが導入されました。これによって、作業ツリーのごく一部のみを変更しただけでも大きなインデックスの書き換えが伴ってしまうといった I/O コストを削減できるようになります。どういうことかと言うと、沢山のファイルの変更を伴う大きなコミットを抱えている場合、git add のスピードが上がるかもしれないのです。git add は、私がローカルで試したあらゆるインクリメンタルなテスト ケースにおいて既に光のような速さを見せていたので、テストではバージョン間のパフォーマンスに大きな差は見られませんでした。興味深いことに、大きなファイル群の場合、最初の add のスピードは多少上がった気がします。私が行った間に合わせのパフォーマンス テスト中、Jira 5 と Jira 6 のコードベースにおけるすべての変更に対してステージングを実施してみました。

$ git checkout jira-v6.0 $ git reset jira-v5.0 $ time git add --all

Git 2.0.3 では、平均 2.44 秒でした。一方、Git 2.1.0 では、平均 2.18 秒で、10% 以上の時間節約になりました! ただし、これはラボの条件下で正確に行われたわけではなく、14,500 個以上のファイルをインデックスに一度に追加すると約 1/4 秒節約できるので、通常の git の使用では大きな影響は見られないかもしれません。新しいインデックス分割形式の詳細については、インデックス形式のドキュメントを参照してください。

"core.preloadindex" 設定変数が既定で有効になっているため、モダンなプラットフォームではマルチ コアを活用できます。便利な機能です。私はこの機能を有効化したことがありませんでしたが、2.1.0 にアップデート後はその違いがハッキリとわかります。先程の間に合わせのテストの一環として、私は上述の Jira 5 と Jira 6 間の変更に対して、git status の実行時間を測ってみました。Git 2.0.3 では、14,500 超のファイルを実行した結果、平均 4.94 秒であり、git 2.1.0 では平均 3.99 秒であったため、何と 19% にも及ぶ節約です。これは、特にカスタム シェル プロンプトを使用して git status のあとに作業コピー内に未コミットの変更がないかを確認しているような場合に便利です。当然、非常に大きなインデックスにおいては、Bash がとても軽快な感じがしました。"git blame" は、実行する作業の追跡に使用されるデータ構造を再編成することによって、大幅に最適化されました。git blame は、何かを壊した特定のコード行に貢献したのは誰かをより速く把握できるようになりました:)。私にとって、この改善点は特に喜ばしいものです。これは、blame アウトプット機能に大いに依存しているからです git-guilt (blame がコミット間でどのように変化するかを調査するために私が書いたちょっとしたツール) もまた、より良いパフォーマンスの向上を期待できることを意味するからです。

別の間に合わせのテストを行って、今度は 2.0.0 と 2.1.0 間の git ソース リポジトリにおける git guilt の計算時間を確認しました。このテストにおいて、git-guilt は git 2.0.0 と git 2.1.0 間で変更されたさまざまなサイズのファイルに対して、886 個の git blame コマンドをフォークしていました。

$ git guilt v2.0.0 v2.1.0

git 2.0.3 では平均 72.1 秒、git 2.1.0 では平均 66.7 秒であり、7.5% の改善が見られました! 興味があれば、git-guilt transfer をご覧ください (Karsten Blees が Junio C Hamano にわずか 66 LOC の差で勝利しました)。

これらのパフォーマンス テストはすべて若干暫定的ではありますが、現在私たちは Bitbucket Cloud を git 2.1.0 へとアップグレードしている最中です。アップグレード前とアップグレード後の両方の機能を観察して、新しいバージョンが特定の git 操作 (特に blamediff) のパフォーマンスにどのように影響するかを確認する予定です。数週間後に報告して、状況をお知らせします。

他にも多くの機能を利用できます。

git 2.1.0 には、このブログ投稿では取り上げていないすばらしい機能が他にもあります。興味があれば、完全版リリース ノートをご覧ください。またもや高品質で機能満載のリリースを提供してくれた Git チームは大きな称賛に値します。Git の世界のヒントや豆知識をもっと読みたい場合には、Twitter で私 (@kannonboy) とアトラシアン開発ツール (@atldevtools) をフォローしてください。

Git を学習する準備はできていますか?

この対話式チュートリアルを利用しましょう。

今すぐ始める