Saltar al contenido

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.

PHP
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:

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

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:

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

Prioridad de resolución de combinadores:

  1. Nombre de columna cualificado (table.column).
  2. Nombre de columna sin cualificar (column).
  3. Coincidencia de patrón de tipo de columna (str_contains sin distinguir mayúsculas de minúsculas).
  4. 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.

PHP
use Merql\CellMerge\TextCellMerger;

$merger = new TextCellMerger();

Ejemplo: combinación de texto sin conflictos

Código
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

Código
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.

PHP
use Merql\CellMerge\JsonCellMerger;

$merger = new JsonCellMerger();

Ejemplo: combinación JSON sin conflictos

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}

Se cambiaron claves distintas. El resultado combinado es:

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

Ejemplo: conflicto JSON

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.

PHP
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:

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

Métodos de fábrica:

PHP
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:

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