
# Різниця

Pitmaster обчислює різницю у форматі unified від git. Механізм обчислення різниці підтримує кілька алгоритмів, форматування unified diff, виявлення двійкових файлів і рекурсивне порівняння на рівні дерев.

Для зміненого текстового файла з режимом `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();
```

Створює вивід unified diff. Для зміненого текстового файла він збігається з `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);
```

### Різниця 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
}
```

Для двійкових файлів створюється `DiffResult` із `$binary = true` та порожнім масивом `$hunks`. Форматований вивід:

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

## Кольоровий вивід

CLI використовує `ColorDiff` для виводу в термінал з кольорами ANSI.

```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'
```
