
# Различия

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`) результат не формируется.
- Для двоичных файлов выводится `Binary files differ` без строки `index`, тогда как 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. Показывает, что войдёт в следующий коммит. Удалённые и добавленные файлы с похожим содержимым отображаются одной записью о переименовании. До первого коммита HEAD отсутствует, поэтому результат пуст ([#5](https://github.com/inline0/pitmaster/issues/5)).

Сейчас при сопоставлении переименований также совпадают несвязанные однострочные файлы, поскольку завершающая пустая строка считается общим содержимым ([#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);
```

Рекурсивно сравнивает два дерева. Передавайте идентификаторы деревьев: идентификаторы коммитов не преобразуются в деревья и дают пустой результат. Тот же механизм `TreeDiff` лежит в основе `diffStaged()`, `show()` и `logPath()`.

### Фильтрация по пути

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

Спецификатор пути — это точный путь к одному файлу. `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` представляет различия для одного файла.

```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 реализует четыре алгоритма вычисления различий. По умолчанию используется Myers.

### Различия Myers

Стандартный алгоритм O(ND), как и используемый по умолчанию алгоритм в git. Формирует минимальные сценарии редактирования.

```php
use Pitmaster\Diff\MyersDiff;

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

Метод `diff()` принимает две строки и возвращает массив объектов `Hunk`. Он разбивает входные данные по символам новой строки, вычисляет кратчайший сценарий редактирования и группирует изменения во фрагменты с 3 строками контекста (как и используемый по умолчанию параметр git).

### Различия Patience

Лучше выявляет структуру кода. Сначала сопоставляет уникальные строки, затем заполняет промежутки между ними.

```php
use Pitmaster\Diff\PatienceDiff;

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

### Гистограммные различия

Вариант алгоритма 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 байтах (git проверяет первые 8000).

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

Для двоичных файлов создаётся `DiffResult` с `$binary = true` и пустым массивом `$hunks`. Форматированный вывод:

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

## Цветной вывод

Для вывода в терминал с цветами ANSI CLI использует `ColorDiff`.

```php
use Pitmaster\Diff\ColorDiff;

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

- Добавленные строки (`+`) отображаются зелёным цветом
- Удалённые строки (`-`) отображаются красным цветом
- Заголовки фрагментов (`@@`) отображаются голубым цветом
- Заголовки файлов (`diff --git`, `index`, `---`, `+++`) отображаются полужирным шрифтом

`pitmaster diff` всегда добавляет цвет, даже если его вывод перенаправлен. `pitmaster show` добавляет цвет, только когда stdout — терминал.

## Сравнение деревьев (внутренний механизм)

Класс `TreeDiff` рекурсивно сравнивает два объекта дерева и создает `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'
```
