<commit_before><?php
/**
 * This file is part of the php-merge package.
 *
 * (c) <NAME> <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PhpMerge;

use SebastianBergmann\Diff\Differ;

/**
 * Class PhpMerge merges three texts by lines.
 *
 * The merge class which in most cases will work, the diff is calculated using
 * an instance of \SebastianBergmann\Diff\Differ. The merge algorithm goes
 * through all the lines and decides which to line to use.
 *
 * @package    PhpMerge
 * @author     <NAME> <<EMAIL>>
 * @copyright  <NAME> <<EMAIL>>
 * @license    https://opensource.org/licenses/MIT
 * @version    Release: @package_version@
 * @link       http://github.com/bircher/php-merge
 */
class PhpMerge extends PhpMergeBase implements PhpMergeInterface
{

    /**
     * The differ used to create the diffs.
     *
     * @var \SebastianBergmann\Diff\Differ
     */
    protected $differ;

    /**
     * PhpMerge constructor.
     */
    public function __construct(Differ $differ = null)
    {
        if (!$differ) {
            $differ = new Differ();
        }
        $this->differ = $differ;
    }


    /**
     * {@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 (!empty($conflicts)) {
            throw new MergeException('A merge conflict has occured.', $conflicts, $merged);
        }

        return $merged;
    }

    /**
     * The merge algorithm.
     *
     * @param Line[] $base
     *   The lines of the original text.
     * @param Hunk[] $remote
     *   The hunks of the remote changes.
     * @param Hunk[] $local
     *   The hunks of the local changes.
     * @param MergeConflict[] $conflicts
     *   The merge conflicts.
     *
     * @return string[]
     *   The merged text.
     */
    protected static function mergeHunks($base, $remote, $local, &$conflicts = [])
    {
        $remote = new \ArrayObject($remote);
        $local = new \ArrayObject($local);

        $merged = [];

        $a = $remote->getIterator();
        $b = $local->getIterator();
        $flipped = false;
        $i = -1;

        // Loop over all indexes of the base and all hunks.
        while ($i <  count($base) || $a->valid() || $b->valid()) {
            // Assure that $aa is the first hunk by swaping $a and $b
            if ($a->valid() && $b->valid() && $a->current()->getStart() > $b->current()->getStart()) {
                self::swap($a, $b, $flipped);
            } elseif (!$a->valid() && $b->valid()) {
                self::swap($a, $b, $flipped);
            }
            /** @var Hunk $aa */
            $aa = $a->current();
            /** @var Hunk $bb */
            $bb = $b->current();

            if ($aa) {
                assert($aa->getStart() >= $i, 'The start of the hunk is after the current index.');
            }
            // The hunk starts at the current index.
            if ($aa && $aa->getStart() == $i) {
                // Hunks from both sources start with the same index.
                if ($bb && $bb->getStart() == $i) {
                    if ($aa != $bb) {
                        // If the hunks are not the same its a conflict.
                        $conflicts[] = self::prepareConflict($base, $a, $b, $flipped, count($merged));
                        $aa = $a->current();
                    } else {
                        // Advance $b it is the same as $a and will be merged.
                        $b->next();
                    }
                } elseif ($aa->hasIntersection($bb)) {
                    // The end overlaps with the start of the next other hunk.
                    $conflicts[] = self::prepareConflict($base, $a, $b, $flipped, count($merged));
                    $aa = $a->current();
                }
            }
            // The conflict resolution could mean the hunk starts now later.
            if ($aa && $aa->getStart() == $i) {
                if ($aa->getType() == Hunk::ADDED && $i >= 0) {
                    $merged[] = $base[$i]->getContent();
                }

                if ($aa->getType() != Hunk::REMOVED) {
                    foreach ($aa->getAddedLines() as $line) {
                        $merged[] = $line->getContent();
                    }
                }
                $i = $aa->getEnd();
                $a->next();
            } else {
                // Not dealing with a change, so return the line from the base.
                if ($i>=0) {
                    $merged[] = $base[$i]->getContent();
                }
            }
            // Finally, advance the index.
            $i++;
        }
        return implode("\n", $merged);
    }

    /**
     * Get a Merge conflict from the two array iterators.
     *
     * @param Line[] $base
     *   The original lines of the base text.
     * @param \ArrayIterator $a
     *   The first hunk iterator.
     * @param \ArrayIterator $b
     *   The second hunk iterator.
     * @param bool $flipped
     *   Whether or not the a corresponds to remote and b to local.
     * @param int $mergedLine
     *   The line on which the merge conflict appears on the merged result.
     *
     * @return MergeConflict
     *   The merge conflict.
     */
    protected static function prepareConflict($base, &$a, &$b, &$flipped, $mergedLine)
    {<commit_msg>Fix merged text in exception.<commit_after><?php
/**
 * This file is part of the php-merge package.
 *
 * (c) <NAME> <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PhpMerge;

use SebastianBergmann\Diff\Differ;

/**
 * Class PhpMerge merges three texts by lines.
 *
 * The merge class which in most cases will work, the diff is calculated using
 * an instance of \SebastianBergmann\Diff\Differ. The merge algorithm goes
 * through all the lines and decides which to line to use.
 *
 * @package    PhpMerge
 * @author     <NAME> <<EMAIL>>
 * @copyright  <NAME> <<EMAIL>>
 * @license    https://opensource.org/licenses/MIT
 * @version    Release: @package_version@
 * @link       http://github.com/bircher/php-merge
 */
class PhpMerge extends PhpMergeBase implements PhpMergeInterface
{

    /**
     * The differ used to create the diffs.
     *
     * @var \SebastianBergmann\Diff\Differ
     */
    protected $differ;

    /**
     * PhpMerge constructor.
     */
    public function __construct(Differ $differ = null)
    {
        if (!$differ) {
            $differ = new Differ();
        }
        $this->differ = $differ;
    }


    /**
     * {@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);
        $merged = implode("\n", $merged);

        if (!empty($conflicts)) {
            throw new MergeException('A merge conflict has occured.', $conflicts, $merged);
        }

        return $merged;
    }

    /**
     * The merge algorithm.
     *
     * @param Line[] $base
     *   The lines of the original text.
     * @param Hunk[] $remote
     *   The hunks of the remote changes.
     * @param Hunk[] $local
     *   The hunks of the local changes.
     * @param MergeConflict[] $conflicts
     *   The merge conflicts.
     *
     * @return string[]
     *   The merged text.
     */
    protected static function mergeHunks($base, $remote, $local, &$conflicts = [])
    {
        $remote = new \ArrayObject($remote);
        $local = new \ArrayObject($local);

        $merged = [];

        $a = $remote->getIterator();
        $b = $local->getIterator();
        $flipped = false;
        $i = -1;

        // Loop over all indexes of the base and all hunks.
        while ($i <  count($base) || $a->valid() || $b->valid()) {
            // Assure that $aa is the first hunk by swaping $a and $b
            if ($a->valid() && $b->valid() && $a->current()->getStart() > $b->current()->getStart()) {
                self::swap($a, $b, $flipped);
            } elseif (!$a->valid() && $b->valid()) {
                self::swap($a, $b, $flipped);
            }
            /** @var Hunk $aa */
            $aa = $a->current();
            /** @var Hunk $bb */
            $bb = $b->current();

            if ($aa) {
                assert($aa->getStart() >= $i, 'The start of the hunk is after the current index.');
            }
            // The hunk starts at the current index.
            if ($aa && $aa->getStart() == $i) {
                // Hunks from both sources start with the same index.
                if ($bb && $bb->getStart() == $i) {
                    if ($aa != $bb) {
                        // If the hunks are not the same its a conflict.
                        $conflicts[] = self::prepareConflict($base, $a, $b, $flipped, count($merged));
                        $aa = $a->current();
                    } else {
                        // Advance $b it is the same as $a and will be merged.
                        $b->next();
                    }
                } elseif ($aa->hasIntersection($bb)) {
                    // The end overlaps with the start of the next other hunk.
                    $conflicts[] = self::prepareConflict($base, $a, $b, $flipped, count($merged));
                    $aa = $a->current();
                }
            }
            // The conflict resolution could mean the hunk starts now later.
            if ($aa && $aa->getStart() == $i) {
                if ($aa->getType() == Hunk::ADDED && $i >= 0) {
                    $merged[] = $base[$i]->getContent();
                }

                if ($aa->getType() != Hunk::REMOVED) {
                    foreach ($aa->getAddedLines() as $line) {
                        $merged[] = $line->getContent();
                    }
                }
                $i = $aa->getEnd();
                $a->next();
            } else {
                // Not dealing with a change, so return the line from the base.
                if ($i>=0) {
                    $merged[] = $base[$i]->getContent();
                }
            }
            // Finally, advance the index.
            $i++;
        }
        return $merged;
    }

    /**
     * Get a Merge conflict from the two array iterators.
     *
     * @param Line[] $base
     *   The original lines of the base text.
     * @param \ArrayIterator $a
     *   The first hunk iterator.
     * @param \ArrayIterator $b
     *   The second hunk iterator.
     * @param bool $flipped
     *   Whether or not the a corresponds to remote and b to local.
     * @param int $mergedLine
     *   The line on which the merge conflict appears on the merged result.
     *
     * @return MergeConflict
     *   The merge conflict.
     */
    protected static function prepareConflict($base, &$a, &$b, &$flipped, $mergedLine)
    {
{'fraction_non_alphanumeric': 0.14064391188574196, 'fraction_numerical': 0.0006455256999919309, 'mean_word_length': 1.620850074011419, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 99, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3796739', 'n_tokens_mistral': 3665, 'n_tokens_neox': 3486, 'n_words': 1052}