差分
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)では結果が生成されません。 - バイナリファイルでは
index行なしでBinary files differが出力されます(gitではBinary files a/<path> and b/<path> differが出力されます)。 - 名前変更は、内容の差分を含む単一の
diff --git a/<old> b/<new>エントリーとして表示され、similarity index、rename from、rename to行はありません。
差分操作
ステージされていない変更(作業ツリーとインデックスの比較)
$diffs = $repo->diff();
foreach ($diffs as $diff) {
echo $diff->format();
}
インデックス内の各ファイルを作業ツリー上の現在の状態と比較します。変更、削除、バイナリファイルを検出します。
ステージ済みの変更(インデックスとHEADの比較)
$diffs = $repo->diffStaged();
foreach ($diffs as $diff) {
echo $diff->format();
}
現在のインデックスをHEADコミットのツリーと比較します。次のコミットに含まれる内容を表示します。内容が似ている削除ファイルと追加ファイルは、名前変更された1つのエントリーとして報告されます。最初のコミット前はHEADが存在しないため、結果は空です(#5)。
現在、末尾の空行が共有コンテンツとして数えられるため、無関係な1行ファイル同士も名前変更の組み合わせとして一致します(#4)。
ツリー間の差分
$a = $repo->readObject($repo->resolve('HEAD~1')->hex)->tree;
$b = $repo->head()->tree;
$diffs = $repo->diffTree($a, $b);
2つのツリーを再帰的に比較します。ツリーIDを渡してください。コミットIDはツリーに展開されず、空の結果になります。同じTreeDiffエンジンがdiffStaged()、show()、logPath()を支えています。
パスで絞り込む
$diffs = $repo->diff('src/specific-file.php');
パス指定は正確なファイルパスを1つ指定します。diffStaged()も同じ引数を受け取りますが、現時点では無視するため、ステージ済みの全ファイルを返します(#5)。
アルゴリズムを選択する
$diffs = $repo->diff(null, 'patience');
$diffs = $repo->diffStaged(null, 'histogram');
$diffs = $repo->diffTree($a, $b, 'minimal');
有効な名前はmyers(既定)、patience、histogram、minimalです。それ以外の名前を指定するとInvalidArgumentExceptionがスローされます。
DiffResultの構造
各DiffResultは、1つのファイルの差分を表します。
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'
}
}
}
フォーマット済み出力
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()
{
変更を確認する
if ($diff->hasChanges()) {
// File was modified (or is binary with changes)
}
ハンクの構造
各Hunkは、コンテキスト行を含む連続した変更箇所を表します。
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は4つの差分アルゴリズムを実装しています。既定はMyersです。
Myers差分
標準的なO(ND)アルゴリズムで、gitの既定と同じです。最小編集スクリプトを生成します。
use Pitmaster\Diff\MyersDiff;
$hunks = MyersDiff::diff($oldContent, $newContent);
diff()メソッドは2つの文字列を受け取り、Hunkオブジェクトの配列を返します。入力を改行で分割し、最短編集スクリプトを計算して、変更を3行のコンテキストを持つハンクにまとめます(gitの既定と同じ)。
Patience差分
コードの構造をよりよく捉える差分です。まず一意な行を照合し、その間を埋めます。
use Pitmaster\Diff\PatienceDiff;
$hunks = PatienceDiff::diff($oldContent, $newContent);
Histogram差分
繰り返し行をより適切に扱うPatience差分の派生版です。JGitとEclipseで使用されています。
use Pitmaster\Diff\HistogramDiff;
$hunks = HistogramDiff::diff($oldContent, $newContent);
最小差分
計算量は増えますが、可能な限り小さい差分(変更行数が最少)を生成します。
use Pitmaster\Diff\MinimalDiff;
$hunks = MinimalDiff::diff($oldContent, $newContent);
バイナリの検出
Pitmasterは先頭8192バイトにNULバイトが含まれる場合、内容をバイナリとして扱います(gitは先頭8000バイトを確認します)。
if (MyersDiff::isBinary($content)) {
// Binary file, skip line-level diff
}
バイナリファイルでは、$binary = trueと空の$hunks配列を持つDiffResultが生成されます。フォーマット済み出力は次のとおりです:
diff --git a/image.png b/image.png
Binary files differ
色付き出力
CLIは、ANSIカラーでのターミナル出力にColorDiffを使用します。
use Pitmaster\Diff\ColorDiff;
$colorized = ColorDiff::colorize($diff->format());
echo $colorized;
- 追加行(
+)は緑で表示されます - 削除行(
-)は赤で表示されます - ハンクヘッダー(
@@)はシアンで表示されます - ファイルヘッダー(
diff --git、index、---、+++)は太字で表示されます
pitmaster diffは出力がパイプに渡される場合も常に色付けします。pitmaster showが色付けするのは、stdoutがターミナルの場合だけです。
ツリー差分(内部)
TreeDiffクラスは2つのツリーオブジェクトを再帰的に比較し、変更されたファイルごとにDiffResultを生成します。
use Pitmaster\Diff\TreeDiff;
$treeDiff = new TreeDiff($repo->objectDatabase());
$diffs = $treeDiff->diff($oldTreeId, $newTreeId);
ツリー差分は次に対応しています:
- 追加ファイル(新しいツリーのみに存在)
- 削除ファイル(古いツリーのみに存在)
- 変更ファイル(同じパスでblobハッシュが異なる)
- サブディレクトリの再帰的な比較
- 削除ファイルと追加ファイルの名前変更の組み合わせ(ハッシュが完全一致、または共有行が50%以上)
ハッシュが変わっていないエントリーはスキップされるため、モードの変更だけでは結果が生成されません。
単語差分
行内の単語レベルの変更を扱います。
use Pitmaster\Diff\WordDiff;
$result = WordDiff::diff('the quick fox', 'the slow fox');
// 'the [-quick-]{+slow+} fox'