Pular para o conteúdo

Mesclagem no nível da célula

A mesclagem no nível das colunas detecta quando os dois lados alteraram a mesma coluna. A mesclagem no nível da célula vai além e mescla o conteúdo dentro dessa coluna. Duas pessoas podem editar linhas diferentes de um campo TEXT ou chaves diferentes de um objeto JSON sem gerar conflitos.

PHP
use Merql\CellMerge\CellMergeConfig;
use Merql\Merge\ThreeWayMerge;

$config = CellMergeConfig::auto();
$merge = new ThreeWayMerge($config);
$result = $merge->merge($base, $ours, $theirs);

Ativação da mesclagem no nível da célula

A mesclagem no nível da célula é opcional. Ela só é executada em um ThreeWayMerge construído com um CellMergeConfig. Sem esse elemento, toda coluna que os dois lados alterarem para valores diferentes será um conflito update_update.

A fachada Merql não recebe uma configuração: Merql::merge(), Merql::patch() e Merql::plan() constroem new ThreeWayMerge() sem uma, e o comando merge de CLI passa por Merql::merge(). Eles sempre fazem a mesclagem no nível das colunas (#7).

Para mesclar snapshots salvos pela fachada ou pelo CLI com mesclagem no nível da célula, carregue-os e chame ThreeWayMerge diretamente. O resultado é um MergeResult comum, que a fachada ainda pode aplicar, e MergePlanBuilder o transforma em um plano de mesclagem:

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

Os mescladores de células só são executados para colunas alteradas pelos dois lados. Se apenas um lado alterou uma coluna, seu valor é mantido como está.

CellMergeConfig

CellMergeConfig mapeia colunas ou tipos de coluna para implementações de mescladores de células. A fábrica auto() fornece valores padrão sensatos:

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

Prioridade de resolução do mesclador:

  1. Nome qualificado da coluna (table.column).
  2. Nome não qualificado da coluna (column).
  3. Correspondência de padrão do tipo de coluna (str_contains sem distinção entre maiúsculas e minúsculas).
  4. Padrão: OpaqueCellMerger (sempre informa um conflito quando os valores diferem).

TextCellMerger

Mescla textos com várias linhas usando o diff de Myers, o mesmo algoritmo usado por git para o conteúdo de arquivos. Cada linha é tratada como uma unidade, e as alterações em linhas diferentes são mescladas sem conflitos.

PHP
use Merql\CellMerge\TextCellMerger;

$merger = new TextCellMerger();

Exemplo: mesclagem de texto sem conflitos

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

Foram editadas linhas diferentes. A mesclagem gera "LINE ONE\nline two\nLINE THREE" sem conflitos.

Exemplo: conflito 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

Os dois lados alteraram a mesma linha para valores diferentes. O mesclador retorna um resultado de conflito. O conteúdo mesclado inclui marcadores de conflito semelhantes aos de git.

Como funciona

TextCellMerger delega para o ThreeWayMerge::merge() do pitmaster, que calcula um diff de Myers entre a base e cada lado e, em seguida, combina os dois scripts de edição. Quando as edições se sobrepõem na mesma região de linhas, ele informa um conflito.

JsonCellMerger

Mescla objetos JSON comparando cada chave de nível superior de forma independente. Cada chave é mesclada segundo as regras padrão de três vias.

PHP
use Merql\CellMerge\JsonCellMerger;

$merger = new JsonCellMerger();

Exemplo: mesclagem de JSON sem conflitos

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}

Foram alteradas chaves diferentes. O resultado mesclado é:

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

Exemplo: conflito de JSON

JSON
// Base
{"color": "red"}

// Ours
{"color": "blue"}

// Theirs
{"color": "green"}

Os dois lados alteraram a chave color para valores diferentes. Isso é um conflito. Por padrão, o mesclador escolhe nosso e informa um conflito.

Regras no nível da chave

Base Nosso Deles Resultado
Chave inalterada Chave inalterada Aceitar base
Chave inalterada Chave alterada Aceitar deles
Chave alterada Chave inalterada Aceitar nosso
Chave alterada (igual) Chave alterada (igual) Aceitar (acordo)
Chave alterada (diferente) Chave alterada (diferente) Conflito (padrão: nosso)
Chave existente Chave existente Chave removida Aceitar remoção
Chave existente Chave removida Chave existente Aceitar remoção
Chave ausente Chave adicionada Chave ausente Aceitar adição
Chave ausente Chave ausente Chave adicionada Aceitar adição

Objetos aninhados são comparados como strings JSON opacas, sem comparação recursiva. Se um objeto aninhado for diferente, compara-se o valor inteiro daquela chave, e não suas subchaves.

Requisitos

Os três valores precisam ser objetos ou arrays JSON válidos. Se algum valor não for JSON válido ou for um escalar JSON (string, número, booleano), o mesclador recorrerá a um conflito.

OpaqueCellMerger

O mesclador padrão quando nenhuma estratégia no nível da célula está configurada. Ele trata o valor da coluna como um bloco opaco e sempre informa um conflito quando os dois lados diferem.

PHP
use Merql\CellMerge\OpaqueCellMerger;

$merger = new OpaqueCellMerger();
$result = $merger->merge($base, $ours, $theirs);
// $result->clean is always false when $ours !== $theirs

CellMergeResult

Todos os mescladores de células retornam um 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

Mescladores de células personalizados

Implemente a interface CellMerger para adicionar sua própria estratégia de mesclagem:

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