Злиття на рівні комірок
Злиття на рівні стовпців виявляє, коли обидві сторони змінили той самий стовпець. Злиття на рівні комірок працює глибше, об’єднуючи вміст усередині стовпця. Двоє людей можуть без конфліктів редагувати різні рядки поля 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 між base і кожною зі сторін, а потім об’єднує два сценарії редагування. Якщо зміни накладаються в тій самій області рядків, він повідомляє про конфлікт.
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 на різні значення. Це конфлікт. За замовчуванням засіб злиття залишає значення ours і повідомляє про один конфлікт.
Правила для ключів
| Base | Ours | Theirs | Результат |
|---|---|---|---|
| Ключ без змін | Ключ без змін | Прийняти base | |
| Ключ без змін | Ключ змінено | Прийняти theirs | |
| Ключ змінено | Ключ без змін | Прийняти ours | |
| Ключ змінено (однаково) | Ключ змінено (однаково) | Прийняти (збігаються) | |
| Ключ змінено (по-різному) | Ключ змінено (по-різному) | Конфлікт (за замовчуванням: ours) | |
| Ключ існує | Ключ існує | Ключ видалено | Прийняти видалення |
| Ключ існує | Ключ видалено | Ключ існує | Прийняти видалення |
| Ключ відсутній | Ключ додано | Ключ відсутній | Прийняти додавання |
| Ключ відсутній | Ключ відсутній | Ключ додано | Прийняти додавання |
Вкладені об’єкти порівнюються як непрозорі рядки 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());