Skip to content

履歴/Files/CMATEタブにGitタブを追加(変更履歴・diff表示) #447

@Kewton

Description

@Kewton

Note: このIssueは 2026-03-08 にレビュー結果(Stage 1, Stage 3 影響範囲レビュー, Stage 5 通常レビュー2回目, Stage 7 影響範囲レビュー2回目)を反映して更新されました。
詳細: dev-reports/issue/447/issue-review/

概要

ワークツリー詳細画面の左ペインタブ(履歴 / Files / CMATE)に「Git」タブを追加し、Git変更履歴の確認とファイルdiff表示を可能にする。

背景・課題

  • 現在、Gitの変更履歴やdiffを確認するにはターミナルで git log / git diff を実行する必要がある
  • コーディングエージェントが行った変更内容を素早く確認したい
  • コミット履歴と変更ファイルの関係をGUI上で直感的に把握したい

提案する解決策

Gitタブの追加

左ペインのタブに「Git」を追加し、以下の機能を提供する。

1. 変更履歴表示(デフォルト表示)

  • Gitタブ選択時、コミット履歴を一覧表示(git log 相当)
  • デフォルト取得件数は最大50件--max-count=50)とし、大規模リポジトリでのタイムアウト・メモリ圧迫を防止する
  • 各コミットにはコミットメッセージ、作成者、日時を表示
  • コミットを選択すると、そのコミットで変更されたファイル一覧を表示(git show --stat 相当)
  • 将来拡張として「Load More」によるページネーション対応を検討する

2. Diff表示

  • 変更ファイルを選択すると、右ペイン(ファイル内容表示領域)にdiffを表示
  • unified diff形式を採用(コンテキスト行数はデフォルト3行)
  • 追加行(緑)/ 削除行(赤)の色分け表示
  • ファイル単位のdiff(git diff 相当)
  • 初期実装では各コミットの直前コミットとのdiff(git show 相当)のみ対応。2コミット間比較やWorking tree changesの表示は将来拡張とする

3. モバイル対応

  • モバイルビュー(MobileTabBar)ではGitタブを非表示とする(現在5タブで画面幅が逼迫しているため、6タブ化はUIの破綻リスクがある)
  • モバイルでのGit履歴確認はターミナルタブからの git log コマンドで代替する
  • モバイルへのGitタブ追加は将来の別Issueとして検討する

APIエンドポイント設計

既存の /api/worktrees/[id]/xxx パターンに準拠する。

GET /api/worktrees/[id]/git/log

  • クエリパラメータ: limit(デフォルト: 50), offset(デフォルト: 0)
  • レスポンス: { commits: CommitInfo[] }
    • CommitInfo: { hash: string, message: string, author: string, date: string }
  • タイムアウト: git log用に専用のタイムアウト定数(GIT_LOG_TIMEOUT_MS = 3000、推奨値: 3000-5000ms)を定義する。既存の GIT_COMMAND_TIMEOUT_MS(1000ms)はgit log実行には不足する可能性があるため、execGitCommand にタイムアウト引数を追加するか、git log専用定数を使用すること

GET /api/worktrees/[id]/git/show/[commitHash]

  • レスポンス: { commit: CommitInfo, files: ChangedFile[] }
    • ChangedFile: { path: string, status: 'added' | 'modified' | 'deleted' | 'renamed' }
  • バリデーション: commitHash[0-9a-f]{7,40} の正規表現でバリデーション

GET /api/worktrees/[id]/git/diff

  • クエリパラメータ: commit(コミットハッシュ), file(ファイルパス)
  • レスポンス: { diff: string }(unified diff形式)
  • バリデーション: commit[0-9a-f]{7,40} でバリデーション、fileisPathSafe() でバリデーション

セキュリティ要件

  • すべてのAPIで execFile を使用すること(exec 禁止)
  • '--' セパレータを使用してgitオプション注入を防止
  • worktreePathはDB由来の信頼されたソースのみ使用(既存の方針を踏襲)
  • すべての新規APIルートで isValidWorktreeId(params.id) によるworktree IDバリデーションを実装すること(既存APIルートの実装パターンを踏襲。例: src/app/api/worktrees/[id]/route.ts の25行目参照)。なお、isValidWorktreeIdsrc/lib/auto-yes-manager.ts からimportする(既存の全7 APIルートと同じパターン)。import元のモジュール名は意味的に不自然だが、既存パターンの踏襲を優先する。将来的には validation専用モジュールへの移動を別Issueで検討する

実装タスク

タブ追加

  • LeftPaneTab 型に 'git' を追加(src/types/ui-state.ts
  • LeftPaneTab 型に 'git' を追加(src/components/worktree/LeftPaneTabSwitcher.tsx 19行目) --- 同一の型定義が2箇所に存在するため、両方を同期的に更新する必要がある。片方のみ更新すると型不整合によるコンパイルエラーまたはランタイムエラーが発生する。理想的には ui-state.ts に一元化すべきだが、本Issueでは最低限両方の更新を必須とする
  • WorktreeDetailRefactored.tsx の変更: (1) デスクトップ用 leftPaneContentuseMemo(2037行目付近)内に leftPaneTab === 'git' の条件分岐を追加し、新規GitPaneコンポーネントをレンダリング (2) 新規GitPaneコンポーネントのimport追加。なお、モバイル用 renderMobileContent の switch文(880行目付近)にはGitケースを追加しない(MobileTabBarにGitタブがないため到達しない)
  • LeftPaneTabSwitcher.tsx にGitタブを追加(ラベル: 'Git'、ハードコード。i18n化は別Issue)
  • useWorktreeUIState reducerの対応確認(※ reducer側の変更は不要。SET_LEFT_PANE_TAB アクションは LeftPaneTab 型をペイロードとして受け取り、直接代入する設計のため、型定義の更新のみで対応完了)

API実装

  • GET /api/worktrees/[id]/git/log エンドポイントの実装(git log --max-count=50 --format=... ベース)
  • GET /api/worktrees/[id]/git/show/[commitHash] エンドポイントの実装
  • GET /api/worktrees/[id]/git/diff エンドポイントの実装
  • commitHashバリデーション([0-9a-f]{7,40})の実装
  • filePathバリデーション(isPathSafe())の実装
  • '--' セパレータによるgitオプション注入防止
  • 全APIルートで isValidWorktreeId(params.id) によるworktree IDバリデーションを実装(既存パターン踏襲。import { isValidWorktreeId } from '@/lib/auto-yes-manager' を使用)
  • git-utils.tsexecGitCommand をexportするか、新規関数(getGitLog, getGitShow, getGitDiff)を git-utils.ts に追加して内部で execGitCommand を再利用する設計とする。タイムアウト引数の追加(git log用: 3000-5000ms)も合わせて実装する。注意: execGitCommand にタイムアウト引数を追加する場合、オプショナル引数(timeout?: number、デフォルト GIT_COMMAND_TIMEOUT_MS = 1000ms)とし、既存の getGitStatus 内3箇所の呼び出しに影響しないよう後方互換性を維持すること
  • git log専用タイムアウト定数(GIT_LOG_TIMEOUT_MS = 3000、推奨3000-5000ms)の定義

UI実装

  • コミット一覧UIの実装(コミットメッセージ、作成者、日時)
  • コミット選択時の変更ファイル一覧表示
  • diff表示UI(追加行/削除行の色分け、unified diff形式)
  • ローディングインジケーターの実装(コミット履歴取得中、diff取得中)
  • 空状態メッセージの実装(コミットがない場合)
  • エラー状態の表示(Gitリポジトリでない場合、タイムアウト時)
  • ダークモード対応

モバイル対応

  • MobileTabBar(MobileTab 型)にはGitタブを追加しない(意図的な除外)

テスト

  • LeftPaneTabSwitcher.test.tsx にGitタブのレンダリング・クリックテストを追加(既存テストは3タブ前提のため4タブに更新)
  • git-utils.test.ts に新規関数(getGitLog / getGitShow / getGitDiff)のテストを追加
  • 新規APIルートの単体テスト(commitHashバリデーション、filePathバリデーション、worktreeIdバリデーション含む)
  • diff表示コンポーネントのテスト

受入条件

基本機能

  • 左ペインのタブに「Git」が表示される
  • Gitタブ選択時にコミット履歴が一覧表示される(最大50件)
  • コミットを選択すると変更ファイル一覧が表示される
  • 変更ファイルを選択するとファイル内容表示領域にdiffが表示される
  • diffの追加行/削除行が色分けされている(unified diff形式)
  • ダークモードで正しく表示される

エラーハンドリング・UI状態

  • コミット履歴取得中はローディングインジケーターが表示される
  • diff取得中はローディングインジケーターが表示される
  • コミットがない場合(空リポジトリ)は空状態メッセージが表示される
  • Gitリポジトリでない場合は適切なエラーメッセージが表示される

モバイル対応

  • モバイルビュー(MobileTabBar)にはGitタブが表示されない(意図的)

セキュリティ

  • APIはexecFileを使用し、コマンドインジェクションが防止されている
  • commitHashパラメータがバリデーションされている
  • すべての新規APIルートで isValidWorktreeId(params.id) によるworktree IDバリデーションが実装されている

テスト

  • ユニットテストが実装されている(git-utils拡張、APIルート、UIコンポーネント)
  • 全テスト(npm run test:unit)がパスする

スコープ外(将来拡張)

以下は本Issueのスコープ外とし、必要に応じて別Issueとして作成する:

  • ページネーション(Load More / 無限スクロール)
  • 2コミット間の任意比較
  • Working tree changes(未コミット変更)の表示
  • side-by-side diff表示
  • モバイルビューへのGitタブ追加
  • LeftPaneTabSwitcherのi18n化
  • LeftPaneTab型の定義一元化(現在2箇所に重複)
  • git log結果のキャッシュ(パフォーマンス問題発生時に検討。tmux-capture-cache.ts のTTL+singleflightパターンが参考になる)
  • isValidWorktreeId のvalidation専用モジュールへの移動(現在 auto-yes-manager.ts に定義されており、意味的に不適切な依存が発生している)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions