From 242f91c73e0c57aace65a214477224edd6eac43e Mon Sep 17 00:00:00 2001 From: Ralf Lang Date: Fri, 13 Nov 2026 11:07:37 +0100 Subject: [PATCH 1/2] test(native): Cover native engine lcpos behaviour. This is a followup to #8 by @bearoff. --- test/EngineTest.php | 20 ++++++++++++++++++++ test/Unnamespaced/EngineTest.php | 17 +++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/test/EngineTest.php b/test/EngineTest.php index 30a8c1f..32dbd82 100644 --- a/test/EngineTest.php +++ b/test/EngineTest.php @@ -10,8 +10,10 @@ namespace Horde\Text\Diff\Test; +use Horde\Text\Diff\AddOperation; use Horde\Text\Diff\ChangeOperation; use Horde\Text\Diff\CopyOperation; +use Horde\Text\Diff\DeleteOperation; use Horde\Text\Diff\Diff; use Horde\Text\Diff\Exception; use Horde\Text\Diff\NativeEngine; @@ -61,6 +63,24 @@ public function testNativeEngine() $this->_testDiff($diff); } + public function testNativeEngineWithRepeatedLines() + { + $diff = Diff::fromFileLineArrays( + ['B', 'C', 'C', 'C', 'C', 'D'], + ['C', 'D', 'C', 'D', 'B', 'C'], + NativeEngine::class + ); + $edits = $diff->getDiff()->toArray(); + + $this->assertCount(3, $edits); + $this->assertInstanceOf(DeleteOperation::class, $edits[0]); + $this->assertSame(['B', 'C', 'C', 'C'], $edits[0]->orig); + $this->assertInstanceOf(CopyOperation::class, $edits[1]); + $this->assertSame(['C', 'D'], $edits[1]->orig); + $this->assertInstanceOf(AddOperation::class, $edits[2]); + $this->assertSame(['C', 'D', 'B', 'C'], $edits[2]->final); + } + public function testShellEngine() { if (!exec('which diff')) { diff --git a/test/Unnamespaced/EngineTest.php b/test/Unnamespaced/EngineTest.php index 8c772b3..ffec389 100644 --- a/test/Unnamespaced/EngineTest.php +++ b/test/Unnamespaced/EngineTest.php @@ -51,6 +51,23 @@ public function testNativeEngine() $this->_testDiff($diff); } + public function testNativeEngineWithRepeatedLines() + { + $diff = new Horde_Text_Diff('Native', [ + ['B', 'C', 'C', 'C', 'C', 'D'], + ['C', 'D', 'C', 'D', 'B', 'C'], + ]); + $edits = $diff->getDiff(); + + $this->assertCount(3, $edits); + $this->assertInstanceOf('Horde_Text_Diff_Op_Delete', $edits[0]); + $this->assertSame(['B', 'C', 'C', 'C'], $edits[0]->orig); + $this->assertInstanceOf('Horde_Text_Diff_Op_Copy', $edits[1]); + $this->assertSame(['C', 'D'], $edits[1]->orig); + $this->assertInstanceOf('Horde_Text_Diff_Op_Add', $edits[2]); + $this->assertSame(['C', 'D', 'B', 'C'], $edits[2]->final); + } + public function testShellEngine() { if (!exec('which diff')) { From bf0977706aafb40a18769d7a897da44728736094 Mon Sep 17 00:00:00 2001 From: Ralf Lang Date: Fri, 13 Nov 2026 11:32:07 +0100 Subject: [PATCH 2/2] fix(NativeEngine): Formally define $k at a higher level before it is used by independent sibling levels --- .horde.yml | 4 +++- src/NativeEngine.php | 11 +++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.horde.yml b/.horde.yml index 83ff42d..067d0c8 100644 --- a/.horde.yml +++ b/.horde.yml @@ -44,7 +44,9 @@ dependencies: horde/cli: ^3 horde/test: ^3 vendor: horde -keywords: null +keywords: + - diff + - patch ci-platform: '8.1': - php: ^7.4 || ^8 diff --git a/src/NativeEngine.php b/src/NativeEngine.php index f4b6af9..b6d57ac 100644 --- a/src/NativeEngine.php +++ b/src/NativeEngine.php @@ -106,9 +106,10 @@ public function diff(): OperationList $this->xchanged = $this->ychanged = []; $this->xv = $this->yv = []; $this->xind = $this->yind = []; - unset($this->seq); - unset($this->in_seq); - unset($this->lcs); + // In modern PHP we cannot unset as NativeEngine may be subclassed and subclasses may have hooks + $this->seq = null; + $this->in_seq = null; + $this->lcs = null; // Skip leading common lines. for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) { @@ -243,6 +244,8 @@ protected function _diag($xoff, $xlim, $yoff, $ylim, $nchunks) $numer = $xlim - $xoff + $nchunks - 1; $x = $xoff; + // $k should be defined by the first iteration reaching an empty inseq(y) + $k = null; for ($chunk = 0; $chunk < $nchunks; $chunk++) { if ($chunk > 0) { for ($i = 0; $i <= $this->lcs; $i++) { @@ -263,7 +266,7 @@ protected function _diag($xoff, $xlim, $yoff, $ylim, $nchunks) assert($k > 0); $ymids[$k] = $ymids[$k - 1]; break; - } elseif ($y > $this->seq[$k - 1]) { + } elseif (is_int($k) && ($k > 0) && ($y > $this->seq[$k - 1])) { /* * Disable this assertion for now until we know why it * causes https://github.com/maintaina-com/Text_Diff/issues/12