
# Fusion à trois voies

Une fusion à trois voies compare deux ensembles de modifications par rapport à un ancêtre commun et les combine en un seul résultat. merql applique cet algorithme aux lignes de base de données plutôt qu’aux lignes de fichiers.

```php
use Merql\Merge\ThreeWayMerge;

$merge = new ThreeWayMerge();
$result = $merge->merge($base, $ours, $theirs);

echo $result->operationCount(); // resolved operations
echo $result->conflictCount();  // unresolved conflicts
```

## Les trois états

Chaque fusion nécessite exactement trois instantanés :

- **Base** -- l’état de la base de données au moment où les deux branches ont divergé.
- **Notre côté** -- une branche après ses modifications, par exemple une copie de test qui a été modifiée.
- **Leur côté** -- l’autre branche après ses modifications, par exemple la base de données en production qui a continué à fonctionner.

Notre côté et leur côté doivent chacun être capturés depuis la base de données qui contient la branche correspondante. Les règles de fusion traitent les deux côtés de la même façon ; les noms déterminent le `source` de chaque opération, la désignation des types de conflit (`update_delete` signifie que notre côté a modifié la ligne et que leur côté l’a supprimée), ainsi que le côté privilégié par `ConflictPolicy::OursWins` et `TheirsWins`.

```
         Base (snapshot at fork time)
        /                              \
   Ours (our changes)                   Theirs (their changes)
        \                              /
         --------- MERGE ----------
                     |
              Merged result
              + conflicts (if any)
```

Sans base commune, impossible de distinguer « ajouté » de « inchangé ». Une comparaison à deux voies constate que le côté A contient une valeur et que le côté B ne la contient pas, mais ne peut pas déterminer si A l’a ajoutée ou si B l’a supprimée. La base permet de trancher.

## Calcul des ensembles de modifications

Le `Differ` compare chaque instantané à la base pour produire deux ensembles de modifications. Chaque ensemble contient trois types d’opérations :

| Base | Actuel | Opération |
|---|---|---|
| La ligne existe | La ligne existe, même empreinte | Aucune modification |
| La ligne existe | La ligne existe, empreinte différente | **UPDATE** (différence par colonne) |
| La ligne existe | Ligne absente | **DELETE** |
| Ligne absente | La ligne existe | **INSERT** |

Les empreintes (SHA-256 hachages du contenu normalisé des lignes) offrent un raccourci. Si deux empreintes correspondent, la ligne est inchangée et aucune comparaison au niveau des colonnes n’est nécessaire.

```php
use Merql\Diff\Differ;

$differ = new Differ();
$oursChangeset = $differ->diff($base, $ours);
$theirsChangeset = $differ->diff($base, $theirs);
```

## Règles de fusion

Les deux ensembles de modifications sont ensuite fusionnés opération par opération. Ce tableau définit le résultat pour chaque combinaison :

| Notre côté | Leur côté | Résultat |
|---|---|---|
| Aucune modification | Aucune modification | Conserver la base |
| Aucune modification | Modifié | Accepter leur côté |
| Modifié | Aucune modification | Accepter notre côté |
| Modifié (même valeur) | Modifié (même valeur) | Accepter (accord des deux côtés) |
| Modifié (valeur différente) | Modifié (valeur différente) | **Conflit** |
| Aucune modification | Supprimé | Accepter la suppression |
| Supprimé | Aucune modification | Accepter la suppression |
| Supprimé | Supprimé | Accepter la suppression (accord des deux côtés) |
| Modifié | Supprimé | **Conflit** |
| Supprimé | Modifié | **Conflit** |
| Inséré (notre côté uniquement) | -- | Accepter l’insertion |
| -- | Inséré (leur côté uniquement) | Accepter l’insertion |
| Inséré (même clé primaire) | Inséré (même clé primaire) | **Conflit** |

Lorsque les deux côtés modifient la même ligne, merql ne déclare pas immédiatement un conflit. Il passe à une fusion au niveau des colonnes et compare chaque colonne séparément. Consultez [Fusion au niveau des colonnes](https://merql.inline0.dev/fr/docs/merge/column-level) pour en savoir plus.

## Utilisation de la façade

La façade `Merql` fonctionne avec des instantanés nommés enregistrés sur le disque. Appelez `Merql::init()` avec la base de données qui contient chaque état avant de créer son instantané :

```php
use Merql\Connection;
use Merql\Merql;

$live    = Connection::sqlite('/data/live.sqlite');
$sandbox = Connection::sqlite('/data/sandbox.sqlite');

Merql::init($live);
Merql::snapshot('base');      // before the sandbox is copied from live

// ... copy live.sqlite to sandbox.sqlite, then both databases change ...

Merql::init($sandbox);
Merql::snapshot('ours');
Merql::init($live);
Merql::snapshot('theirs');

$result = Merql::merge('base', 'ours', 'theirs');
```

Ne créez pas les trois instantanés depuis une seule base de données, l’un après l’autre. Leur côté contiendrait alors aussi les modifications de notre côté : chaque ligne insérée par notre côté semblerait avoir été insérée des deux côtés et serait signalée comme un conflit `insert_insert`.

`Merql::merge()`, `Merql::patch()` et `Merql::plan()` créent un `ThreeWayMerge` sans `CellMergeConfig` ; la fusion s’effectue donc uniquement au niveau des colonnes. Pour une fusion au niveau des cellules, chargez les instantanés et utilisez directement la classe, comme ci-dessous.

## Utilisation directe des classes

Pour plus de contrôle, instanciez vous-même les composants. `Snapshotter::capture()` n’enregistre rien ; gardez donc les instantanés en mémoire ou enregistrez-les avec `SnapshotStore::save()` :

```php
use Merql\CellMerge\CellMergeConfig;
use Merql\Merge\ThreeWayMerge;
use Merql\Snapshot\Snapshotter;
use Merql\Snapshot\SnapshotStore;

$base   = (new Snapshotter($live))->capture('base');
// ... copy live to the sandbox, then both databases change ...
$ours   = (new Snapshotter($sandbox))->capture('ours');
$theirs = (new Snapshotter($live))->capture('theirs');

$merge = new ThreeWayMerge();
$result = $merge->merge($base, $ours, $theirs);

// Snapshots saved earlier, merged with cell-level merge for TEXT and JSON columns.
$merge = new ThreeWayMerge(CellMergeConfig::auto());
$result = $merge->merge(
    SnapshotStore::load('base'),
    SnapshotStore::load('ours'),
    SnapshotStore::load('theirs'),
);
```

## Raccourci pour les correctifs

Lorsque les modifications proviennent d’un seul côté (sans modifications concurrentes), utilisez `patch()`. Il s’agit d’une fusion à deux voies où notre côté est identique à la base ; les conflits sont donc impossibles :

```php
$result = $merge->patch($base, $changes);
// Always clean: ours is identical to base.
```

Via la façade :

```php
$result = Merql::patch('base', 'changes');
```

`patch()` est équivalent à `merge($base, $base, $changes)`. Comme notre côté n’a jamais divergé de la base, toutes les modifications de leur côté sont acceptées sans conflit.

## Détection des incompatibilités de schéma

Si des colonnes ont été ajoutées à une table ou supprimées de celle-ci entre les instantanés, la fusion se poursuit, mais signale les incompatibilités. Les types de colonnes modifiés ne sont pas comparés, et une colonne renommée apparaît comme une colonne supprimée et une colonne ajoutée ; consultez [Validation du schéma](https://merql.inline0.dev/fr/docs/advanced/schema).

```php
$result = $merge->merge($base, $ours, $theirs);

if ($result->hasSchemaMismatches()) {
    foreach ($result->schemaMismatches() as $mismatch) {
        echo $mismatch->getMessage() . "\n";
    }
}
```

merql ne migre pas automatiquement les schémas. Les modifications de schéma relèvent de la responsabilité de l’appelant. La fusion les signale afin que vous puissiez décider de la marche à suivre.

## L’objet MergeResult

L’objet résultat contient tout ce dont vous avez besoin :

```php
$result->isClean();            // true if no conflicts
$result->operations();         // list of MergeOperation objects
$result->conflicts();          // list of Conflict objects
$result->operationCount();     // number of resolved operations
$result->conflictCount();      // number of unresolved conflicts
$result->hasSchemaMismatches(); // true if schemas differ
$result->baseSnapshot();       // the base snapshot used for the merge
```

Chaque `MergeOperation` possède un type (`insert`, `update` ou `delete`), un nom de table, une clé de ligne, les valeurs des colonnes et une source (`ours`, `theirs` ou `merged`). `merged` marque une ligne modifiée des deux côtés (combinée colonne par colonne) ou supprimée des deux côtés.

## Où appliquer un résultat de fusion

Un résultat de fusion décrit comment transformer la base en état fusionné. Ses opérations regroupent les modifications des deux côtés : une insertion effectuée par notre côté et une insertion effectuée par leur côté apparaissent toutes deux comme des insertions.

- `Merql::apply()` (ou `Applier`) exécute chaque opération sur la base de données connectée. L’application fonctionne sans problème sur une base qui contient encore la base d’origine, par exemple une copie fraîche de cet état. Sur une base qui contient déjà les modifications d’un côté, l’insertion d’une ligne déjà présente échoue et la transaction est annulée.
- Pour intégrer les modifications de notre côté dans une base qui contient déjà celles de leur côté, par exemple pour promouvoir des modifications de test en production, appliquez uniquement les opérations manquantes à cette base : celles dont la source est `ours`, ainsi que les mises à jour `merged`. Utilisez un [plan de fusion](https://merql.inline0.dev/fr/docs/merge/plans) pour les sélectionner et [l’application protégée](https://merql.inline0.dev/fr/docs/apply/guarded) pour les écrire avec des préconditions sur les lignes en temps réel.
