Treci la conținut

Îmbinare la nivel de celulă

Îmbinarea la nivel de coloană detectează când ambele părți au modificat aceeași coloană. Îmbinarea la nivel de celulă merge mai în profunzime, îmbinând conținutul din acea coloană. Două persoane care editează linii diferite dintr-un câmp TEXT sau chei diferite ale unui obiect JSON pot îmbina modificările fără conflicte.

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

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

Activarea îmbinării la nivel de celulă

Îmbinarea la nivel de celulă este opțională. Se execută doar într-un ThreeWayMerge construit cu un CellMergeConfig. În lipsa acestuia, fiecare coloană pe care ambele părți au modificat-o la valori diferite reprezintă un conflict update_update.

Fațada Merql nu primește o configurație: Merql::merge(), Merql::patch() și Merql::plan() construiesc new ThreeWayMerge() fără aceasta, iar comanda merge a CLI trece prin Merql::merge(). Acestea îmbină întotdeauna la nivel de coloană (#7).

Pentru a îmbina instantanee salvate de fațadă sau de CLI cu îmbinare la nivel de celulă, încărcați-le și apelați chiar voi ThreeWayMerge. Rezultatul este un MergeResult obișnuit, astfel că fațada îl poate aplica în continuare, iar MergePlanBuilder îl transformă într-un plan de îmbinare:

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

Îmbinările la nivel de celulă se execută doar pentru coloanele modificate de ambele părți. Dacă o coloană a fost modificată de o singură parte, valoarea acesteia este preluată ca atare.

CellMergeConfig

CellMergeConfig asociază coloanele sau tipurile de coloane cu implementările de îmbinare a celulelor. Fabrica auto() oferă valori implicite potrivite:

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

Prioritatea rezolvării îmbinărilor:

  1. Numele calificat al coloanei (table.column).
  2. Numele necalificat al coloanei (column).
  3. Potrivirea după tipul coloanei (str_contains fără distincție între majuscule și minuscule).
  4. Valoare implicită: OpaqueCellMerger (raportează întotdeauna un conflict când valorile diferă).

TextCellMerger

Îmbină texte pe mai multe linii folosind diferențierea Myers, același algoritm pe care git îl folosește pentru conținutul fișierelor. Fiecare linie este tratată ca unitate, iar modificările liniilor diferite se îmbină fără conflicte.

PHP
use Merql\CellMerge\TextCellMerger;

$merger = new TextCellMerger();

Exemplu: îmbinare de text fără conflicte

Cod
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

Au fost editate linii diferite. Îmbinarea produce "LINE ONE\nline two\nLINE THREE" fără conflicte.

Exemplu: conflict de text

Cod
Base:    "line one\nline two"
Ours:    "changed by us\nline two"     // changed line 1
Theirs:  "changed by them\nline two"   // also changed line 1

Ambele părți au modificat aceeași linie la valori diferite. Îmbinarea returnează un rezultat cu conflict. Conținutul îmbinat include marcaje de conflict similare cu cele din git.

Cum funcționează

TextCellMerger deleagă către ThreeWayMerge::merge() al pitmaster, care calculează o diferențiere Myers între baza comună și fiecare parte, apoi combină cele două secvențe de editări. Când editările se suprapun pe aceeași regiune de linii, raportează un conflict.

JsonCellMerger

Îmbină obiectele JSON comparând independent cheile de nivel superior. Fiecare cheie este îmbinată folosind regulile standard în trei direcții.

PHP
use Merql\CellMerge\JsonCellMerger;

$merger = new JsonCellMerger();

Exemplu: îmbinare JSON fără conflicte

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}

Au fost modificate chei diferite. Rezultatul îmbinat este:

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

Exemplu: conflict JSON

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

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

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

Ambele părți au modificat cheia color la valori diferite. Acesta este un conflict. În mod implicit, îmbinarea păstrează valoarea noastră și raportează un conflict.

Reguli la nivel de cheie

Bază Ale noastre Ale lor Rezultat
Cheie nemodificată Cheie nemodificată Acceptă baza
Cheie nemodificată Cheie modificată Acceptă valoarea lor
Cheie modificată Cheie nemodificată Acceptă valoarea noastră
Cheie modificată (identic) Cheie modificată (identic) Acceptă (acord)
Cheie modificată (diferit) Cheie modificată (diferit) Conflict (implicit: ale noastre)
Cheie existentă Cheie existentă Cheie eliminată Acceptă eliminarea
Cheie existentă Cheie eliminată Cheie existentă Acceptă eliminarea
Cheie absentă Cheie adăugată Cheie absentă Acceptă adăugarea
Cheie absentă Cheie absentă Cheie adăugată Acceptă adăugarea

Obiectele imbricate sunt comparate ca șiruri JSON opace, nu recursiv. Dacă un obiect imbricat diferă, se compară întreaga valoare a acelei chei, nu subcheile sale.

Cerințe

Toate cele trei valori trebuie să fie obiecte sau tablouri JSON valide. Dacă vreo valoare nu este JSON validă sau este un scalar JSON (șir, număr, valoare booleană), îmbinarea revine la un conflict.

OpaqueCellMerger

Îmbinarea implicită, folosită când nu este configurată nicio strategie la nivel de celulă. Tratează valoarea coloanei ca pe un bloc opac și raportează întotdeauna un conflict când valorile celor două părți diferă.

PHP
use Merql\CellMerge\OpaqueCellMerger;

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

CellMergeResult

Toate îmbinările la nivel de celulă returnează 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

Metode de fabricare:

PHP
CellMergeResult::resolved($value);           // clean merge
CellMergeResult::conflict($oursValue, $n);   // n conflicts, defaults to ours

Îmbinări personalizate de celule

Implementați interfața CellMerger pentru a adăuga propria strategie de îmbinare:

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