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.
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.
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é.
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:
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():
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úť:
$result = $merge->patch($base, $changes);
// Always clean: ours is identical to base.
Cez fasádu:
$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.
$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é:
$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()(aleboApplier) 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
oursa aktualizáciemerged. Pomocou plánu zlúčenia ich vyberte a pomocou Riadeného použitia ich zapíšte s predbežnými podmienkami pre živé riadky.