public function testCreate() { $lines = [new Line(Line::ADDED, 'added', -1), new Line(Line::UNCHANGED, 'unchanged', 0), new Line(Line::REMOVED, 'replaced', 1), new Line(Line::ADDED, 'replacement', 1), new Line(Line::UNCHANGED, 'unchanged', 2), new Line(Line::REMOVED, 'removed', 3)]; $expected = [new Hunk($lines[0], Hunk::ADDED, -1, -1), new Hunk([$lines[2], $lines[3]], Hunk::REPLACED, 1, 1), new Hunk($lines[5], Hunk::REMOVED, 3, 3)]; $result = Hunk::createArray($lines); $this->assertEquals($expected, $result); $this->assertEquals([$lines[2], $lines[3]], $result[1]->getLines()); $this->assertEquals([$lines[2]], $result[1]->getRemovedLines()); $this->assertEquals([$lines[3]], $result[1]->getAddedLines()); $this->assertEquals(['replacement'], $result[1]->getLinesContent()); }
/** * @inheritDoc */ public function merge($base, $remote, $local) { // Skip merging if there is nothing to do. if ($merged = PhpMergeBase::simpleMerge($base, $remote, $local)) { return $merged; } $remoteDiff = Line::createArray($this->differ->diffToArray($base, $remote)); $localDiff = Line::createArray($this->differ->diffToArray($base, $local)); $baseLines = Line::createArray(array_map(function ($l) { return [$l, 0]; }, explode("\n", $base))); $remoteHunks = Hunk::createArray($remoteDiff); $localHunks = Hunk::createArray($localDiff); $conflicts = []; $merged = PhpMerge::mergeHunks($baseLines, $remoteHunks, $localHunks, $conflicts); if ($conflicts) { throw new MergeException('A merge conflict has occured.', $conflicts, $merged); } return $merged; }
/** * Get the conflicts from a file which is left with merge conflicts. * * @param string $file * The file name. * @param string $baseText * The original text used for merging. * @param string $remoteText * The first chaned text. * @param string $localText * The second changed text. * @param MergeConflict[] $conflicts * The merge conflicts will be apended to this array. * @param string[] $merged * The merged text resolving conflicts by using the first set of changes. */ protected static function getConflicts($file, $baseText, $remoteText, $localText, &$conflicts, &$merged) { $raw = new \ArrayObject(explode("\n", file_get_contents($file))); $lineIterator = $raw->getIterator(); $state = 'unchanged'; $conflictIndicator = ['<<<<<<< HEAD' => 'local', '||||||| merged common ancestors' => 'base', '=======' => 'remote', '>>>>>>> original' => 'end conflict']; // Create hunks from the text diff. $differ = new Differ(); $remoteDiff = Line::createArray($differ->diffToArray($baseText, $remoteText)); $localDiff = Line::createArray($differ->diffToArray($baseText, $localText)); $remote_hunks = new \ArrayObject(Hunk::createArray($remoteDiff)); $local_hunks = new \ArrayObject(Hunk::createArray($localDiff)); $remoteIterator = $remote_hunks->getIterator(); $localIterator = $local_hunks->getIterator(); $base = []; $remote = []; $local = []; $lineNumber = -1; $newLine = 0; $skipedLines = 0; $addingConflict = false; // Loop over all the lines in the file. while ($lineIterator->valid()) { $line = $lineIterator->current(); if (array_key_exists(trim($line), $conflictIndicator)) { // Check for a line matching a conflict indicator. $state = $conflictIndicator[trim($line)]; $skipedLines++; if ($state == 'end conflict') { // We just treated a merge conflict. $conflicts[] = new MergeConflict($base, $remote, $local, $lineNumber, $newLine); if ($lineNumber == -1) { $lineNumber = 0; } $lineNumber += count($base); $newLine += count($remote); $base = []; $remote = []; $local = []; $remoteIterator->next(); $localIterator->next(); if ($addingConflict) { // Advance the counter for conflicts with adding. $lineNumber++; $newLine++; $addingConflict = false; } $state = 'unchanged'; } } else { switch ($state) { case 'local': $local[] = $line; $skipedLines++; break; case 'base': $base[] = $line; $skipedLines++; if ($lineNumber == -1) { $lineNumber = 0; } break; case 'remote': $remote[] = $line; $merged[] = $line; break; case 'unchanged': if ($lineNumber == -1) { $lineNumber = 0; } $merged[] = $line; /** @var Hunk $r */ $r = $remoteIterator->current(); /** @var Hunk $l */ $l = $localIterator->current(); if ($r == $l) { // If they are the same, treat only one. $localIterator->next(); $l = $localIterator->current(); } // A hunk has been successfully merged, so we can just // tally the lines added and removed and skip forward. if ($r && $r->getStart() == $lineNumber) { if (!$r->hasIntersection($l)) { $lineNumber += count($r->getRemovedLines()); $newLine += count($r->getAddedLines()); $lineIterator->seek($newLine + $skipedLines - 1); $remoteIterator->next(); } else { // If the conflict occurs on added lines, the // next line in the merge will deal with it. if ($r->getType() == Hunk::ADDED && $l->getType() == Hunk::ADDED) { $addingConflict = true; } else { $lineNumber++; $newLine++; } } } elseif ($l && $l->getStart() == $lineNumber) { if (!$l->hasIntersection($r)) { $lineNumber += count($l->getRemovedLines()); $newLine += count($l->getAddedLines()); $lineIterator->seek($newLine + $skipedLines - 1); $localIterator->next(); } else { $lineNumber++; $newLine++; } } else { $lineNumber++; $newLine++; } break; } } $lineIterator->next(); } }