
# Diferencia

Pitmaster calcula diferencias en el formato unificado de git. El motor de diferencias admite varios algoritmos, formato diff unificado, detección de archivos binarios y comparación recursiva a nivel de árbol.

Para un archivo de texto modificado con el modo `100644`, `format()` produce los mismos bytes que `git diff`. Actualmente, otros casos tienen un formato distinto ([#3](https://github.com/inline0/pitmaster/issues/3)):

- Los archivos nuevos y eliminados no tienen línea `new file mode` / `deleted file mode`, línea `index` ni `/dev/null`: ambos lados se etiquetan como `a/<path>` y `b/<path>`, y el rango de bloque del lado vacío se escribe como `-1,0` o `+1,0` donde git escribe `-0,0` o `+0,0`.
- La línea `index` siempre termina en `100644`, independientemente del modo del archivo.
- Los cambios solo de modo (por ejemplo, `chmod +x`) no producen resultados.
- Los archivos binarios muestran `Binary files differ` sin una línea `index`, mientras que git muestra `Binary files a/<path> and b/<path> differ`.
- Los renombrados aparecen como una sola entrada `diff --git a/<old> b/<new>` con una diferencia de contenido, sin líneas `similarity index`, `rename from` ni `rename to`.

## Operaciones de diferencias

### Cambios sin preparar (árbol de trabajo frente al índice)

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

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

Compara cada archivo del índice con su estado actual en el árbol de trabajo. Detecta modificaciones, eliminaciones y archivos binarios.

### Cambios preparados (índice frente a HEAD)

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

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

Compara el índice actual con el árbol del commit HEAD. Muestra lo que se incluirá en el siguiente commit. Los archivos eliminados y añadidos con contenido similar se informan como una sola entrada renombrada. Antes del primer commit no existe HEAD y el resultado está vacío ([#5](https://github.com/inline0/pitmaster/issues/5)).

Actualmente, el emparejamiento de renombrados también relaciona archivos de una sola línea que no guardan relación, porque la línea vacía final cuenta como contenido compartido ([#4](https://github.com/inline0/pitmaster/issues/4)).

### Diferencia entre árboles

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

Compara dos árboles de forma recursiva. Pasa los ID de árbol: los ID de commit no se convierten a sus árboles y producen un resultado vacío. El mismo motor `TreeDiff` sustenta `diffStaged()`, `show()` y `logPath()`.

### Filtrar por ruta

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

El pathspec es una ruta de archivo exacta. `diffStaged()` acepta el mismo argumento, pero por ahora lo ignora, así que siempre devuelve todos los archivos preparados ([#5](https://github.com/inline0/pitmaster/issues/5)).

### Elegir un algoritmo

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

Los nombres válidos son `myers` (predeterminado), `patience`, `histogram` y `minimal`. Cualquier otro nombre genera `InvalidArgumentException`.

## Estructura de DiffResult

Cada `DiffResult` representa la diferencia de un solo archivo.

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

### Salida formateada

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

Genera una salida de diff unificado. Para un archivo de texto modificado, coincide con `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()
     {
```

### Comprobar si hay cambios

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

## Estructura de los bloques

Cada `Hunk` representa una región continua de cambios con líneas de contexto.

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

## Algoritmos de diferencias

Pitmaster implementa cuatro algoritmos de diferencias. Myers es el predeterminado.

### Diferencia de Myers

El algoritmo estándar de O(ND), igual que el predeterminado de git. Genera secuencias de edición mínimas.

```php
use Pitmaster\Diff\MyersDiff;

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

El método `diff()` toma dos cadenas y devuelve una matriz de objetos `Hunk`. Divide la entrada por saltos de línea, calcula la secuencia de edición más corta y agrupa los cambios en bloques con 3 líneas de contexto (igual que el valor predeterminado de git).

### Diferencia de paciencia

Genera diferencias estructurales mejores para el código. Primero empareja las líneas únicas y luego completa los espacios entre ellas.

```php
use Pitmaster\Diff\PatienceDiff;

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

### Diferencia de histograma

Una variante de la diferencia de paciencia que gestiona mejor las líneas repetidas. La usan JGit y Eclipse.

```php
use Pitmaster\Diff\HistogramDiff;

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

### Diferencia mínima

Genera la diferencia más pequeña posible (con el menor número de líneas modificadas), a costa de más cálculos.

```php
use Pitmaster\Diff\MinimalDiff;

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

## Detección de binarios

Pitmaster trata el contenido como binario si contiene un byte NUL en sus primeros 8192 bytes (git comprueba los primeros 8000).

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

Los archivos binarios generan un `DiffResult` con `$binary = true` y una matriz `$hunks` vacía. La salida formateada es:

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

## Salida con colores

CLI usa `ColorDiff` para generar una salida de terminal con colores ANSI.

```php
use Pitmaster\Diff\ColorDiff;

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

- Las líneas añadidas (`+`) se muestran en verde
- Las líneas eliminadas (`-`) se muestran en rojo
- Los encabezados de bloque (`@@`) se muestran en cian
- Los encabezados de archivo (`diff --git`, `index`, `---`, `+++`) se muestran en negrita

`pitmaster diff` siempre aplica colores, incluso cuando su salida se redirige. `pitmaster show` solo aplica colores cuando stdout es un terminal.

## Diferencia entre árboles (interna)

La clase `TreeDiff` compara dos objetos de árbol de forma recursiva y produce un `DiffResult` por cada archivo modificado.

```php
use Pitmaster\Diff\TreeDiff;

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

La diferencia entre árboles gestiona:
- Archivos añadidos (solo presentes en el árbol nuevo)
- Archivos eliminados (solo presentes en el árbol antiguo)
- Archivos modificados (misma ruta, hash de blob distinto)
- Comparación recursiva de subdirectorios
- Emparejamiento de renombrados entre archivos eliminados y añadidos (coincidencia exacta de hash o al menos 50 % de líneas compartidas)

Se omiten las entradas cuyo hash no cambia, por lo que un cambio de modo por sí solo no produce resultados.

## Diferencia de palabras

Para cambios en línea a nivel de palabra dentro de las líneas.

```php
use Pitmaster\Diff\WordDiff;

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