
# Разлики

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>`, а диапазонът на празната страна в hunk се изписва като `-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

Всеки `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'
```
