Перейти к содержимому

Слияние на уровне ячеек

Слияние на уровне столбцов обнаруживает, когда обе стороны изменили один и тот же столбец. Слияние на уровне ячеек углубляется и объединяет содержимое внутри этого столбца. Изменения в разных строках поля TEXT или в разных ключах объекта JSON, внесенные двумя людьми, можно объединить без конфликтов.

PHP
use Merql\CellMerge\CellMergeConfig;
use Merql\Merge\ThreeWayMerge;

$config = CellMergeConfig::auto();
$merge = new ThreeWayMerge($config);
$result = $merge->merge($base, $ours, $theirs);

Включение слияния на уровне ячеек

Слияние на уровне ячеек включается по желанию. Оно выполняется только в ThreeWayMerge, созданном с помощью CellMergeConfig. Без него каждый столбец, который обе стороны изменили на разные значения, становится конфликтом update_update.

Фасад Merql не принимает конфигурацию: Merql::merge(), Merql::patch() и Merql::plan() создают new ThreeWayMerge() без нее, а команда merge из CLI выполняется через Merql::merge(). Они всегда объединяют данные на уровне столбцов (#7).

Чтобы объединить снимки, сохраненные фасадом или CLI, слиянием на уровне ячеек, загрузите их и самостоятельно вызовите ThreeWayMerge. Результат — обычный MergeResult, поэтому фасад по-прежнему может его применить, а MergePlanBuilder превращает его в план слияния:

PHP
use Merql\CellMerge\CellMergeConfig;
use Merql\Merge\ThreeWayMerge;
use Merql\Merql;
use Merql\Plan\MergePlanBuilder;
use Merql\Snapshot\SnapshotStore;

$base   = SnapshotStore::load('base');
$ours   = SnapshotStore::load('ours');
$theirs = SnapshotStore::load('theirs');

$result = (new ThreeWayMerge(CellMergeConfig::auto()))->merge($base, $ours, $theirs);

$plan = (new MergePlanBuilder())->build('plan-1', $base, $ours, $theirs, $result);

Слияние ячеек выполняется только для столбцов, измененных обеими сторонами. Если столбец изменила только одна сторона, его значение берется без изменений.

CellMergeConfig

CellMergeConfig сопоставляет столбцы или типы столбцов с реализациями слияния ячеек. Фабрика auto() предоставляет разумные значения по умолчанию:

PHP
use Merql\CellMerge\CellMergeConfig;
use Merql\CellMerge\JsonCellMerger;
use Merql\CellMerge\TextCellMerger;

// Auto: column types containing "text" (TEXT, MEDIUMTEXT, LONGTEXT, ...) use
// TextCellMerger, column types containing "json" use JsonCellMerger.
$config = CellMergeConfig::auto();

// Custom: assign mergers to specific columns.
$config = (new CellMergeConfig())
    ->forColumn('posts.content', new TextCellMerger())
    ->forColumn('settings', new JsonCellMerger())
    ->forType('json', new JsonCellMerger());

Приоритет выбора стратегии слияния:

  1. Полное имя столбца (table.column).
  2. Имя столбца без квалификатора (column).
  3. Совпадение с шаблоном типа столбца (без учета регистра str_contains).
  4. По умолчанию: OpaqueCellMerger (всегда сообщает о конфликте, если значения различаются).

TextCellMerger

Объединяет многострочный текст с помощью алгоритма различий Майерса — того же, который git использует для содержимого файлов. Каждая строка рассматривается как единица, и изменения в разных строках объединяются без конфликтов.

PHP
use Merql\CellMerge\TextCellMerger;

$merger = new TextCellMerger();

Пример: слияние текста без конфликтов

Код
Base:    "line one\nline two\nline three"
Ours:    "LINE ONE\nline two\nline three"    // changed line 1
Theirs:  "line one\nline two\nLINE THREE"    // changed line 3

Изменены разные строки. В результате слияния получается "LINE ONE\nline two\nLINE THREE" без конфликтов.

Пример: конфликт при слиянии текста

Код
Base:    "line one\nline two"
Ours:    "changed by us\nline two"     // changed line 1
Theirs:  "changed by them\nline two"   // also changed line 1

Обе стороны изменили одну и ту же строку на разные значения. Стратегия слияния возвращает результат с конфликтом. Объединенное содержимое включает маркеры конфликта, похожие на git.

Как это работает

TextCellMerger делегирует работу ThreeWayMerge::merge() из pitmaster: он вычисляет различия Майерса между базой и каждой стороной, а затем объединяет два набора изменений. Если изменения пересекаются в одной и той же области строк, сообщается о конфликте.

JsonCellMerger

Объединяет объекты JSON, независимо сравнивая ключи верхнего уровня. Для каждого ключа применяются стандартные правила трехстороннего слияния.

PHP
use Merql\CellMerge\JsonCellMerger;

$merger = new JsonCellMerger();

Пример: слияние JSON без конфликтов

JSON
// Base
{"name": "Alice", "role": "editor", "active": true}

// Ours: changed "role"
{"name": "Alice", "role": "admin", "active": true}

// Theirs: changed "name"
{"name": "Bob", "role": "editor", "active": true}

Изменены разные ключи. Результат слияния:

JSON
{"name": "Bob", "role": "admin", "active": true}

Пример: конфликт JSON

JSON
// Base
{"color": "red"}

// Ours
{"color": "blue"}

// Theirs
{"color": "green"}

Обе стороны изменили ключ color на разные значения. Это конфликт. По умолчанию стратегия оставляет наше значение и сообщает об одном конфликте.

Правила для ключей

База Наша сторона Их сторона Результат
Ключ не изменен Ключ не изменен Принять базовое значение
Ключ не изменен Ключ изменен Принять значение их стороны
Ключ изменен Ключ не изменен Принять наше значение
Ключ изменен (одинаково) Ключ изменен (одинаково) Принять (совпадает)
Ключ изменен (по-разному) Ключ изменен (по-разному) Конфликт (по умолчанию: наша сторона)
Ключ существует Ключ существует Ключ удален Принять удаление
Ключ существует Ключ удален Ключ существует Принять удаление
Ключ отсутствует Ключ добавлен Ключ отсутствует Принять добавление
Ключ отсутствует Ключ отсутствует Ключ добавлен Принять добавление

Вложенные объекты сравниваются как непрозрачные строки JSON, без рекурсивного обхода. Если вложенный объект отличается, сравнивается все значение этого ключа, а не его подчиненные ключи.

Требования

Все три значения должны быть допустимыми объектами или массивами JSON. Если какое-либо значение не является допустимым JSON или представляет собой скаляр JSON (строку, число, логическое значение), стратегия возвращает конфликт.

OpaqueCellMerger

Стратегия слияния по умолчанию, если стратегия слияния на уровне ячеек не настроена. Она рассматривает значение столбца как непрозрачный блок данных и всегда сообщает о конфликте, если значения различаются.

PHP
use Merql\CellMerge\OpaqueCellMerger;

$merger = new OpaqueCellMerger();
$result = $merger->merge($base, $ours, $theirs);
// $result->clean is always false when $ours !== $theirs

CellMergeResult

Все стратегии слияния ячеек возвращают CellMergeResult:

PHP
$result = $merger->merge($base, $ours, $theirs);

$result->clean;     // bool: true if merge resolved cleanly
$result->value;     // mixed: the merged value (or ours on conflict)
$result->conflicts; // int: number of conflicts within the cell

Методы фабрики:

PHP
CellMergeResult::resolved($value);           // clean merge
CellMergeResult::conflict($oursValue, $n);   // n conflicts, defaults to ours

Пользовательские стратегии слияния ячеек

Реализуйте интерфейс CellMerger, чтобы добавить собственную стратегию слияния:

PHP
use Merql\CellMerge\CellMerger;
use Merql\CellMerge\CellMergeResult;

class YamlCellMerger implements CellMerger
{
    public function merge(mixed $base, mixed $ours, mixed $theirs): CellMergeResult
    {
        // Parse YAML, merge keys, return result.
        $merged = $this->mergeYaml($base, $ours, $theirs);

        if ($merged['clean']) {
            return CellMergeResult::resolved($merged['value']);
        }

        return CellMergeResult::conflict($ours);
    }
}

$config = (new CellMergeConfig())
    ->forColumn('config_data', new YamlCellMerger());