diff --git a/src/Gitonomy/Git/Diff/File.php b/src/Gitonomy/Git/Diff/File.php index 4ef2e0a..8a73748 100644 --- a/src/Gitonomy/Git/Diff/File.php +++ b/src/Gitonomy/Git/Diff/File.php @@ -38,6 +38,7 @@ public function __construct( private readonly ?string $oldIndex, private readonly ?string $newIndex, private readonly bool $isBinary, + private readonly bool $isCopy = false, ) { } @@ -65,11 +66,21 @@ public function isModification(): bool /** * Indicates if it's a rename. * - * A rename can only occurs if it's a modification (not a creation or a deletion). + * A rename can only occurs if it's a modification (not a creation or a deletion), and is not a copy. */ public function isRename(): bool { - return $this->isModification() && $this->oldName !== $this->newName; + return $this->isModification() && $this->oldName !== $this->newName && !$this->isCopy; + } + + /** + * Indicates if it's a copy. + * + * Unlike a rename, the source file of a copy still exists after the change. + */ + public function isCopy(): bool + { + return $this->isCopy; } /** @@ -184,6 +195,7 @@ public function toArray(): array 'old_index' => $this->oldIndex, 'new_index' => $this->newIndex, 'is_binary' => $this->isBinary, + 'is_copy' => $this->isCopy, 'changes' => array_map(static function (FileChange $change) { return $change->toArray(); }, $this->changes), @@ -192,7 +204,7 @@ public function toArray(): array public static function fromArray(array $array): self { - $file = new self($array['old_name'], $array['new_name'], $array['old_mode'], $array['new_mode'], $array['old_index'], $array['new_index'], $array['is_binary']); + $file = new self($array['old_name'], $array['new_name'], $array['old_mode'], $array['new_mode'], $array['old_index'], $array['new_index'], $array['is_binary'], $array['is_copy'] ?? false); foreach ($array['changes'] as $change) { $file->addChange(FileChange::fromArray($change)); diff --git a/src/Gitonomy/Git/Parser/DiffParser.php b/src/Gitonomy/Git/Parser/DiffParser.php index 3e95d1a..43e968b 100644 --- a/src/Gitonomy/Git/Parser/DiffParser.php +++ b/src/Gitonomy/Git/Parser/DiffParser.php @@ -76,12 +76,16 @@ protected function doParse(): void $this->consumeNewLine(); } + $isCopy = false; if ($this->expects('similarity index ')) { $this->consumeRegexp('/\d{1,3}%\n/'); - $this->consume('rename from '); + $isCopy = $this->expects('copy from '); + if (!$isCopy) { + $this->consume('rename from '); + } $this->consumeTo("\n"); $this->consumeNewLine(); - $this->consume('rename to '); + $this->consume($isCopy ? 'copy to ' : 'rename to '); $this->consumeTo("\n"); $this->consumeNewLine(); } @@ -120,7 +124,7 @@ protected function doParse(): void $newIndex = null === $newIndex ? '' : $newIndex; $oldIndex = preg_match('/^0+$/', $oldIndex) ? null : $oldIndex; $newIndex = preg_match('/^0+$/', $newIndex) ? null : $newIndex; - $file = new File($oldName, $newName, $oldMode, $newMode, $oldIndex, $newIndex, $isBinary); + $file = new File($oldName, $newName, $oldMode, $newMode, $oldIndex, $newIndex, $isBinary, $isCopy); // 5. Diff while ($this->expects('@@ ')) { diff --git a/tests/Gitonomy/Git/Tests/DiffTest.php b/tests/Gitonomy/Git/Tests/DiffTest.php index a79fd4b..acfe081 100644 --- a/tests/Gitonomy/Git/Tests/DiffTest.php +++ b/tests/Gitonomy/Git/Tests/DiffTest.php @@ -199,6 +199,55 @@ public function testModeChangeFileWithRaw(): void $this->assertSame('d1af4b23d0cc9313e5b2d3ef2fb9696c94afaa82', $secondFile->getNewIndex()); } + public function testCopyFileWithoutRaw(): void + { + $this->expectUserDeprecationMessage('Using Diff::parse without raw information is deprecated. See https://github.com/gitonomy/gitlib/issues/227.'); + + $diff = Diff::parse(<<<'DIFF' + diff --git a/style.css b/common.css + similarity index 97% + copy from style.css + copy to common.css + index 1234567..89abcde 100644 + --- a/style.css + +++ b/common.css + @@ -1,3 +1,4 @@ + body { + + color: red; + } + + DIFF); + $firstFile = $diff->getFiles()[0]; + + $this->assertTrue($firstFile->isModification()); + $this->assertTrue($firstFile->isCopy()); + $this->assertFalse($firstFile->isRename()); + $this->assertFalse($firstFile->isDeletion()); + $this->assertFalse($firstFile->isCreation()); + $this->assertSame('style.css', $firstFile->getOldName()); + $this->assertSame('common.css', $firstFile->getNewName()); + $this->assertSame(1, $firstFile->getAdditions()); + } + + public function testPureCopyFileWithoutRaw(): void + { + $this->expectUserDeprecationMessage('Using Diff::parse without raw information is deprecated. See https://github.com/gitonomy/gitlib/issues/227.'); + + $diff = Diff::parse(<<<'DIFF' + diff --git a/style.css b/common.css + similarity index 100% + copy from style.css + copy to common.css + + DIFF); + $firstFile = $diff->getFiles()[0]; + + $this->assertTrue($firstFile->isCopy()); + $this->assertFalse($firstFile->isRename()); + $this->assertSame('style.css', $firstFile->getOldName()); + $this->assertSame('common.css', $firstFile->getNewName()); + } + public function testThrowErrorOnBlobGetWithoutIndex(): void { $repository = self::createEmptyRepository();