Combinación a nivel de celda
La combinación a nivel de columna detecta cuándo ambas partes modificaron la misma columna. La combinación a nivel de celda profundiza y combina el contenido de esa columna. Dos personas que editan líneas distintas de un campo TEXT, o claves distintas de un objeto JSON, pueden combinar sus cambios sin conflictos.
use Merql\CellMerge\CellMergeConfig;
use Merql\Merge\ThreeWayMerge;
$config = CellMergeConfig::auto();
$merge = new ThreeWayMerge($config);
$result = $merge->merge($base, $ours, $theirs);
Cómo habilitar la combinación a nivel de celda
La combinación a nivel de celda es opcional. Solo se ejecuta en un ThreeWayMerge construido con un CellMergeConfig. Sin él, cada columna que ambas partes modificaron con valores distintos produce un conflicto update_update.
La fachada Merql no acepta una configuración: Merql::merge(), Merql::patch() y Merql::plan() construyen new ThreeWayMerge() sin ella, y el comando merge de CLI pasa por Merql::merge(). Siempre combinan a nivel de columna (#7).
Para combinar instantáneas guardadas por la fachada o CLI con combinación a nivel de celda, cárguelas y llame a ThreeWayMerge directamente. El resultado es un MergeResult normal, así que la fachada aún puede aplicarlo, y MergePlanBuilder lo convierte en un plan de combinación:
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);
Los combinadores de celdas solo se ejecutan en las columnas que ambas partes modificaron. Si solo una parte modificó una columna, se toma su valor tal cual.
CellMergeConfig
CellMergeConfig asigna implementaciones de combinadores de celdas a columnas o tipos de columna. La fábrica auto() proporciona valores predeterminados adecuados:
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());
Prioridad de resolución de combinadores:
- Nombre de columna cualificado (
table.column). - Nombre de columna sin cualificar (
column). - Coincidencia de patrón de tipo de columna (
str_containssin distinguir mayúsculas de minúsculas). - Predeterminado:
OpaqueCellMerger(siempre informa de un conflicto cuando los valores difieren).
TextCellMerger
Combina texto de varias líneas mediante el algoritmo Myers diff, el mismo que usa git para el contenido de archivos. Cada línea se trata como una unidad, y los cambios en líneas distintas se combinan sin conflictos.
use Merql\CellMerge\TextCellMerger;
$merger = new TextCellMerger();
Ejemplo: combinación de texto sin conflictos
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
Se editaron líneas distintas. La combinación produce "LINE ONE\nline two\nLINE THREE" sin conflictos.
Ejemplo: conflicto de texto
Base: "line one\nline two"
Ours: "changed by us\nline two" // changed line 1
Theirs: "changed by them\nline two" // also changed line 1
Ambas partes cambiaron la misma línea por valores distintos. El combinador devuelve un resultado con conflictos. El contenido combinado incluye marcadores de conflicto similares a los de git.
Cómo funciona
TextCellMerger delega en ThreeWayMerge::merge() de pitmaster, que calcula una diferencia Myers entre la base y cada parte y luego combina los dos conjuntos de ediciones. Si las ediciones se solapan en la misma región de líneas, informa de un conflicto.
JsonCellMerger
Combina objetos JSON comparando de forma independiente las claves de nivel superior. Cada clave se combina con las reglas estándar de tres vías.
use Merql\CellMerge\JsonCellMerger;
$merger = new JsonCellMerger();
Ejemplo: combinación JSON sin conflictos
// 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}
Se cambiaron claves distintas. El resultado combinado es:
{"name": "Bob", "role": "admin", "active": true}
Ejemplo: conflicto JSON
// Base
{"color": "red"}
// Ours
{"color": "blue"}
// Theirs
{"color": "green"}
Ambas partes cambiaron la clave color por valores distintos. Esto es un conflicto. De forma predeterminada, el combinador conserva el valor de nuestro lado e informa de un conflicto.
Reglas a nivel de clave
| Base | Nuestro lado | Su lado | Resultado |
|---|---|---|---|
| Clave sin cambios | Clave sin cambios | Aceptar la base | |
| Clave sin cambios | Clave modificada | Aceptar el valor de su lado | |
| Clave modificada | Clave sin cambios | Aceptar el valor de nuestro lado | |
| Clave modificada (igual) | Clave modificada (igual) | Aceptar (coinciden) | |
| Clave modificada (distinta) | Clave modificada (distinta) | Conflicto (predeterminado: nuestro lado) | |
| La clave existe | La clave existe | La clave se eliminó | Aceptar la eliminación |
| La clave existe | La clave se eliminó | La clave existe | Aceptar la eliminación |
| Falta la clave | Se añadió la clave | Falta la clave | Aceptar la adición |
| Falta la clave | Falta la clave | Se añadió la clave | Aceptar la adición |
Los objetos anidados se comparan como cadenas JSON opacas, sin comparación recursiva. Si un objeto anidado difiere, se compara el valor completo de esa clave, no sus subclaves.
Requisitos
Los tres valores deben ser objetos o matrices JSON válidos. Si algún valor no es JSON válido o es un escalar JSON (cadena, número o booleano), el combinador recurre a un conflicto.
OpaqueCellMerger
El combinador predeterminado cuando no se configura una estrategia a nivel de celda. Trata el valor de la columna como un bloque opaco e informa siempre de un conflicto cuando los valores de ambas partes difieren.
use Merql\CellMerge\OpaqueCellMerger;
$merger = new OpaqueCellMerger();
$result = $merger->merge($base, $ours, $theirs);
// $result->clean is always false when $ours !== $theirs
CellMergeResult
Todos los combinadores de celdas devuelven un 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
Métodos de fábrica:
CellMergeResult::resolved($value); // clean merge
CellMergeResult::conflict($oursValue, $n); // n conflicts, defaults to ours
Combinadores de celdas personalizados
Implemente la interfaz CellMerger para añadir su propia estrategia de combinación:
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());