
# Diff

Pitmaster calcule les différences au format unifié de git. Le moteur de diff prend en charge plusieurs algorithmes, le formatage des diffs unifiés, la détection des fichiers binaires et la comparaison récursive au niveau des arbres.

Pour un fichier texte modifié en mode `100644`, `format()` produit les mêmes octets que `git diff`. Les autres cas sont formatés différemment actuellement ([#3](https://github.com/inline0/pitmaster/issues/3)) :

- Les fichiers créés et supprimés n’ont pas de ligne `new file mode` / `deleted file mode`, pas de ligne `index` et pas de `/dev/null` : les deux côtés portent les étiquettes `a/<path>` et `b/<path>`, et la plage du bloc du côté vide est notée `-1,0` ou `+1,0` là où git écrit `-0,0` ou `+0,0`.
- La ligne `index` se termine toujours par `100644`, quel que soit le mode du fichier.
- Les changements de mode seuls (par exemple `chmod +x`) ne produisent aucun résultat.
- Les fichiers binaires affichent `Binary files differ` sans ligne `index`, alors que git affiche `Binary files a/<path> and b/<path> differ`.
- Les renommages sont affichés comme une seule entrée `diff --git a/<old> b/<new>` avec un diff de contenu, sans lignes `similarity index`, `rename from` ou `rename to`.

## Opérations de diff

### Modifications non indexées (arbre de travail par rapport à l’index)

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

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

Compare chaque fichier de l’index à son état actuel dans l’arbre de travail. Détecte les modifications, les suppressions et les fichiers binaires.

### Modifications indexées (index par rapport à HEAD)

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

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

Compare l’index actuel à l’arbre du commit HEAD. Affiche ce qui sera inclus dans le prochain commit. Les fichiers supprimés et ajoutés au contenu similaire sont signalés comme une seule entrée renommée. Avant le premier commit, HEAD n’existe pas et le résultat est vide ([#5](https://github.com/inline0/pitmaster/issues/5)).

L’association des renommages correspond actuellement aussi à des fichiers d’une ligne sans rapport, car la ligne vide finale est comptée comme contenu commun ([#4](https://github.com/inline0/pitmaster/issues/4)).

### Diff entre deux arbres

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

Compare récursivement deux arbres. Transmettez les ID d’arbre : les ID de commit ne sont pas convertis en leurs arbres et produisent un résultat vide. Le même moteur `TreeDiff` est utilisé par `diffStaged()`, `show()` et `logPath()`.

### Filtrer par chemin

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

La spécification de chemin est un chemin de fichier exact. `diffStaged()` accepte le même argument, mais l’ignore pour le moment : il renvoie donc toujours tous les fichiers indexés ([#5](https://github.com/inline0/pitmaster/issues/5)).

### Choisir un algorithme

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

Les noms valides sont `myers` (par défaut), `patience`, `histogram` et `minimal`. Tout autre nom déclenche `InvalidArgumentException`.

## Structure de DiffResult

Chaque `DiffResult` représente le diff d’un seul fichier.

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

### Sortie formatée

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

Produit une sortie de diff unifié. Pour un fichier texte modifié, elle correspond à `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()
     {
```

### Vérifier les modifications

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

## Structure des blocs

Chaque `Hunk` représente une zone contiguë de modifications avec des lignes de contexte.

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

## Algorithmes de diff

Pitmaster implémente quatre algorithmes de diff. Myers est l’algorithme par défaut.

### Diff Myers

L’algorithme O(ND) standard, identique à celui utilisé par défaut par git. Il produit des scripts de modification minimaux.

```php
use Pitmaster\Diff\MyersDiff;

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

La méthode `diff()` prend deux chaînes et renvoie un tableau d’objets `Hunk`. Elle découpe l’entrée aux retours à la ligne, calcule le plus court script de modification et regroupe les changements en blocs avec 3 lignes de contexte (comme par défaut dans git).

### Diff Patience

Produit de meilleurs diffs structurels pour le code. Commence par faire correspondre les lignes uniques, puis complète les espaces entre elles.

```php
use Pitmaster\Diff\PatienceDiff;

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

### Diff Histogram

Une variante du diff Patience qui gère mieux les lignes répétées. Utilisée par JGit et Eclipse.

```php
use Pitmaster\Diff\HistogramDiff;

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

### Diff minimal

Produit le diff le plus court possible (avec le moins de lignes modifiées), au prix d’un calcul plus important.

```php
use Pitmaster\Diff\MinimalDiff;

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

## Détection des fichiers binaires

Pitmaster traite le contenu comme binaire s’il contient un octet NUL parmi ses 8192 premiers octets (git vérifie les 8000 premiers).

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

Les fichiers binaires produisent un `DiffResult` avec `$binary = true` et un tableau `$hunks` vide. La sortie formatée est :

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

## Sortie avec couleurs

Le CLI utilise `ColorDiff` pour produire une sortie de terminal avec des couleurs ANSI.

```php
use Pitmaster\Diff\ColorDiff;

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

- Les lignes ajoutées (`+`) s’affichent en vert
- Les lignes supprimées (`-`) s’affichent en rouge
- Les en-têtes de bloc (`@@`) s’affichent en cyan
- Les en-têtes de fichier (`diff --git`, `index`, `---`, `+++`) s’affichent en gras

`pitmaster diff` applique toujours les couleurs, même lorsque sa sortie est redirigée. `pitmaster show` n’applique les couleurs que lorsque stdout est un terminal.

## Diff entre arbres (interne)

La classe `TreeDiff` compare récursivement deux objets arbre et produit un `DiffResult` pour chaque fichier modifié.

```php
use Pitmaster\Diff\TreeDiff;

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

Le diff d’arbres gère :
- Les fichiers ajoutés (présents uniquement dans le nouvel arbre)
- Les fichiers supprimés (présents uniquement dans l’ancien arbre)
- Les fichiers modifiés (même chemin, hash de blob différent)
- La comparaison récursive des sous-répertoires
- L’association des renommages entre les fichiers supprimés et ajoutés (hash exact identique, ou au moins 50 % de lignes communes)

Les entrées dont le hash n’a pas changé sont ignorées ; un changement de mode seul ne produit donc aucun résultat.

## Diff par mot

Pour les modifications de mots en ligne à l’intérieur des lignes.

```php
use Pitmaster\Diff\WordDiff;

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