Aller au contenu

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) :

  • 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).

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).

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).

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 :

Code
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 :

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