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.
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:
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:
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:
- Kwalifikowana nazwa kolumny (
table.column). - Niekwalifikowana nazwa kolumny (
column). - Dopasowanie wzorca typu kolumny (bez rozróżniania wielkości liter
str_contains). - 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.
use Merql\CellMerge\TextCellMerger;
$merger = new TextCellMerger();
Przykład: bezkonfliktowe scalanie tekstu
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
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.
use Merql\CellMerge\JsonCellMerger;
$merger = new JsonCellMerger();
Przykład: bezkonfliktowe scalanie 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:
{"name": "Bob", "role": "admin", "active": true}
Przykład: konflikt 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.
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:
$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:
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:
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());