Перейти к содержимому

Различия

Pitmaster вычисляет различия в унифицированном формате git. Механизм вычисления поддерживает несколько алгоритмов, форматирование унифицированных различий, обнаружение двоичных файлов и рекурсивное сравнение на уровне деревьев.

Для изменённого текстового файла в режиме 100644 format() формирует те же байты, что и git diff. Сейчас другие случаи форматируются иначе (#3):

  • У новых и удалённых файлов нет строк new file mode / deleted file mode, строки index и /dev/null: обе стороны обозначаются как a/<path> и b/<path>, а диапазон пустого фрагмента записывается как -1,0 или +1,0, тогда как git записывает -0,0 или +0,0.
  • Строка index всегда заканчивается на 100644 независимо от режима файла.
  • При изменении только режима (например, chmod +x) результат не формируется.
  • Для двоичных файлов выводится Binary files differ без строки index, тогда как git выводит Binary files a/<path> and b/<path> differ.
  • Переименования отображаются одной записью diff --git a/<old> b/<new> с различиями в содержимом, без строк similarity index, rename from или rename to.

Операции с различиями

Неиндексированные изменения (рабочее дерево и индекс)

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

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

Сравнивает каждый файл в индексе с его текущим состоянием в рабочем дереве. Обнаруживает изменения, удаления и двоичные файлы.

Проиндексированные изменения (индекс и HEAD)

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

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

Сравнивает текущий индекс с деревом коммита HEAD. Показывает, что войдёт в следующий коммит. Удалённые и добавленные файлы с похожим содержимым отображаются одной записью о переименовании. До первого коммита HEAD отсутствует, поэтому результат пуст (#5).

Сейчас при сопоставлении переименований также совпадают несвязанные однострочные файлы, поскольку завершающая пустая строка считается общим содержимым (#4).

Сравнение дерева с деревом

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

Рекурсивно сравнивает два дерева. Передавайте идентификаторы деревьев: идентификаторы коммитов не преобразуются в деревья и дают пустой результат. Тот же механизм TreeDiff лежит в основе diffStaged(), show() и logPath().

Фильтрация по пути

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

Спецификатор пути — это точный путь к одному файлу. diffStaged() принимает такой же аргумент, но пока игнорирует его, поэтому всегда возвращает все проиндексированные файлы (#5).

Выбор алгоритма

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

Допустимые названия: myers (по умолчанию), patience, histogram и minimal. Любое другое название приводит к исключению InvalidArgumentException.

Структура DiffResult

Каждый DiffResult представляет различия для одного файла.

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

Форматированный вывод

PHP
echo $diff->format();

Формирует вывод в формате унифицированных различий. Для изменённого текстового файла результат совпадает с 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()
     {

Проверка наличия изменений

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

Структура фрагмента

Каждый Hunk представляет непрерывную область изменений со строками контекста.

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

Алгоритмы вычисления различий

Pitmaster реализует четыре алгоритма вычисления различий. По умолчанию используется Myers.

Различия Myers

Стандартный алгоритм O(ND), как и используемый по умолчанию алгоритм в git. Формирует минимальные сценарии редактирования.

PHP
use Pitmaster\Diff\MyersDiff;

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

Метод diff() принимает две строки и возвращает массив объектов Hunk. Он разбивает входные данные по символам новой строки, вычисляет кратчайший сценарий редактирования и группирует изменения во фрагменты с 3 строками контекста (как и используемый по умолчанию параметр git).

Различия Patience

Лучше выявляет структуру кода. Сначала сопоставляет уникальные строки, затем заполняет промежутки между ними.

PHP
use Pitmaster\Diff\PatienceDiff;

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

Гистограммные различия

Вариант алгоритма Patience, который лучше обрабатывает повторяющиеся строки. Используется в JGit и Eclipse.

PHP
use Pitmaster\Diff\HistogramDiff;

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

Минимальные различия

Формирует минимально возможный результат (с наименьшим числом изменённых строк) ценой дополнительных вычислений.

PHP
use Pitmaster\Diff\MinimalDiff;

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

Обнаружение бинарных файлов

Pitmaster считает содержимое двоичным, если нулевой байт встречается в первых 8192 байтах (git проверяет первые 8000).

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

Для двоичных файлов создаётся DiffResult с $binary = true и пустым массивом $hunks. Форматированный вывод:

Код
diff --git a/image.png b/image.png
Binary files differ

Цветной вывод

Для вывода в терминал с цветами ANSI CLI использует ColorDiff.

PHP
use Pitmaster\Diff\ColorDiff;

$colorized = ColorDiff::colorize($diff->format());
echo $colorized;
  • Добавленные строки (+) отображаются зелёным цветом
  • Удалённые строки (-) отображаются красным цветом
  • Заголовки фрагментов (@@) отображаются голубым цветом
  • Заголовки файлов (diff --git, index, ---, +++) отображаются полужирным шрифтом

pitmaster diff всегда добавляет цвет, даже если его вывод перенаправлен. pitmaster show добавляет цвет, только когда stdout — терминал.

Сравнение деревьев (внутренний механизм)

Класс TreeDiff рекурсивно сравнивает два объекта дерева и создает DiffResult для каждого измененного файла.

PHP
use Pitmaster\Diff\TreeDiff;

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

Сравнение деревьев обрабатывает:

  • Добавленные файлы (есть только в новом дереве)
  • Удаленные файлы (есть только в старом дереве)
  • Измененные файлы (одинаковый путь, разные хеши blob-объектов)
  • Рекурсивное сравнение подкаталогов
  • Сопоставление переименований между удаленными и добавленными файлами (точное совпадение хешей или не менее 50% общих строк)

Записи с неизменившимся хешем пропускаются, поэтому одно лишь изменение режима не дает результата.

Сравнение слов

Для встроенного в строки сравнения изменений на уровне слов.

PHP
use Pitmaster\Diff\WordDiff;

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