Към съдържанието

Сливане на ниво клетка

Сливането на ниво колона открива кога двете страни са променили една и съща колона. Сливането на ниво клетка отива по-надълбоко и обединява съдържанието в тази колона. Двама души могат да редактират различни редове в поле 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

Слива многоредов текст чрез алгоритъма за разлики Myers, същия алгоритъм, който 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, който изчислява разликите Myers между базата и всяка страна, след което комбинира двата скрипта за редакции. Когато редакциите се застъпват в един и същ участък от редове, се отчита конфликт.

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());