# 機能設計書 42-jest-changed-files

## 概要

本ドキュメントは、jest-changed-filesパッケージの機能設計について記載する。jest-changed-filesは、Git、Mercurial（hg）、Sapling（sl）を使用して変更されたファイルを検出するパッケージである。

### 本機能の処理概要

jest-changed-filesは、バージョン管理システム（VCS）と連携して、コミット間またはワーキングディレクトリで変更されたファイルを特定する。これにより、変更に関連するテストのみを実行する機能を提供する。

**業務上の目的・背景**：大規模なプロジェクトでは、すべてのテストを実行すると時間がかかりすぎる場合がある。jest-changed-filesは、変更されたファイルのみを特定することで、関連するテストだけを効率的に実行できるようにする。これにより、開発者のフィードバックループが短縮され、CI/CDパイプラインの実行時間も最適化される。

**機能の利用シーン**：`jest --onlyChanged`オプションや`jest --changedSince`オプションを使用した際に、変更ファイルの検出に利用される。また、ウォッチモードでファイル変更を検知する際にも活用される。

**主要な処理内容**：
1. getChangedFilesForRoots: 指定されたルートディレクトリから変更ファイルを取得
2. findRepos: 各ルートディレクトリがどのVCSで管理されているか検出
3. Git/Hg/Sl アダプター: 各VCSコマンドを実行して変更ファイルを取得
4. ルートディレクトリの検出: 各VCSのリポジトリルートを特定

**関連システム・外部連携**：Git、Mercurial（hg）、Sapling（sl）の各コマンドラインツールと連携。execaライブラリを使用してプロセスを起動し、コマンドの出力を解析する。

**権限による制御**：特になし。ただし、VCSコマンドの実行にはファイルシステムとVCSへのアクセス権が必要。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | CLIのみで使用される機能のため関連画面なし |

## 機能種別

ファイル検索 / 外部コマンド実行

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| roots | Array<string> | Yes | 検索対象のルートディレクトリパス配列 | 有効なディレクトリパス |
| options.lastCommit | boolean | No | 直近のコミットの変更を取得 | - |
| options.withAncestor | boolean | No | HEAD^との差分を取得 | - |
| options.changedSince | string | No | 指定コミット/ブランチからの差分を取得 | 有効なコミットID/ブランチ名 |
| options.includePaths | Array<string> | No | 検索対象パスを制限 | - |

### 入力データソース

- ファイルシステム（ディレクトリ構造）
- バージョン管理システム（Git、Mercurial、Sapling）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| changedFiles | Set<string> | 変更されたファイルの絶対パス集合 |
| repos | Repos | 検出されたリポジトリ情報（git/hg/slそれぞれのパスセット） |

### 出力先

- 呼び出し元への戻り値

## 処理フロー

### 処理シーケンス

```
1. getChangedFilesForRoots呼び出し
   └─ rootsとoptionsを受け取る
2. findRepos実行
   └─ 各ルートに対してGit/Hg/Slのルートを並列検索
3. 各VCSの変更ファイル取得
   └─ Git: git diff/show/ls-filesコマンド実行
   └─ Hg: hg statusコマンド実行
   └─ Sl: sl statusコマンド実行
4. 結果のマージ
   └─ すべてのVCSからの変更ファイルをSetに集約
5. 結果返却
   └─ changedFilesとreposを返却
```

### フローチャート

```mermaid
flowchart TD
    A[getChangedFilesForRoots開始] --> B[findRepos実行]
    B --> C{各VCSルート検出}
    C --> D[Gitルート検出]
    C --> E[Hgルート検出]
    C --> F[Slルート検出]
    D --> G{オプション判定}
    E --> G
    F --> G
    G -->|lastCommit| H[git show --name-only]
    G -->|changedSince| I[git diff + staged + unstaged]
    G -->|デフォルト| J[staged + unstaged]
    H --> K[結果マージ]
    I --> K
    J --> K
    K --> L[changedFiles + repos返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-42-01 | 並列実行制限 | 最大5プロセスまで同時実行（p-limitで制御） | 常時 |
| BR-42-02 | changedSince優先 | withAncestorがtrueの場合、changedSinceを'HEAD^'に設定 | Gitの場合 |
| BR-42-03 | HGPLAIN設定 | Mercurial/Sapling実行時にHGPLAIN=1を設定 | Hg/Sl使用時 |
| BR-42-04 | Steam Locomotive検出 | slコマンドがSteam Locomotiveの場合はスキップ | Sl検出時 |

### 計算ロジック

Git変更ファイル取得ロジック:
```
if (lastCommit) {
  return git show --name-only HEAD
} else if (changedSince) {
  committed = git diff {changedSince}...HEAD
  staged = git diff --cached
  unstaged = git ls-files --other --modified
  return committed + staged + unstaged
} else {
  staged = git diff --cached
  unstaged = git ls-files --other --modified
  return staged + unstaged
}
```

## データベース操作仕様

### 操作別データベース影響一覧

該当なし（データベース操作は行わない）

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | null返却 | VCSコマンド実行失敗 | nullを返却し、そのVCSは使用しない |
| - | 空配列 | VCSが見つからない | 空の結果を返す |

### リトライ仕様

リトライは行わない。VCSコマンド失敗時はnullを返却して処理を継続。

## トランザクション仕様

該当なし

## パフォーマンス要件

- VCSコマンド実行: 数秒以内
- 並列実行数: 最大5プロセス

## セキュリティ考慮事項

- execaを使用したコマンド実行。ユーザー入力を直接コマンドに渡すことはない
- HGPLAIN=1設定により、Mercurialの設定による出力変更を防止

## 備考

- Saplingはslコマンドを使用するが、sl（Steam Locomotive）と名前が衝突する可能性がある
- Steam Locomotiveの場合、出力の最初の文字がエスケープ文字（0x1B）であることで検出

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

まず、変更ファイル検出に使用される型定義を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | types.ts | `packages/jest-changed-files/src/types.ts` | Options、Repos、ChangedFiles、SCMAdapter型の定義を把握 |

**読解のコツ**: SCMAdapterインターフェースは各VCSアダプターが実装すべきメソッドを定義している。

#### Step 2: エントリーポイントを理解する

パッケージの主要なエクスポート関数を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.ts | `packages/jest-changed-files/src/index.ts` | getChangedFilesForRoots、findReposの実装 |

**主要処理フロー**:
1. **20行目**: pLimitで並列実行数を5に制限
2. **22-24行目**: 各VCSのルート検索関数をmutexでラップ
3. **26-60行目**: getChangedFilesForRoots - メイン関数
4. **62-74行目**: findRepos - リポジトリ検出関数

#### Step 3: Git アダプターを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | git.ts | `packages/jest-changed-files/src/git.ts` | Gitコマンドによる変更ファイル検出 |

**主要処理フロー**:
- **13-23行目**: findChangedFilesUsingCommand - 共通コマンド実行関数
- **26-95行目**: findChangedFiles - 条件に応じたGitコマンド選択
- **34-46行目**: lastCommitオプション時の処理（git show）
- **47-76行目**: changedSinceオプション時の処理（committed + staged + unstaged）
- **77-94行目**: デフォルト処理（staged + unstaged）
- **97-107行目**: getRoot - git rev-parse --show-cdupでルート検出

#### Step 4: Mercurial アダプターを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | hg.ts | `packages/jest-changed-files/src/hg.ts` | hgコマンドによる変更ファイル検出 |

**主要処理フロー**:
- **13行目**: HGPLAIN=1環境変数設定
- **16-38行目**: findChangedFiles - hg status -amnuコマンド実行
- **40-48行目**: getRoot - hg rootコマンドでルート検出

#### Step 5: Sapling アダプターを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | sl.ts | `packages/jest-changed-files/src/sl.ts` | slコマンドによる変更ファイル検出 |

**主要処理フロー**:
- **17行目**: HGPLAIN=1環境変数設定
- **20行目**: isSteamLocomotiveフラグ
- **47-76行目**: getRoot - Steam Locomotive検出ロジック
- **57-63行目**: エスケープ文字（0x1B = 27）でSteam Locomotive判定

### プログラム呼び出し階層図

```
getChangedFilesForRoots(roots, options)
    │
    ├─ findRepos(roots)
    │      ├─ findGitRoot(dir) → git.getRoot()
    │      ├─ findHgRoot(dir) → hg.getRoot()
    │      └─ findSlRoot(dir) → sl.getRoot()
    │
    ├─ git.findChangedFiles(repo, options)
    │      └─ findChangedFilesUsingCommand(args, cwd)
    │             └─ execa('git', args, {cwd})
    │
    ├─ hg.findChangedFiles(repo, options)
    │      └─ execa('hg', args, {cwd, env})
    │
    └─ sl.findChangedFiles(repo, options)
           └─ execa('sl', args, {cwd, env})
```

### データフロー図

```
[入力]                    [処理]                         [出力]

roots (ディレクトリ) ────▶ findRepos() ─────────────────▶ Repos {git, hg, sl}
                              │
                              ├─ git.getRoot() ◀── git rev-parse --show-cdup
                              ├─ hg.getRoot() ◀── hg root
                              └─ sl.getRoot() ◀── sl root

options ─────────────────▶ findChangedFiles() ──────────▶ Set<string>
                              │
                              ├─ git: diff/show/ls-files
                              ├─ hg: status -amnu
                              └─ sl: status -amnu

changedFiles + repos ───▶ 結果オブジェクト ────────────▶ ChangedFiles
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.ts | `packages/jest-changed-files/src/index.ts` | ソース | エントリーポイント、主要関数 |
| types.ts | `packages/jest-changed-files/src/types.ts` | ソース | 型定義 |
| git.ts | `packages/jest-changed-files/src/git.ts` | ソース | Gitアダプター |
| hg.ts | `packages/jest-changed-files/src/hg.ts` | ソース | Mercurialアダプター |
| sl.ts | `packages/jest-changed-files/src/sl.ts` | ソース | Saplingアダプター |
