
# Diff

Pitmaster berechnet Diffs im einheitlichen Format von git. Die Diff-Engine unterstützt mehrere Algorithmen, einheitliche Diff-Formatierung, die Erkennung von Binärdateien und rekursive Vergleiche auf Baumebene.

Für eine geänderte Textdatei mit Modus `100644` erzeugt `format()` dieselben Bytes wie `git diff`. Andere Fälle werden derzeit anders formatiert ([#3](https://github.com/inline0/pitmaster/issues/3)):

- Neue und gelöschte Dateien haben keine Zeile `new file mode` / `deleted file mode`, keine Zeile `index` und kein `/dev/null`: Beide Seiten sind mit `a/<path>` und `b/<path>` beschriftet, und der Hunk-Bereich der leeren Seite wird als `-1,0` oder `+1,0` angegeben, wo git `-0,0` oder `+0,0` schreibt.
- Die Zeile `index` endet unabhängig vom Dateimodus immer mit `100644`.
- Änderungen nur am Modus (zum Beispiel `chmod +x`) erzeugen keine Ausgabe.
- Bei Binärdateien wird `Binary files differ` ohne eine Zeile `index` ausgegeben; git gibt `Binary files a/<path> and b/<path> differ` aus.
- Umbenennungen erscheinen als einzelner Eintrag `diff --git a/<old> b/<new>` mit Inhalts-Diff, ohne Zeilen `similarity index`, `rename from` oder `rename to`.

## Diff-Operationen

### Nicht bereitgestellte Änderungen (Arbeitsbaum gegenüber Index)

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

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

Vergleicht jede Datei im Index mit ihrem aktuellen Zustand im Arbeitsbaum. Erkennt Änderungen, Löschungen und Binärdateien.

### Bereitgestellte Änderungen (Index gegenüber HEAD)

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

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

Vergleicht den aktuellen Index mit dem Tree des HEAD-Commits. Zeigt, was im nächsten Commit enthalten sein wird. Gelöschte und hinzugefügte Dateien mit ähnlichem Inhalt werden als ein umbenannter Eintrag gemeldet. Vor dem ersten Commit gibt es keinen HEAD, daher ist das Ergebnis leer ([#5](https://github.com/inline0/pitmaster/issues/5)).

Beim Zuordnen von Umbenennungen werden derzeit auch nicht zusammengehörige einzeilige Dateien erkannt, weil die abschließende Leerzeile als gemeinsamer Inhalt zählt ([#4](https://github.com/inline0/pitmaster/issues/4)).

### Tree-zu-Tree-Diff

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

Vergleicht zwei Trees rekursiv. Übergib Tree-IDs: Commit-IDs werden nicht auf ihre Trees zurückgeführt und ergeben ein leeres Ergebnis. Dieselbe `TreeDiff`-Engine liegt `diffStaged()`, `show()` und `logPath()` zugrunde.

### Nach Pfad filtern

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

Die Pfadangabe ist ein exakter Dateipfad. `diffStaged()` akzeptiert dasselbe Argument, ignoriert es derzeit jedoch und gibt daher immer alle bereitgestellten Dateien zurück ([#5](https://github.com/inline0/pitmaster/issues/5)).

### Algorithmus auswählen

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

Gültige Namen sind `myers` (Standard), `patience`, `histogram` und `minimal`. Jeder andere Name löst `InvalidArgumentException` aus.

## DiffResult-Struktur

Jedes `DiffResult` stellt den Diff für eine einzelne Datei dar.

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

### Formatierte Ausgabe

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

Erzeugt eine einheitliche Diff-Ausgabe. Bei einer geänderten Textdatei entspricht sie `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()
     {
```

### Auf Änderungen prüfen

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

## Hunk-Struktur

Jedes `Hunk` stellt einen zusammenhängenden Änderungsbereich mit Kontextzeilen dar.

```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 @@"
```

## Diff-Algorithmen

Pitmaster implementiert vier Diff-Algorithmen. Standardmäßig wird Myers verwendet.

### Myers-Diff

Der Standardalgorithmus O(ND), identisch mit der Standardeinstellung von git. Erzeugt minimale Bearbeitungsskripte.

```php
use Pitmaster\Diff\MyersDiff;

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

Die Methode `diff()` akzeptiert zwei Zeichenfolgen und gibt ein Array von `Hunk`-Objekten zurück. Sie teilt die Eingabe an Zeilenumbrüchen auf, berechnet das kürzeste Bearbeitungsskript und gruppiert Änderungen in Hunks mit 3 Kontextzeilen (wie bei der Standardeinstellung von git).

### Patience-Diff

Liefert strukturell bessere Diffs für Code. Ordnet zuerst eindeutige Zeilen zu und füllt dann die Bereiche dazwischen auf.

```php
use Pitmaster\Diff\PatienceDiff;

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

### Histogramm-Diff

Eine Variante des Patience-Diffs, die besser mit wiederholten Zeilen umgeht. Wird von JGit und Eclipse verwendet.

```php
use Pitmaster\Diff\HistogramDiff;

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

### Minimaler Diff

Erzeugt den kleinstmöglichen Diff (mit den wenigsten geänderten Zeilen), benötigt dafür aber mehr Rechenaufwand.

```php
use Pitmaster\Diff\MinimalDiff;

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

## Binärerkennung

Pitmaster behandelt Inhalte als Binärdaten, wenn eines der ersten 8192 Bytes ein NUL-Byte enthält (git prüft die ersten 8000).

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

Binärdateien erzeugen ein `DiffResult` mit `$binary = true` und einem leeren `$hunks`-Array. Die formatierte Ausgabe lautet:

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

## Farbige Ausgabe

CLI verwendet `ColorDiff` für die Terminalausgabe mit ANSI-Farben.

```php
use Pitmaster\Diff\ColorDiff;

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

- Hinzugefügte Zeilen (`+`) werden grün dargestellt
- Entfernte Zeilen (`-`) werden rot dargestellt
- Hunk-Header (`@@`) werden cyan dargestellt
- Datei-Header (`diff --git`, `index`, `---`, `+++`) werden fett dargestellt

`pitmaster diff` färbt die Ausgabe immer ein, auch wenn sie weitergeleitet wird. `pitmaster show` färbt sie nur ein, wenn stdout ein Terminal ist.

## Tree-Diff (intern)

Die Klasse `TreeDiff` vergleicht zwei Tree-Objekte rekursiv und erzeugt für jede geänderte Datei ein `DiffResult`.

```php
use Pitmaster\Diff\TreeDiff;

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

Der Tree-Diff verarbeitet:
- Hinzugefügte Dateien (nur im neuen Tree vorhanden)
- Gelöschte Dateien (nur im alten Tree vorhanden)
- Geänderte Dateien (gleicher Pfad, anderer Blob-Hash)
- Rekursiven Vergleich von Unterverzeichnissen
- Zuordnung von Umbenennungen zwischen gelöschten und hinzugefügten Dateien (exakte Hash-Übereinstimmung oder mindestens 50 % gemeinsame Zeilen)

Einträge mit unverändertem Hash werden übersprungen, daher erzeugt eine reine Modusänderung kein Ergebnis.

## Wort-Diff

Für Änderungen auf Wortebene innerhalb von Zeilen.

```php
use Pitmaster\Diff\WordDiff;

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