2ヶ月半前の git 2.0.0 のリリースに続いて、今回は多数の新機能を備えてマイナーバージョンアップした git 2.1.0 をご紹介します!

リリースノート全文はここで確認できますが、これはあなたが git コミュニティに深く携わっている人間でなければ、若干味気ない内容に感じるかもしれません。私自身による注釈であるこのブログを読めば、私たちアトラシアンがなぜ今回のリリースで興奮したのかが分かると思います。

より優れたページャーのデフォルト値

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

Git の最初期の頃から、私たちはページャーとして「less」を使用する際に環境変数 LESS に対してデフォルト値「FRSX」を与えていました。正当な根拠を持つ他のものと比較すると「S」(長い文字列の末尾を折り返す代わりに切り捨てる) にはどちらかと言えば個人の好みの問題が関わってくるため、このデフォルトのオプションセットからは取り除かれました (なお「R」に大いに正当性があるのは、私たちのアウトプットの多くはカラーであるためで、また「FX」に正当性があるの は、私たちのアウトプットの大半が一ページ未満であるため)。

あなたが既に git ページャーのデフォルト値を無効にしていない限り、今回の変更では git コマンドからのページ化アウトプットはターミナル幅に合わせて切り捨てられるのではなく、折り返す事を意味します。以下は、左側に git 2.1.0 (折り返し)、右側に 2.0.3 (切り捨て) を示した一例です。

これがログアウトプットに影響を及ぼす可能性が高いのは、コミットメッセージに狭いターミナルを使用しているか、途切れない長い行がある場合です。メッセージを横幅72文字以下に留める事が一般的な git のコツですが、折り返しが気になるため無効にしたい場合は、以下の方法で元々の動作を復元できます:

1 $ git config core.pager “less -S”

当然、このページャーは作者名の長さやコーディングスタイル次第で非常に長い行にもなり得る git blame 等、他のアウトプット表示にも適用されます。また、2.1.0 リリースノートにおいては、以下の方法で blame ページャーのみに対して-S フラグを有効にできる点も指摘されています。

1 $ git config pager.blame “less -S”

git がまだ使用している、デフォルトの less オプションに関して知りたい方のために、以下のようにまとめました:

  • -F は、アウトプットが 1 ページ未満である場合に less から exit します
  • -R は、ANSI color escape sequence のみが生の出力 (エスケープシーケンスを解釈しない) に使われます (よって git コンソールで色が付きます)
  • -X は、less が起動した際に画面が消されるのを防止します (これもやはり、長さが 1 ページ未満のログに便利)。

より良い bash 補完機能

複雑なコマンド列を定義するエイリアスに対応するため、bash 向け補完スクリプト (contrib/) がアップデートされました。

これは、素晴らしい事です! 私自身、カスタム git エイリアスの大ファンです。 git の bash 補完を複雑なエイリアスにプラグできる機能は、コマンドラインからの使用がより強力かつ便利になった事を意味します。例えば、私はログから JIRA スタイルの課題キーの grep を利用するよう定義されたエイリアスを持っています。

1 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 においては、正しく master を補完しますが、これは本来の git log コマンドの動作です。また、「:」で始まるnull コマンドをエイリアスの頭に付ける事で、bash 補完を利用できます。これは、補完したい git コマンドがエイリアスの最初のコマンドでない場合に便利です。例えば、

1 issues = “!f() { echo ‘Printing issue keys’; git log –oneline $@ | egrep -o [A-Z]+-[0-9]+ | sort | uniq; }; f”

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

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

これは、複雑な git エイリアスのスクリプトを書くのが好きな人にとってかなり便利です。これは git の中核ではなく、contrib/にあるため、必要であれば bash profile をアップデートして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 は以下のように説明されています:

--full-name サブディレクトリから実行すると、通常はコマンドが現ディレクトリからの相対パスをアウトプットします。このオプションでは、プロジェクトのトップディレクトリからの相対パスかアウトプットされるようになります。

いいですね!私の場合、git grep を実行して取得したファイルパスを様々な XML ファイルにコピー&ペーストすることが多いので、私のワークフローにとっては素晴らしいデフォルト機能です (私が Java デベロッパーである事実が露呈されてしまったかもしれません)。あなたにとってもこれが便利であれば、以下を実行するだけで構成を有効化できます:

1 $ git config –global grep.fullname true

--global フラグはこの構成を私の $HOME/.gitconfig に適用させるため、私のシステム上のあらゆる git リポジトリのデフォルト動作となります。必要であればいつでも、リポジトリレベルで無効化できます。

更に賢くなった git replace

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

要するに、git replace であれば、git リポジトリ内の一部のオブジェクトを書き換える際、対応するツリーやコミット SHA を変更せずに済みます。もしも git replace を耳にするのが初めてでも、git データモデルには馴染みがある方には、これは冒涜的行為のように感じるかもしれません!私もそうでした。このような機能を使用する時のために、現在私は別のブログ記事を執筆中です。それまでの間に何かを学びたい方は、ごく僅かなユースケースのみを扱った man ページよりも、この記事 の方が遥かにためになります。

さて、git replace はどのようにして賢くなったのでしょうか?

“git replace” は、既存オブジェクトを編集する事で代わりを作成する、”‐‐edit” サブコマンドが利用可能になりました。

--edit オプションでは、中身を一時ファイルにダンプして好きなエディターを起動させる事で、特定のオブジェクトを便利にコピー&交換できます。master の最新のコミットを交換するには、単に以下を実行します:

1 $ git replace –edit master

また、master の最新のコミットが jira-components/pom.xml だと認識している blob を編集するには、以下を実行します:

1 $ git replace –edit master:jira-components/pom.xml

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

“git replace” は、コミットの親を書き換える “‐‐graft” オプションを使えるようになりました

--graft オプションは、同一であるものの、親が異なるコミットを交換するショートカットの方法です。これは、git 履歴を短くするという git replace を使った僅かながらまともなユースケースを実現する上で便利な方法です。例えば、以下を実行するだけで、自分のマスターブランチの最新コミットの親を交換できます:

1 $ git replace master –graft [new parent]..

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

1 $ git replace master –graft

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

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

“git tag” は、‐‐sort= オプションが与えられていない場合のデフォルトのソート順序として ”tag.sort” が設定できるようになりました。

恐らく99.9%のヒトが使用しているとものと思われますが、タグ名においてバージョン名を使用しているのであれば、これは朗報です。表記が 1 桁以上のバージョン (例、v10 または v1.10) を一度リリースすると、git のデフォルトの辞書式ソートでは正しくソートされません。例として、Atlassian Stash の git リポジトリにおけるデフォルトのタグ順序付けを見てみましょう:

1 src/stash $ git tag -l *.*.0
2 ..
3 stash-parent-2.0.0
4 stash-parent-2.1.0
5 stash-parent-2.10.0
6 stash-parent-2.11.0
7 stash-parent-2.12.0
8 stash-parent-2.2.0
9 stash-parent-2.3.0
10 stash-parent-2.4.0
11 stash-parent-2.5.0
12 stash-parent-2.6.0
13 stash-parent-2.7.0
14 stash-parent-2.8.0
15 stash-parent-2.9.0
16 stash-parent-3.0.0
17 ..

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

1 src/stash $ git tag –sort="version:refname" -l *.*.0
2 ..
3 stash-parent-2.0.0
4 stash-parent-2.1.0
5 stash-parent-2.2.0
6 stash-parent-2.3.0
7 stash-parent-2.4.0
8 stash-parent-2.5.0
9 stash-parent-2.6.0
10 stash-parent-2.7.0
11 stash-parent-2.8.0
12 stash-parent-2.9.0
13 stash-parent-2.10.0
14 stash-parent-2.11.0
15 stash-parent-2.12.0
16 stash-parent-3.0.0
17 ..

このほうが良いですね。git 2.1.0 であれば、以下を実行する事でこの順序付けをデフォルトに設定できます:

1 $ git config –global tag.sort version:refname

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

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

新コマンド “git verify-commit” が追加され、”git verify-tag” コマンドが署名付きタグの GPG 署名を検証するのと同様に、署名済みコミットが検証できるようになりました。

もしもあなたかコミッターを検証する上でコミット署名を使用している場合、verify-commit コマンドが署名検証を非常に簡単にしてくれます。git log --show-signature のアウトプットをパースするために自分のスクリプトを書く代わりに、署名の確認を必要とする一連のコミットをgit verify-commit に渡すだけで済みます。但し、署名済みコミットを使用すると(アトラシアンでは使用していません)、管理者とデベロッパーに苛立たしいオーバーヘッドをもたらすため、現在皆さんは使用していない確率が高いのではないでしょうか。一般的に、利便性とセキュリティの中間をいった署名済みタグの方が、ほとんどのプロジェクトには受け入れやすくなっています。なぜプロジェクトが署名済みコミットを選ぶのか気になる方は、それらが非常に便利になる仮説シナリオを紹介した Mike Gerwitz の git ホラーストーリー を読んでみるといいでしょう。あなたが非常に神経質な業界で働いている場合は、これらをワークフローの一部として実装する事を一考するといいでしょう。

パフォーマンスのブースト

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

2 つのファイル (ベースとなるファイルと、そこから派生するインクリメンタルな変更) を利用してインデックスを作成する実験的フォーマットが導入されました。これによって、作業ツリーのごく一部のみが変更した場合でも大きなインデックスの書き換えが伴ってしまうといった I/O コストを削減できるようになります。

どういう事かと言うと: 沢山のファイルの変更を伴う大きなコミットを抱えている場合、git add のスピードがかなり上がっていたかもしれないのです。git add は、私がローカルで試したあらゆるインクリメンタルなテストケースにおいて光りのような早さを見せていたので、テスト中には Git 2.0 と 2.1 の間にパフォーマンス上の大きな差は見つかりませんでした。興味深い事に、大きなファイル群の場合、一番最初の add のスピートば多少上がった気がします。私が行った間に合わせのパフォーマンステスト中、JIRA 5 と JIRA 6 のコードベースにおける全ての変更に対して実施してみました。

  $ git checkout jira-v6.0  
1
  $ git reset jira-v5.0   
2
  $ time git add –all
3

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 コマンドを並列実行していました。

1  $ git guilt v2.0.0 v2.1.0

git 2.0.3 は平均 72.1 秒に対し、git 2.1.0 は平均 66.7 秒であったため、7.5% の改善が見られました!興味がある方は、実際のgit-guilt の変化を参照して下さい (つい最近、Karsten Blees が 66 のコード行という僅差で Junio C Hamano に勝ちました)。

これらのパフォーマンステストは全て若干アドホックな性質のものですが、現在私たちは Bitbucket を git 2.1.0 へとアップグレードしている最中です。 アップグレード前後に機能を監視する事で、一部の git 操作、特に blame と diff のパフォーマンスに対して新バージョンがどのような効果をもたらすかを確認する予定です。その結果は、数週間後に私が報告します。

最後に、あと 1 つ!

このブログ投稿において扱えなかった git 2.1.0 のその他の素晴らしい点に興味のある方は、リリースノート全文を確認して下さい。またしても質の高い、機能満載のリリースを届けてくれた git チームに大きな感謝の意を伝えたいと思います。git の世界にまつわる更なるアドバイスやちょっとした情報に興味のある方は、私 (@kannonboy) および Atlassian Dev Tools (@atldevtools) を Twitter でフォローして下さい。

本稿は、Atlassian Blogs 日本語版の転載です。本文中の日時などはAtlassian Blogs 英語版での投稿当時のものですのでご了承ください。