跳转到正文

差异

Pitmaster 以 git 的统一格式计算差异。差异引擎支持多种算法、统一差异格式、二进制文件检测和树级递归比较。

对于模式为 100644 的已修改文本文件,format() 生成的字节与 git diff 相同。当前其他情况的格式有所不同(问题 #3):

  • 新建和已删除文件没有 new file mode / deleted file mode 行,也没有 index 行和 /dev/null:两侧都标记为 a/<path> 和 b/<path>;空侧的 hunk 范围写作 -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);

递归比较两棵树。传入树 ID:提交 ID 不会被解析为对应的树,因此会得到空结果。同一个 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 结构

每个 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 行上下文的 hunk(与 git 的默认值一致)。

Patience 差异

为代码生成结构更清晰的差异。先匹配唯一行,再填补它们之间的部分。

PHP
use Pitmaster\Diff\PatienceDiff;

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

Histogram 差异

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 字节中包含 NUL 字节(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

彩色输出

CLI 使用 ColorDiff 为终端输出添加 ANSI 颜色。

PHP
use Pitmaster\Diff\ColorDiff;

$colorized = ColorDiff::colorize($diff->format());
echo $colorized;
  • 新增行(+)显示为绿色
  • 删除行(-)显示为红色
  • Hunk 标头(@@)显示为青色
  • 文件标头(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'