Pular para o conteúdo

Diff

Pitmaster calcula diffs no formato unificado de git. O mecanismo de diff oferece suporte a vários algoritmos, formatação de diff unificado, detecção de arquivos binários e comparação recursiva no nível da árvore.

Para um arquivo de texto modificado com modo 100644, format() produz os mesmos bytes que git diff. Atualmente, os outros casos são formatados de forma diferente (#3):

  • Arquivos novos e excluídos não têm linha new file mode / deleted file mode, linha index nem /dev/null: os dois lados recebem os rótulos a/<path> e b/<path>, e o intervalo do hunk do lado vazio é escrito como -1,0 ou +1,0, enquanto git escreve -0,0 ou +0,0.
  • A linha index sempre termina em 100644, seja qual for o modo do arquivo.
  • Alterações apenas no modo (por exemplo, chmod +x) não geram resultado.
  • Arquivos binários exibem Binary files differ sem uma linha index, enquanto git exibe Binary files a/<path> and b/<path> differ.
  • Renomeações são mostradas como uma única entrada diff --git a/<old> b/<new> com diff de conteúdo, sem linhas similarity index, rename from ou rename to.

Operações de diff

Alterações não preparadas (árvore de trabalho vs índice)

PHP
$diffs = $repo->diff();

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

Compara cada arquivo do índice com seu estado atual na árvore de trabalho. Detecta modificações, exclusões e arquivos binários.

Alterações preparadas (índice vs HEAD)

PHP
$diffs = $repo->diffStaged();

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

Compara o índice atual com a árvore do commit HEAD. Mostra o que será incluído no próximo commit. Arquivos excluídos e adicionados com conteúdo semelhante são relatados como uma única entrada renomeada. Antes do primeiro commit, não há HEAD e o resultado é vazio (#5).

Atualmente, o pareamento de renomeações também corresponde a arquivos de uma linha sem relação entre si, porque a linha vazia final conta como conteúdo compartilhado (#4).

Diff entre árvores

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

Compara duas árvores recursivamente. Passe IDs de árvore: IDs de commit não são convertidos em suas árvores e retornam um resultado vazio. O mesmo mecanismo TreeDiff dá suporte a diffStaged(), show() e logPath().

Filtrar por caminho

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

A pathspec é um caminho exato de arquivo. diffStaged() aceita o mesmo argumento, mas por enquanto o ignora, então sempre retorna todos os arquivos preparados (#5).

Escolher um algoritmo

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

Os nomes válidos são myers (padrão), patience, histogram e minimal. Qualquer outro nome lança InvalidArgumentException.

Estrutura de DiffResult

Cada DiffResult representa o diff de um único arquivo.

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

Saída formatada

PHP
echo $diff->format();

Gera saída de diff unificado. Para um arquivo de texto modificado, corresponde a git diff:

Código
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()
     {

Verificar alterações

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

Estrutura de hunk

Cada Hunk representa uma região contínua de alterações com linhas 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 diff

Pitmaster implementa quatro algoritmos de diff. O padrão é Myers.

Diff de Myers

O algoritmo O(ND) padrão, igual ao padrão de git. Produz scripts de edição mínimos.

PHP
use Pitmaster\Diff\MyersDiff;

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

O método diff() recebe duas strings e retorna um array de objetos Hunk. Divide a entrada em novas linhas, calcula o script de edição mais curto e agrupa as alterações em hunks com 3 linhas de contexto (igual ao padrão de git).

Diff de Patience

Gera diffs estruturais melhores para código. Primeiro corresponde linhas únicas e, em seguida, preenche os intervalos entre elas.

PHP
use Pitmaster\Diff\PatienceDiff;

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

Diff de histograma

Uma variante do diff de patience que lida melhor com linhas repetidas. Usada por JGit e Eclipse.

PHP
use Pitmaster\Diff\HistogramDiff;

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

Diff mínimo

Gera o menor diff possível (com o menor número de linhas alteradas), ao custo de mais processamento.

PHP
use Pitmaster\Diff\MinimalDiff;

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

Detecção de binários

Pitmaster trata o conteúdo como binário quando ele contém um byte NUL em seus primeiros 8192 bytes (git verifica os primeiros 8000).

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

Arquivos binários produzem um DiffResult com $binary = true e um array $hunks vazio. A saída formatada é:

Código
diff --git a/image.png b/image.png
Binary files differ

Saída colorida

O CLI usa ColorDiff para saída de terminal com cores ANSI.

PHP
use Pitmaster\Diff\ColorDiff;

$colorized = ColorDiff::colorize($diff->format());
echo $colorized;
  • Linhas adicionadas (+) aparecem em verde
  • Linhas removidas (-) aparecem em vermelho
  • Cabeçalhos de hunk (@@) aparecem em ciano
  • Cabeçalhos de arquivo (diff --git, index, ---, +++) aparecem em negrito

pitmaster diff sempre aplica cores, inclusive quando sua saída é redirecionada por pipe. pitmaster show aplica cores somente quando stdout é um terminal.

Diff de árvore (interno)

A classe TreeDiff compara dois objetos de árvore recursivamente, produzindo um DiffResult para cada arquivo alterado.

PHP
use Pitmaster\Diff\TreeDiff;

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

O diff de árvore trata de:

  • Arquivos adicionados (presentes apenas na nova árvore)
  • Arquivos excluídos (presentes apenas na árvore antiga)
  • Arquivos modificados (mesmo caminho, hash de blob diferente)
  • Comparação recursiva de subdiretórios
  • Associação de renomeações entre arquivos excluídos e adicionados (hash exato ou pelo menos 50% de linhas em comum)

Entradas cujo hash não mudou são ignoradas, portanto uma alteração apenas no modo não gera um resultado.

Diff de palavras

Para alterações em linha no nível das palavras.

PHP
use Pitmaster\Diff\WordDiff;

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