
# 差分

Pitmasterはgitの統一形式で差分を計算します。差分エンジンは、複数のアルゴリズム、統一差分形式、バイナリファイルの検出、ツリーレベルの再帰的な比較に対応しています。

モード`100644`の変更済みテキストファイルでは、`format()`は`git diff`と同じバイト列を生成します。その他のケースでは現在、異なる形式になります（[#3](https://github.com/inline0/pitmaster/issues/3)）:

- 新規ファイルと削除ファイルには`new file mode`/`deleted file mode`行、`index`行、`/dev/null`がありません。両側とも`a/<path>`と`b/<path>`と表示され、空側のハンク範囲は`-1,0`または`+1,0`と記されます（gitでは`-0,0`または`+0,0`と記されます）。
- `index`行はファイルモードにかかわらず、常に`100644`で終わります。
- モードのみの変更（例: `chmod +x`）では結果が生成されません。
- バイナリファイルでは`index`行なしで`Binary files differ`が出力されます（gitでは`Binary files a/<path> and b/<path> differ`が出力されます）。
- 名前変更は、内容の差分を含む単一の`diff --git a/<old> b/<new>`エントリーとして表示され、`similarity index`、`rename from`、`rename to`行はありません。

## 差分操作

### ステージされていない変更（作業ツリーとインデックスの比較）

```php
$diffs = $repo->diff();

foreach ($diffs as $diff) {
    echo $diff->format();
}
```

インデックス内の各ファイルを作業ツリー上の現在の状態と比較します。変更、削除、バイナリファイルを検出します。

### ステージ済みの変更（インデックスとHEADの比較）

```php
$diffs = $repo->diffStaged();

foreach ($diffs as $diff) {
    echo $diff->format();
}
```

現在のインデックスをHEADコミットのツリーと比較します。次のコミットに含まれる内容を表示します。内容が似ている削除ファイルと追加ファイルは、名前変更された1つのエントリーとして報告されます。最初のコミット前はHEADが存在しないため、結果は空です（[#5](https://github.com/inline0/pitmaster/issues/5)）。

現在、末尾の空行が共有コンテンツとして数えられるため、無関係な1行ファイル同士も名前変更の組み合わせとして一致します（[#4](https://github.com/inline0/pitmaster/issues/4)）。

### ツリー間の差分

```php
$a = $repo->readObject($repo->resolve('HEAD~1')->hex)->tree;
$b = $repo->head()->tree;
$diffs = $repo->diffTree($a, $b);
```

2つのツリーを再帰的に比較します。ツリーIDを渡してください。コミットIDはツリーに展開されず、空の結果になります。同じ`TreeDiff`エンジンが`diffStaged()`、`show()`、`logPath()`を支えています。

### パスで絞り込む

```php
$diffs = $repo->diff('src/specific-file.php');
```

パス指定は正確なファイルパスを1つ指定します。`diffStaged()`も同じ引数を受け取りますが、現時点では無視するため、ステージ済みの全ファイルを返します（[#5](https://github.com/inline0/pitmaster/issues/5)）。

### アルゴリズムを選択する

```php
$diffs = $repo->diff(null, 'patience');
$diffs = $repo->diffStaged(null, 'histogram');
$diffs = $repo->diffTree($a, $b, 'minimal');
```

有効な名前は`myers`（既定）、`patience`、`histogram`、`minimal`です。それ以外の名前を指定すると`InvalidArgumentException`がスローされます。

## DiffResultの構造

各`DiffResult`は、1つのファイルの差分を表します。

```php
use Pitmaster\Diff\DiffResult;

foreach ($diffs as $diff) {
    echo $diff->oldPath;    // Path in the old tree
    echo $diff->newPath;    // Path in the new tree
    echo $diff->binary;     // true for binary files
    echo $diff->oldHash;    // Hex hash of the old blob (null for added files)
    echo $diff->newHash;    // Hex hash of the new blob (null for deleted files)

    foreach ($diff->hunks as $hunk) {
        echo $hunk->header();  // @@ -1,5 +1,7 @@
        foreach ($hunk->lines as $line) {
            echo $line . "\n"; // ' context', '+added', '-removed'
        }
    }
}
```

### フォーマット済み出力

```php
echo $diff->format();
```

統一形式の差分を生成します。変更済みテキストファイルでは`git diff`と一致します:

```
diff --git a/src/File.php b/src/File.php
index abc1234..def5678 100644
--- a/src/File.php
+++ b/src/File.php
@@ -10,6 +10,8 @@ class File
     private string $name;

+    private int $count;
+
     public function __construct()
     {
```

### 変更を確認する

```php
if ($diff->hasChanges()) {
    // File was modified (or is binary with changes)
}
```

## ハンクの構造

各`Hunk`は、コンテキスト行を含む連続した変更箇所を表します。

```php
use Pitmaster\Diff\Hunk;

$hunk->oldStart;   // Starting line in old file
$hunk->oldCount;   // Number of lines from old file
$hunk->newStart;   // Starting line in new file
$hunk->newCount;   // Number of lines in new file
$hunk->lines;      // Array of diff lines (prefixed with ' ', '+', or '-')
$hunk->header();   // "@@ -10,6 +10,8 @@"
```

## 差分アルゴリズム

Pitmasterは4つの差分アルゴリズムを実装しています。既定はMyersです。

### Myers差分

標準的なO(ND)アルゴリズムで、gitの既定と同じです。最小編集スクリプトを生成します。

```php
use Pitmaster\Diff\MyersDiff;

$hunks = MyersDiff::diff($oldContent, $newContent);
```

`diff()`メソッドは2つの文字列を受け取り、`Hunk`オブジェクトの配列を返します。入力を改行で分割し、最短編集スクリプトを計算して、変更を3行のコンテキストを持つハンクにまとめます（gitの既定と同じ）。

### Patience差分

コードの構造をよりよく捉える差分です。まず一意な行を照合し、その間を埋めます。

```php
use Pitmaster\Diff\PatienceDiff;

$hunks = PatienceDiff::diff($oldContent, $newContent);
```

### Histogram差分

繰り返し行をより適切に扱うPatience差分の派生版です。JGitとEclipseで使用されています。

```php
use Pitmaster\Diff\HistogramDiff;

$hunks = HistogramDiff::diff($oldContent, $newContent);
```

### 最小差分

計算量は増えますが、可能な限り小さい差分（変更行数が最少）を生成します。

```php
use Pitmaster\Diff\MinimalDiff;

$hunks = MinimalDiff::diff($oldContent, $newContent);
```

## バイナリの検出

Pitmasterは先頭8192バイトにNULバイトが含まれる場合、内容をバイナリとして扱います（gitは先頭8000バイトを確認します）。

```php
if (MyersDiff::isBinary($content)) {
    // Binary file, skip line-level diff
}
```

バイナリファイルでは、`$binary = true`と空の`$hunks`配列を持つ`DiffResult`が生成されます。フォーマット済み出力は次のとおりです:

```
diff --git a/image.png b/image.png
Binary files differ
```

## 色付き出力

CLIは、ANSIカラーでのターミナル出力に`ColorDiff`を使用します。

```php
use Pitmaster\Diff\ColorDiff;

$colorized = ColorDiff::colorize($diff->format());
echo $colorized;
```

- 追加行（`+`）は緑で表示されます
- 削除行（`-`）は赤で表示されます
- ハンクヘッダー（`@@`）はシアンで表示されます
- ファイルヘッダー（`diff --git`、`index`、`---`、`+++`）は太字で表示されます

`pitmaster diff`は出力がパイプに渡される場合も常に色付けします。`pitmaster show`が色付けするのは、stdoutがターミナルの場合だけです。

## ツリー差分（内部）

`TreeDiff`クラスは2つのツリーオブジェクトを再帰的に比較し、変更されたファイルごとに`DiffResult`を生成します。

```php
use Pitmaster\Diff\TreeDiff;

$treeDiff = new TreeDiff($repo->objectDatabase());
$diffs = $treeDiff->diff($oldTreeId, $newTreeId);
```

ツリー差分は次に対応しています:
- 追加ファイル（新しいツリーのみに存在）
- 削除ファイル（古いツリーのみに存在）
- 変更ファイル（同じパスでblobハッシュが異なる）
- サブディレクトリの再帰的な比較
- 削除ファイルと追加ファイルの名前変更の組み合わせ（ハッシュが完全一致、または共有行が50%以上）

ハッシュが変わっていないエントリーはスキップされるため、モードの変更だけでは結果が生成されません。

## 単語差分

行内の単語レベルの変更を扱います。

```php
use Pitmaster\Diff\WordDiff;

$result = WordDiff::diff('the quick fox', 'the slow fox');
// 'the [-quick-]{+slow+} fox'
```
