Aller au contenu

Fusion au niveau des cellules

La fusion au niveau des colonnes détecte les modifications simultanées d'une même colonne. La fusion au niveau des cellules va plus loin et fusionne le contenu de cette colonne. Deux personnes modifiant des lignes différentes d'un champ TEXT, ou des clés différentes d'un objet JSON, peuvent fusionner leurs modifications sans conflit.

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

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

Activer la fusion au niveau des cellules

La fusion au niveau des cellules est facultative. Elle ne s'exécute que dans un ThreeWayMerge construit avec un CellMergeConfig. Sans cela, toute colonne modifiée par les deux côtés avec des valeurs différentes constitue un conflit update_update.

La façade Merql n'accepte pas de configuration : Merql::merge(), Merql::patch() et Merql::plan() construisent new ThreeWayMerge() sans configuration, et la commande merge de CLI passe par Merql::merge(). La fusion se fait toujours au niveau des colonnes (#7).

Pour fusionner des instantanés enregistrés par la façade ou par CLI avec la fusion au niveau des cellules, chargez-les et appelez vous-même ThreeWayMerge. Le résultat est un MergeResult ordinaire, que la façade peut toujours appliquer, et MergePlanBuilder le transforme en plan de fusion :

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

Les stratégies de fusion des cellules ne s'exécutent que pour les colonnes modifiées par les deux côtés. Si un seul côté a modifié une colonne, sa valeur est reprise telle quelle.

CellMergeConfig

CellMergeConfig associe des colonnes ou des types de colonnes à des implémentations de stratégies de fusion des cellules. La fabrique auto() fournit des valeurs par défaut adaptées :

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

Ordre de priorité des stratégies de fusion :

  1. Nom de colonne qualifié (table.column).
  2. Nom de colonne non qualifié (column).
  3. Correspondance de motif de type de colonne (str_contains sans distinction de casse).
  4. Valeur par défaut : OpaqueCellMerger (signale toujours un conflit lorsque les valeurs diffèrent).

TextCellMerger

Fusionne le texte sur plusieurs lignes à l'aide du diff de Myers, le même algorithme que git utilise pour le contenu des fichiers. Chaque ligne est traitée comme une unité et les modifications portant sur des lignes différentes fusionnent sans conflit.

PHP
use Merql\CellMerge\TextCellMerger;

$merger = new TextCellMerger();

Exemple : fusion de texte sans conflit

Code
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

Des lignes différentes ont été modifiées. La fusion produit "LINE ONE\nline two\nLINE THREE" sans conflit.

Exemple : conflit de texte

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

Les deux côtés ont modifié la même ligne avec des valeurs différentes. La stratégie de fusion renvoie un résultat en conflit. Le contenu fusionné inclut des marqueurs de conflit similaires à ceux de git.

Fonctionnement

TextCellMerger délègue à ThreeWayMerge::merge() de pitmaster, qui calcule un diff de Myers entre la base et chaque côté, puis combine les deux scripts de modification. Si des modifications se chevauchent sur une même zone de lignes, un conflit est signalé.

JsonCellMerger

Fusionne les objets JSON en comparant séparément les clés de premier niveau. Chaque clé est fusionnée selon les règles standard à trois voies.

PHP
use Merql\CellMerge\JsonCellMerger;

$merger = new JsonCellMerger();

Exemple : fusion JSON sans conflit

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}

Des clés différentes ont été modifiées. Voici le résultat de la fusion :

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

Exemple : conflit JSON

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

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

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

Les deux côtés ont modifié la clé color avec des valeurs différentes. Il s'agit d'un conflit. Par défaut, la stratégie de fusion conserve notre valeur et signale un conflit.

Règles au niveau des clés

Base Nous Eux Résultat
Clé inchangée Clé inchangée Accepter la base
Clé inchangée Clé modifiée Accepter leur valeur
Clé modifiée Clé inchangée Accepter notre valeur
Clé modifiée (identique) Clé modifiée (identique) Accepter (accord)
Clé modifiée (différente) Clé modifiée (différente) Conflit (par défaut : notre valeur)
Clé présente Clé présente Clé supprimée Accepter la suppression
Clé présente Clé supprimée Clé présente Accepter la suppression
Clé absente Clé ajoutée Clé absente Accepter l'ajout
Clé absente Clé absente Clé ajoutée Accepter l'ajout

Les objets imbriqués sont comparés comme des chaînes JSON opaques, sans récursion. Si un objet imbriqué diffère, la valeur entière de sa clé est comparée, et non ses sous-clés.

Prérequis

Les trois valeurs doivent être des objets ou tableaux JSON valides. Si une valeur n'est pas un JSON valide ou est un scalaire JSON (chaîne, nombre, booléen), la stratégie de fusion signale un conflit.

OpaqueCellMerger

La stratégie de fusion par défaut lorsqu'aucune stratégie au niveau des cellules n'est configurée. Elle traite la valeur de la colonne comme un bloc opaque et signale toujours un conflit lorsque les deux côtés diffèrent.

PHP
use Merql\CellMerge\OpaqueCellMerger;

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

CellMergeResult

Toutes les stratégies de fusion des cellules renvoient 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éthodes de fabrique :

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

Stratégies de fusion des cellules personnalisées

Implémentez l'interface CellMerger pour ajouter votre propre stratégie de fusion :

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