← ブログに戻る

ファイルタイムライン:あらゆるファイルの変遷を commit ごとに眺める

feature tutorial

すべての開発者がいつか直面する疑問

先月までは動いていたはずです。間違いありません。サイドバーはきちんと揃っていて、日付パーサーはタイムゾーンを正しく処理し、ボタンはちょうどいい青色でした。ところがバグ報告が届き、気がつけば 2 年分の履歴を持つファイルとにらめっこしながら、一見シンプルな問いに答えようとしています。これはいつ変わったのか、そしてどの commit が原因なのか?

そのすぐ後に第二の問いがやってきます。誰がその変更を行い、なぜそうしたのか? commit メッセージが説明してくれるかもしれません。作者が覚えているかもしれません。しかしまずはその commit を見つけなければならず、ここから頭の体操が始まるのです。

CLI のツールボックスと、その限界

ファイルの履歴を調査するために必要なものは、Git にすべて揃っています。これらのツールは強力で、実戦で鍛え上げられてきました。問題は個々のコマンドにあるのではなく、いくつものコマンドを連鎖させなければならない点にあります。

git log --follow

git log --follow --oneline -- src/styles/sidebar.css

これはリネームをまたいででも、そのファイルに触れたすべての commit を一覧表示します。出発点としては正解です。ただし得られるのは SHA と commit メッセージだけで、内容は表示されません。ある時点でファイルが実際にどうなっていたかを見るには、次のコマンドが必要です。

git show SHA:path

git show a3b4c5d:src/styles/sidebar.css

これはその commit 時点のファイル全体を出力します。2 つのバージョンを比較するには、これを 2 回実行して出力を比較するか、git diff a3b4c5d 9f2e1d0 -- src/styles/sidebar.css を使います。そしてこれを候補となる commit ごとに繰り返すのです。もし 30 個の commit がファイルに触れていたら、しばらくの間 SHA をコピペし続けながら、バージョン間の差分をずっと頭の中に保持しておくことになります。

git blame

git blame src/styles/sidebar.css

blame は各行に、その行を最後に変更した commit を注釈として付けます。疑わしい行がまだファイル内に残っていて、誰が書いたのかを知りたい場合には最適です。一方で、リグレッションの原因が削除された行にある場合や、行への最新の変更が無害なフォーマット修正で、本当に知りたい変更を覆い隠している場合には、あまり役に立ちません。そうなると git blame SHA^ -- path を何度も実行し、履歴を一層ずつ剥がしていく羽目になります。

git bisect

bisect は履歴全体に対して二分探索を実行し、テスト可能な挙動を壊した commit を見つけ出します。「壊れた」がビルドの失敗やテストの失敗を意味するなら、bisect こそが正しいツールであり、GUI のどんな機能もこれを置き換えることはできません。しかし「この CSS ルールはいつ変わったのか?」のような問いに対しては、あまりに大掛かりな機械です。ステップごとにプロジェクトを checkout してビルドし直す必要はありません。1 つのファイルの変遷を眺めたいだけなのですから。

つまり「このファイルの変遷を見たい」に対する CLI の答えはこうです。log で commit を列挙し、show で各バージョンを出力し、手作業でペアごとに diff を取り、すべての状態を頭の中で管理する。どのコマンドも正しく動きます。問題なのはワークフローなのです。

ファイルタイムライン:視覚的な答え

GitSquid は v2.6 でファイルタイムラインをリリースしました。無料プランにも含まれています。アイデアはシンプルです。ファイルの変遷を commit ごとに頭の中で再構築するのではなく、画面上でその変化を眺めるのです。

右クリックで開く

任意のファイルを右クリックして履歴を表示を選びます。そのファイルだけにフォーカスした履歴ビューが開きます。

commit ごとに 1 つの目盛り

ファイルビューアの上にタイムラインのストリップが配置され、ファイルに触れた commit ごとに 1 つの目盛りが表示されます。ファイルの一生が水平に並ぶのです。commit が 3 つのファイルは穏やかに見えますし、60 個の目盛りが並ぶファイルなら、1 行も読まないうちにその変更の激しさが伝わってきます。

スライダーでバージョンをスクラブ

スライダーをドラッグすると、ドラッグに合わせてビューアが更新されます。これがこの機能の核心です。頭の中で diff を取らなければならない離散的なスナップショット間をジャンプするのではなく、ファイルが変化していく様子をそのまま眺めるのです。最初に一往復した後はスクラブが瞬時に感じられるので、履歴を一度なぞってしまえば、怪しい領域の周辺を自由に行き来して、変化が起きた正確な commit を絞り込めます。

ホバーで誰が何を変えたかを確認

目盛りにマウスを乗せると、その履歴時点の作者と commit 情報が表示されます。「誰がいつ書いたのか」という問いに、タイムラインを離れることなく、blame を一度も実行することなく答えが得られます。

再生ボタンを押す

をクリックすると、GitSquid が履歴を自動再生します。馴染みのないファイルを初めて眺めるとき、直感をつかむにはこれが最速の方法です。構造が現れ、成長し、リファクタリングされていく様子が見えるのです。30 個の diff を読むことと、タイムラプスを眺めることの違いと言えます。

Snapshot または Diff

ヘッダーの Snapshot / Diff トグルで 2 つのビューを切り替えられます。その commit 時点のファイル全体の内容か、おなじみの親 commit との diff か。Snapshot モードは「当時ファイルはどうなっていたのか?」に答え、Diff モードは「この commit は具体的に何を変えたのか?」に答えます。リグレッション探しはたいてい Snapshot モードで始まり、Diff モードで終わります。

特定のバージョンへジャンプ

目当てのバージョンが見つかったら、右側にある 3 つのアクションで次のステップに進めます。SHA をコピーは commit ハッシュをクリップボードに置いてどこでも使えるようにし、commit を開くは完全な commit にジャンプして、対象ファイル以外に何が変わったかを確認でき、Checkout detached は古いコードを実際に動かす必要があるときに、ワーキングディレクトリをその commit にぴったり合わせます。

実践例:CSS のリグレッションを探す

具体的な例を見てみましょう。アプリのサイドバーから padding が消えてしまい、いつからなのか誰も分かりません。ファイルタイムラインを使った追跡はこうなります。

  1. sidebar.css を右クリック → 履歴を表示。タイムラインには 8 か月にわたる 24 個の目盛りが表示されます。
  2. 最新から最古へスクラブしながら padding のルールを見守ります。3 分の 2 あたりで、ルールが目に見えて変わります。最初の一往復の後はスクラブが瞬時なので、その領域の周辺でスライダーを小刻みに動かし、値が変わった正確な目盛りを特定します。
  3. その目盛りにホバー。3 週間前、チームメイトによる、レイアウトリファクタリングと書かれた commit でした。謎は半分解けました。
  4. Diff に切り替え。トグルが親 commit との diff を表示します。padding がショートハンドプロパティにまとめられた際に、値が 1 つ失われていたのです。これがリグレッションの正体です。
  5. commit を開く。完全な commit を見ると、一括変更の一環として 12 ファイルに触れていたことが分かります。レビューが甘くなりがちなタイプの変更です。そもそもこうした変更を履歴にどう残すべきかチームで議論しているなら、rebase と merge の比較をご覧ください。
  6. SHA をコピーして修正。原因の commit が特定できたので、revert する、前進修正する、cherry-pick で回避するなどの選択肢があります。どの状況にどの方法が合うかは、Git の commit を取り消す方法のガイドで解説しています。

所要時間は数分。SHA を暗記する必要も、ターミナルのスクロールバックを発掘する必要もありません。

それでも blame や bisect が正解の場面

タイムラインはツールボックスの他の道具を置き換えるものではありませんし、そうでないふりをするのは不誠実でしょう。

  • blame を使うのは、すでに特定の行を見ていて、その場で最後の作者を知りたいとき。最近フォーマット変更されていない 1 行に対しては、blame が今でも最短ルートです。
  • bisect を使うのは、症状が「ビルドが壊れた」「テストが落ちる」であり、どのファイルが原因か分からないとき。bisect はリポジトリ全体の履歴を探索し、タイムラインは 1 つのファイルを調べます。両者は異なる問いに答えるのです。
  • タイムラインを使うのは、問いが 1 つのファイルの変遷に関するものであるとき。いつ変わったのか、誰が変えたのか、以前はどうなっていたのか。

自分のコードの変遷を眺めよう

ファイルタイムラインは GitSquid の無料プランに含まれています。アカウント不要、テレメトリーなし、macOS・Windows・Linux でネイティブ動作。リポジトリを開いて、ずっと気になっていたファイルを右クリックし、再生ボタンを押してみてください。

GitSquid Free をダウンロードして、次のリグレッション探しには SHA のリストではなくタイムラインを使いましょう。