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.
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 :
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 :
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 :
- Nom de colonne qualifié (
table.column). - Nom de colonne non qualifié (
column). - Correspondance de motif de type de colonne (
str_containssans distinction de casse). - 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.
use Merql\CellMerge\TextCellMerger;
$merger = new TextCellMerger();
Exemple : fusion de texte sans conflit
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
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.
use Merql\CellMerge\JsonCellMerger;
$merger = new JsonCellMerger();
Exemple : fusion JSON sans conflit
// 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 :
{"name": "Bob", "role": "admin", "active": true}
Exemple : conflit 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.
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 :
$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 :
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 :
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());