Сливане на ниво клетка
Сливането на ниво колона открива кога двете страни са променили една и съща колона. Сливането на ниво клетка отива по-надълбоко и обединява съдържанието в тази колона. Двама души могат да редактират различни редове в поле TEXT или различни ключове в обект JSON, без да възникне конфликт.
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 го преобразува в план за сливане:
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() предоставя подходящи стойности по подразбиране:
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());
Приоритет при избора на обединител:
- Пълно име на колона (
table.column). - Непълно име на колона (
column). - Съвпадение по шаблон за тип на колона (без разлика между малки и главни букви
str_contains). - По подразбиране:
OpaqueCellMerger(винаги отчита конфликт при различни стойности).
TextCellMerger
Слива многоредов текст чрез алгоритъма за разлики Myers, същия алгоритъм, който git използва за съдържанието на файлове. Всеки ред се третира като единица и промените в различни редове се сливат без конфликт.
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, като сравнява всеки ключ от най-горното ниво поотделно. Всеки ключ се слива според стандартните правила за тристранно сливане.
use Merql\CellMerge\JsonCellMerger;
$merger = new JsonCellMerger();
Пример: чисто сливане на 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}
Променени са различни ключове. Полученият резултат е:
{"name": "Bob", "role": "admin", "active": true}
Пример: конфликт в JSON
// Base
{"color": "red"}
// Ours
{"color": "blue"}
// Theirs
{"color": "green"}
И двете страни са променили ключа color с различни стойности. Това е конфликт. По подразбиране обединителят приема нашата стойност и отчита един конфликт.
Правила на ниво ключ
| База | Нашето | Тяхното | Резултат |
|---|---|---|---|
| Ключът не е променен | Ключът не е променен | Приема се базата | |
| Ключът не е променен | Ключът е променен | Приема се тяхната стойност | |
| Ключът е променен | Ключът не е променен | Приема се нашата стойност | |
| Ключът е променен (еднакво) | Ключът е променен (еднакво) | Приема се (съвпадат) | |
| Ключът е променен (различно) | Ключът е променен (различно) | Конфликт (по подразбиране: нашата стойност) | |
| Ключът съществува | Ключът съществува | Ключът е премахнат | Приема се премахването |
| Ключът съществува | Ключът е премахнат | Ключът съществува | Приема се премахването |
| Ключът липсва | Ключът е добавен | Ключът липсва | Приема се добавянето |
| Ключът липсва | Ключът липсва | Ключът е добавен | Приема се добавянето |
Вложените обекти се сравняват като непрозрачни низове JSON, а не рекурсивно. Ако вложен обект се различава, се сравнява цялата стойност на съответния ключ, а не подчинените му ключове.
Изисквания
И трите стойности трябва да са валидни обекти или масиви JSON. Ако някоя стойност не е валидна JSON или е скаларна стойност JSON (низ, число, булева стойност), обединителят преминава към конфликт.
OpaqueCellMerger
Обединителят по подразбиране, когато не е конфигурирана стратегия за сливане на ниво клетка. Той третира стойността на колоната като непрозрачен блок и винаги отчита конфликт, когато двете страни се различават.
use Merql\CellMerge\OpaqueCellMerger;
$merger = new OpaqueCellMerger();
$result = $merger->merge($base, $ours, $theirs);
// $result->clean is always false when $ours !== $theirs
CellMergeResult
Всички обединители на ниво клетка връщат 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
Фабрични методи:
CellMergeResult::resolved($value); // clean merge
CellMergeResult::conflict($oursValue, $n); // n conflicts, defaults to ours
Персонализирани обединители на ниво клетка
Реализирайте интерфейса CellMerger, за да добавите собствена стратегия за сливане:
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());