Przejdź do treści

Scalanie na poziomie komórek

Scalanie na poziomie kolumn wykrywa, gdy obie strony zmieniły tę samą kolumnę. Scalanie na poziomie komórek sięga głębiej i scala zawartość tej kolumny. Dwie osoby edytujące różne wiersze pola TEXT lub różne klucze obiektu JSON mogą bezproblemowo scalić zmiany.

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

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

Włączanie scalania na poziomie komórek

Scalanie na poziomie komórek jest opcjonalne. Działa tylko w obiekcie ThreeWayMerge utworzonym z CellMergeConfig. Bez niego każda kolumna, którą obie strony zmieniły na różne wartości, powoduje konflikt update_update.

Fasada Merql nie przyjmuje konfiguracji: Merql::merge(), Merql::patch() i Merql::plan() tworzą new ThreeWayMerge() bez niej, a polecenie merge obiektu CLI przechodzi przez Merql::merge(). Zawsze scalają na poziomie kolumn (#7).

Aby scalić zrzuty zapisane przez fasadę lub CLI z użyciem scalania na poziomie komórek, wczytaj je i samodzielnie wywołaj ThreeWayMerge. Wynik jest zwykłym obiektem MergeResult, więc fasada nadal może go zastosować, a MergePlanBuilder zmienia go w plan scalania:

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

Scalanie komórek działa tylko dla kolumn zmienionych przez obie strony. Gdy kolumnę zmieniła tylko jedna strona, jej wartość jest przyjmowana bez zmian.

CellMergeConfig

CellMergeConfig mapuje kolumny lub typy kolumn na implementacje scalania komórek. Fabryka auto() udostępnia rozsądne wartości domyślne:

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

Priorytety wyboru mechanizmu scalania:

  1. Kwalifikowana nazwa kolumny (table.column).
  2. Niekwalifikowana nazwa kolumny (column).
  3. Dopasowanie wzorca typu kolumny (bez rozróżniania wielkości liter str_contains).
  4. Wartość domyślna: OpaqueCellMerger (zawsze zgłasza konflikt, gdy wartości się różnią).

TextCellMerger

Łączy wielowierszowy tekst za pomocą algorytmu różnicowego Myersa, tego samego, którego git używa do zawartości plików. Każdy wiersz jest traktowany jako osobna jednostka, a zmiany w różnych wierszach łączą się bez konfliktów.

PHP
use Merql\CellMerge\TextCellMerger;

$merger = new TextCellMerger();

Przykład: bezkonfliktowe scalanie tekstu

Kod
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

Edytowano różne wiersze. W wyniku scalania powstaje "LINE ONE\nline two\nLINE THREE" bez konfliktu.

Przykład: konflikt tekstowy

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

Obie strony zmieniły ten sam wiersz na różne wartości. Narzędzie scalające zwraca wynik z konfliktem. Scalone treści zawierają znaczniki konfliktu podobne do git.

Jak to działa

TextCellMerger korzysta z ThreeWayMerge::merge() w pitmaster, który oblicza różnice Myersa między bazą a każdą ze stron, a następnie łączy oba zestawy zmian. Jeśli zmiany nakładają się na ten sam obszar wiersza, zgłasza konflikt.

JsonCellMerger

Scala obiekty JSON, porównując niezależnie klucze najwyższego poziomu. Każdy klucz jest scalany zgodnie ze standardowymi regułami trójstronnymi.

PHP
use Merql\CellMerge\JsonCellMerger;

$merger = new JsonCellMerger();

Przykład: bezkonfliktowe scalanie 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}

Zmieniono różne klucze. Wynik scalania:

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

Przykład: konflikt JSON

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

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

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

Obie strony zmieniły klucz color na różne wartości. To konflikt. Domyślnie mechanizm scalania zachowuje naszą wartość i zgłasza jeden konflikt.

Reguły na poziomie kluczy

Baza Nasza strona Druga strona Wynik
Klucz bez zmian Klucz bez zmian Przyjmij bazę
Klucz bez zmian Klucz zmieniony Przyjmij wartość drugiej strony
Klucz zmieniony Klucz bez zmian Przyjmij naszą wartość
Klucz zmieniony (tak samo) Klucz zmieniony (tak samo) Przyjmij (zgodne)
Klucz zmieniony (inaczej) Klucz zmieniony (inaczej) Konflikt (domyślnie: nasza wartość)
Klucz istnieje Klucz istnieje Klucz usunięty Przyjmij usunięcie
Klucz istnieje Klucz usunięty Klucz istnieje Przyjmij usunięcie
Brak klucza Dodano klucz Brak klucza Przyjmij dodanie
Brak klucza Brak klucza Dodano klucz Przyjmij dodanie

Zagnieżdżone obiekty są porównywane jako nieprzezroczyste ciągi JSON, bez rekurencji. Jeśli zagnieżdżony obiekt się różni, porównywana jest cała wartość danego klucza, a nie jego podklucze.

Wymagania

Wszystkie trzy wartości muszą być prawidłowymi obiektami lub tablicami JSON. Jeśli którakolwiek wartość nie jest prawidłowym JSON lub jest skalarem JSON (ciągiem, liczbą, wartością logiczną), mechanizm scalania zgłasza konflikt.

OpaqueCellMerger

Domyślny mechanizm scalania, gdy nie skonfigurowano strategii na poziomie komórek. Traktuje wartość kolumny jako nieprzezroczysty blok i zawsze zgłasza konflikt, gdy obie strony mają różne wartości.

PHP
use Merql\CellMerge\OpaqueCellMerger;

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

CellMergeResult

Wszystkie mechanizmy scalania komórek zwracają wartość 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

Metody fabrykujące:

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

Własne mechanizmy scalania komórek

Zaimplementuj interfejs CellMerger, aby dodać własną strategię scalania:

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