Preskočiť na obsah

Trojstranné zlúčenie

Trojstranné zlúčenie porovnáva dve množiny zmien voči spoločnému predkovi a spája ich do jedného výsledku. merql používa tento algoritmus na riadky databázy namiesto riadkov súborov.

PHP
use Merql\Merge\ThreeWayMerge;

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

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

Tri stavy

Každé zlúčenie vyžaduje presne tri snímky:

  • Základná verzia -- stav databázy v okamihu, keď sa obe vetvy oddelili.
  • Naša strana -- jedna vetva po vykonaní zmien, napríklad upravená sandboxová kópia.
  • Ich strana -- druhá vetva po vykonaní zmien, napríklad živá databáza, ktorá ďalej fungovala.

Našu aj ich stranu treba zaznamenať z databázy, v ktorej sa daná vetva nachádza. Pravidlá zlúčenia zaobchádzajú s oboma stranami rovnako; názvy určujú source každej operácie, názvy typov konfliktov (update_delete znamená, že naša strana aktualizovala a ich strana odstránila riadok) a to, ktorej strane dávajú prednosť ConflictPolicy::OursWins a TheirsWins.

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

Bez spoločnej základnej verzie nemožno rozlíšiť „pridané“ od „nezmenené“. Dvojstranné porovnanie zistí, že strana A má hodnotu a strana B ju nemá, ale nedokáže určiť, či ju strana A pridala, alebo ju strana B odstránila. Na túto otázku odpovedá základná verzia.

Ako sa počítajú sady zmien

Differ porovná každú snímku so základnou verziou a vytvorí dve sady zmien. Každá sada obsahuje tri druhy operácií:

Základná verzia Aktuálny stav Operácia
Riadok existuje Riadok existuje, rovnaký odtlačok Žiadna zmena
Riadok existuje Riadok existuje, odlišný odtlačok UPDATE (rozdiely po jednotlivých stĺpcoch)
Riadok existuje Riadok chýba DELETE
Riadok chýba Riadok existuje INSERT

Odtlačky (SHA-256 hashy normalizovaného obsahu riadka) umožňujú rýchlu kontrolu. Ak sa dva odtlačky zhodujú, riadok sa nezmenil a porovnávanie na úrovni stĺpcov nie je potrebné.

PHP
use Merql\Diff\Differ;

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

Pravidlá zlúčenia

Dve sady zmien sa potom zlúčia operáciu po operácii. Táto tabuľka uvádza výsledok pre každú kombináciu:

Naša strana Ich strana Výsledok
Bez zmeny Bez zmeny Ponechať základnú verziu
Bez zmeny Aktualizované Prijať zmeny ich strany
Aktualizované Bez zmeny Prijať naše zmeny
Aktualizované (rovnaká hodnota) Aktualizované (rovnaká hodnota) Prijať (zhoda oboch strán)
Aktualizované (odlišná hodnota) Aktualizované (odlišná hodnota) Konflikt
Bez zmeny Odstránené Prijať odstránenie
Odstránené Bez zmeny Prijať odstránenie
Odstránené Odstránené Prijať odstránenie (zhoda oboch strán)
Aktualizované Odstránené Konflikt
Odstránené Aktualizované Konflikt
Pridané (iba na našej strane) -- Prijať pridanie
-- Pridané (iba na ich strane) Prijať pridanie
Pridané (rovnaký PK) Pridané (rovnaký PK) Konflikt

Keď obe strany aktualizujú ten istý riadok, merql okamžite neoznačí konflikt. Prejde na úroveň stĺpcov a porovná každý stĺpec samostatne. Podrobnosti nájdete v príručke Zlúčenie na úrovni stĺpcov.

Použitie fasády

Fasáda Merql pracuje s pomenovanými snímkami uloženými na disku. Pred vytvorením snímky zavolajte Merql::init() s databázou, v ktorej sa nachádza každý stav:

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

Nevytvárajte všetky tri snímky postupne z jednej databázy. Ich strana by potom obsahovala aj zmeny našej strany, takže každý riadok pridaný na našej strane by vyzeral ako pridaný na oboch stranách a bol by označený ako konflikt insert_insert.

Merql::merge(), Merql::patch() a Merql::plan() vytvoria ThreeWayMerge bez CellMergeConfig, takže zlučujú iba na úrovni stĺpcov. Ak chcete zlúčiť na úrovni buniek, načítajte snímky a použite priamo túto triedu, ako je uvedené nižšie.

Priame použitie tried

Ak chcete väčšiu kontrolu, vytvorte inštancie komponentov sami. Snapshotter::capture() nič neukladá, preto ponechajte snímky v pamäti alebo ich uložte pomocou 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'),
);

Skratka na opravu

Ak sa zmenila iba jedna strana (bez súbežných úprav), použite patch(). Ide o obojsmerné zlúčenie, v ktorom sa naša strana rovná základnej verzii, takže konflikty nemôžu vzniknúť:

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

Cez fasádu:

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

patch() je ekvivalentom merge($base, $base, $changes). Keďže sa naša strana nikdy neodchýlila od základnej verzie, každá zmena z ich strany sa prijme bez konfliktu.

Zisťovanie nezhôd schémy

Ak boli medzi snímkami do tabuľky pridané alebo z nej odstránené stĺpce, zlúčenie napriek tomu pokračuje, no zaznamená nezhody. Zmenené typy stĺpcov sa neporovnávajú a premenovaný stĺpec sa zobrazí ako jeden odstránený a jeden pridaný; pozrite si Overenie schémy.

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

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

merql automaticky nemigruje schémy. Za zmeny schémy zodpovedá volajúci. Zlúčenie ich nahlási, aby ste sa mohli rozhodnúť, ako pokračovať.

MergeResult

Objekt výsledku obsahuje všetko potrebné:

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

Každý MergeOperation má typ (insert, update alebo delete), názov tabuľky, kľúč riadka, hodnoty stĺpcov a zdroj (ours, theirs alebo merged). merged označuje riadok, ktorý aktualizovali obe strany (kombináciou zmien po jednotlivých stĺpcoch), alebo ktorý obe strany odstránili.

Kde možno použiť výsledok zlúčenia

Výsledok zlúčenia opisuje, ako previesť základnú verziu do zlúčeného stavu. Jeho operácie obsahujú zmeny z oboch strán: vloženie z našej strany aj vloženie z ich strany sa zobrazia ako vloženia.

  • Merql::apply() (alebo Applier) vykoná každú operáciu v pripojenej databáze. Bez problémov sa použije v databáze, ktorá stále obsahuje základnú verziu, napríklad v jej čerstvej kópii. Ak ho použijete v databáze, ktorá už obsahuje zmeny jednej strany, vloženie už existujúceho riadka zlyhá a transakcia sa vráti späť.
  • Ak chcete preniesť našu stranu do databázy, ktorá už obsahuje ich stranu, napríklad nasadiť úpravy zo sandboxu do produkčného prostredia, použite iba operácie, ktoré v danej databáze chýbajú: tie so zdrojom ours a aktualizácie merged. Pomocou plánu zlúčenia ich vyberte a pomocou Riadeného použitia ich zapíšte s predbežnými podmienkami pre živé riadky.