Merge a livello di cella
Il merge a livello di colonna rileva quando entrambi i lati hanno modificato la stessa colonna. Quello a livello di cella scende più in profondità e unisce i contenuti all'interno della colonna. Due persone che modificano righe diverse di un campo TEXT, o chiavi diverse di un oggetto JSON, possono unire le modifiche senza conflitti.
use Merql\CellMerge\CellMergeConfig;
use Merql\Merge\ThreeWayMerge;
$config = CellMergeConfig::auto();
$merge = new ThreeWayMerge($config);
$result = $merge->merge($base, $ours, $theirs);
Abilitazione del merge a livello di cella
Il merge a livello di cella è facoltativo. Viene eseguito solo con un ThreeWayMerge costruito con un CellMergeConfig. Senza quest'ultimo, ogni colonna modificata da entrambi i lati con valori diversi genera un conflitto update_update.
La facciata Merql non accetta una configurazione: Merql::merge(), Merql::patch() e Merql::plan() costruiscono new ThreeWayMerge() senza configurazione, e il comando merge di CLI passa per Merql::merge(). Eseguono sempre il merge a livello di colonna (#7).
Per eseguire un merge a livello di cella sugli snapshot salvati dalla facciata o da CLI, caricateli e chiamate direttamente ThreeWayMerge. Il risultato è un normale MergeResult, quindi la facciata può comunque applicarlo e MergePlanBuilder lo trasforma in un piano di merge:
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);
I merger di cella vengono eseguiti solo per le colonne modificate da entrambi i lati. Se una colonna è stata modificata da un solo lato, il suo valore viene mantenuto così com'è.
CellMergeConfig
CellMergeConfig associa colonne o tipi di colonna alle implementazioni dei merger di cella. La factory auto() fornisce valori predefiniti ragionevoli:
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());
Priorità di risoluzione dei merger:
- Nome di colonna qualificato (
table.column). - Nome di colonna non qualificato (
column). - Corrispondenza del tipo di colonna (
str_containssenza distinzione tra maiuscole e minuscole). - Predefinito:
OpaqueCellMerger(se i valori differiscono, segnala sempre un conflitto).
TextCellMerger
Unisce testi su più righe usando il diff di Myers, lo stesso algoritmo usato da git per i contenuti dei file. Ogni riga viene trattata come un'unità e le modifiche a righe diverse vengono unite senza conflitti.
use Merql\CellMerge\TextCellMerger;
$merger = new TextCellMerger();
Esempio: merge di testo senza conflitti
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
Sono state modificate righe diverse. Il merge produce "LINE ONE\nline two\nLINE THREE" senza conflitti.
Esempio: conflitto nel testo
Base: "line one\nline two"
Ours: "changed by us\nline two" // changed line 1
Theirs: "changed by them\nline two" // also changed line 1
Entrambi i lati hanno modificato la stessa riga assegnandole valori diversi. Il merger restituisce un risultato con conflitto. Il contenuto unito include marcatori di conflitto simili a quelli di git.
Come funziona
TextCellMerger delega a ThreeWayMerge::merge() di pitmaster, che calcola un diff di Myers tra la base e ciascun lato, poi combina i due script di modifica. Se le modifiche si sovrappongono nella stessa regione di righe, segnala un conflitto.
JsonCellMerger
Unisce gli oggetti JSON confrontando separatamente le chiavi di primo livello. Ogni chiave viene unita con le normali regole a tre vie.
use Merql\CellMerge\JsonCellMerger;
$merger = new JsonCellMerger();
Esempio: merge JSON senza conflitti
// 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}
Sono state modificate chiavi diverse. Il risultato del merge è:
{"name": "Bob", "role": "admin", "active": true}
Esempio: conflitto JSON
// Base
{"color": "red"}
// Ours
{"color": "blue"}
// Theirs
{"color": "green"}
Entrambi i lati hanno modificato la chiave color assegnandole valori diversi. Si tratta di un conflitto. Per impostazione predefinita, il merger mantiene ours e segnala un conflitto.
Regole a livello di chiave
| Base | Ours | Theirs | Risultato |
|---|---|---|---|
| Chiave invariata | Chiave invariata | Accetta la base | |
| Chiave invariata | Chiave modificata | Accetta theirs | |
| Chiave modificata | Chiave invariata | Accetta ours | |
| Chiave modificata (uguale) | Chiave modificata (uguale) | Accetta (concordano) | |
| Chiave modificata (diversa) | Chiave modificata (diversa) | Conflitto (predefinito: ours) | |
| Chiave presente | Chiave presente | Chiave rimossa | Accetta la rimozione |
| Chiave presente | Chiave rimossa | Chiave presente | Accetta la rimozione |
| Chiave assente | Chiave aggiunta | Chiave assente | Accetta l'aggiunta |
| Chiave assente | Chiave assente | Chiave aggiunta | Accetta l'aggiunta |
Gli oggetti annidati vengono confrontati come stringhe JSON opache, non in modo ricorsivo. Se un oggetto annidato è diverso, viene confrontato l'intero valore della chiave, non le sue sottochiavi.
Requisiti
Tutti e tre i valori devono essere oggetti o array JSON validi. Se un valore non è JSON valido o è uno scalare JSON (stringa, numero, booleano), il merger ripiega su un conflitto.
OpaqueCellMerger
Il merger predefinito quando non è configurata alcuna strategia a livello di cella. Tratta il valore della colonna come un blocco opaco e segnala sempre un conflitto quando entrambi i lati lo modificano.
use Merql\CellMerge\OpaqueCellMerger;
$merger = new OpaqueCellMerger();
$result = $merger->merge($base, $ours, $theirs);
// $result->clean is always false when $ours !== $theirs
CellMergeResult
Tutti i merger di cella restituiscono 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
Metodi factory:
CellMergeResult::resolved($value); // clean merge
CellMergeResult::conflict($oursValue, $n); // n conflicts, defaults to ours
Merger di cella personalizzati
Implementate l'interfaccia CellMerger per aggiungere una strategia di merge personalizzata:
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());