MergeResult ThreeWayMerge::mergeKeySet (const MergeTask & task) { MergeResult result; detectConflicts (task, result); detectConflicts (task.reverse (), result, true); if (!result.hasConflicts ()) return result; // TODO: test this behaviour (would probably need mocks) Key current; KeySet conflicts = result.getConflictSet (); conflicts.rewind (); while ((current = conflicts.next ())) { for (auto & elem : strategies) { (elem)->resolveConflict (task, current, result); if (!result.isConflict (current)) break; } } return result; }
MergeResult ThreeWayMerge::mergeKeySet(const MergeTask& task) { MergeResult result; detectConflicts (task, result); detectConflicts (task.reverse (), result, true); if (!result.hasConflicts()) return result; // TODO: test this behaviour (would probably need mocks) Key current; KeySet conflicts = result.getConflictSet(); conflicts.rewind(); while ((current = conflicts.next ())) { for (vector<MergeConflictStrategy *>::iterator it = strategies.begin (); it != strategies.end (); ++it) { (*it)->resolveConflict (task, current, result); if (!result.isConflict(current)) break; } } return result; }
TEST (MergeResult, ResolveConflictRemovesKeyFromConflicts) { Key conflictKey = Key ("user/test/config/key1", KEY_VALUE, "testvalue", KEY_END); KeySet conflicts; conflicts.append (conflictKey); KeySet merged; MergeResult result (conflicts, merged); result.resolveConflict (conflictKey); EXPECT_EQ (0, result.getConflictSet ().size ()); }
TEST_F(ThreeWayMergeTest, DeleteModifyConflict) { ours.lookup ("user/parento/config/key1", KDB_O_POP); theirs.lookup ("user/parentt/config/key1").setString ("modifiedvalue"); MergeResult result = merger.mergeKeySet (base, ours, theirs, mergeParent); EXPECT_TRUE(result.hasConflicts()) << "No conflict detected although conflicts should exist"; KeySet conflicts = result.getConflictSet (); ASSERT_EQ(1, conflicts.size())<< "Wrong number of conflicts"; testConflictMeta (conflicts.at (0), CONFLICT_DELETE, CONFLICT_MODIFY); KeySet merged = result.getMergedKeys (); EXPECT_EQ(4, merged.size ()); compareAllExceptKey1 (merged); }
TEST_F(ThreeWayMergeTest, SameMetaKeyModifyConflict) { ours.lookup ("user/parento/config/key1").setMeta<std::string> ("testmeta", "ourvalue"); theirs.lookup ("user/parentt/config/key1").setMeta<std::string> ("testmeta", "theirvalue"); MergeResult result = merger.mergeKeySet (base, ours, theirs, mergeParent); ASSERT_TRUE (result.hasConflicts())<< "No conflict detected although conflicts should exist"; KeySet conflicts = result.getConflictSet (); EXPECT_EQ(1, conflicts.size ()); testConflictMeta (conflicts.at (0), CONFLICT_META, CONFLICT_META); KeySet merged = result.getMergedKeys (); EXPECT_EQ(4, merged.size ()); compareAllExceptKey1 (merged); }
TEST_F(ThreeWayMergeTest, SameAddedDifferentValueConflict) { ours.append (Key ("user/parento/config/key5", KEY_VALUE, "newvalueours", KEY_END)); theirs.append (Key ("user/parentt/config/key5", KEY_VALUE, "newvaluetheirs", KEY_END)); MergeResult result = merger.mergeKeySet (base, ours, theirs, mergeParent); ASSERT_TRUE (result.hasConflicts())<< "No conflict detected although conflicts should exist"; KeySet conflicts = result.getConflictSet (); EXPECT_EQ(1, conflicts.size ()); testConflictMeta (conflicts.at (0), CONFLICT_ADD, CONFLICT_ADD); KeySet merged = result.getMergedKeys (); EXPECT_EQ(5, merged.size ()); compareAllKeys (merged); }
int MergingKDB::synchronize (KeySet & returned, Key & parentKey, ThreeWayMerge & merger) { try { // write our config int ret = KDB::set (returned, parentKey); // update our config (if no conflict) KDB::get (returned, parentKey); return ret; } catch (KDBException const &) { // a conflict occurred, see if we can solve it with the merger // refresh the key database KeySet theirs = returned.dup (); KDB::get (theirs, parentKey); // try to merge MergeResult result = merger.mergeKeySet (MergeTask (BaseMergeKeys (base, parentKey), OurMergeKeys (returned, parentKey), TheirMergeKeys (theirs, parentKey), parentKey)); if (!result.hasConflicts ()) { // hurray, we solved the issue KeySet resultKeys = result.getMergedKeys (); int ret = KDB::set (resultKeys, parentKey); base = resultKeys; return ret; } else { // nothing we can do anymore KeySet conflictSet = result.getConflictSet (); throw MergingKDBException (parentKey, conflictSet); } } }
int main() { KeySet ours; KeySet theirs; KeySet base; // the root of the subtree containing our keys (i.e. our side of the merge) Key oursRoot ("user/ours", KEY_END); // the root of the subtree containing their keys (i.e. their side of the merge) Key theirsRoot ("user/theirs", KEY_END); // the root of the subtree containing the base keys (i.e. the common ancestor of the merge) Key baseRoot ("user/base", KEY_END); // the root of the subtree that will contain the merge result Key resultRoot ("user/result", KEY_END); // Step 1: retrieve clean KeySets containing only those // keys that should be merged. This is a bit trickier than // it seems at first. Have a look at the documentation of kdbGet // for detailed information // things to note: // * use blocks with local KDB instances so we don't have to worry about // writing the keys back // * remove the root key itself from the result KeySet because it usually // contains the mounted filename and cannot be merged anyway // Also have a look at the documentation of kdbSet() // (http://doc.libelektra.org/api/latest/html/group__kdb.html#ga11436b058408f83d303ca5e996832bcf). // The merging framework can also be used to resolve conflicts resulting from // concurrent calls to kdbSet() as described in the example of kdbSet(). { KDB lkdb; lkdb.get (ours, oursRoot); ours = ours.cut (oursRoot); ours.lookup(oursRoot, KDB_O_POP); lkdb.get (theirs, theirsRoot); theirs = theirs.cut (theirsRoot); theirs.lookup(theirsRoot, KDB_O_POP); lkdb.get (base, baseRoot); base = base.cut (baseRoot); base.lookup(baseRoot, KDB_O_POP); } // Step 2: Make sure that no keys reside below the intended merge result root // Usually the merge can be either aborted if this is the case or the existing // keys can be overwritten. KeySet resultKeys; kdb::KDB kdb; kdb.get (resultKeys, resultRoot); KeySet discard = resultKeys.cut (resultRoot); if (discard.size () != 0) { // handle existing keys below the result root return -1; } ThreeWayMerge merger; // Step 3: Decide which resolution strategies to use. The strategies are registered // with the merge and applied in order as soon as a conflict is detected. If a strategy // marks a conflict as resolved, no further strategies are consulted. Therefore the order // in which they are registered is absolutely crucial. With this chaining the strategies // remain simple, but can be combined to powerful resolution strategies. // Have a look at the strategy documentation for further details on what they do and how they work. // The unit tests also provide insight into how the strategies work. // In order to simplify the initialization, predefined merge configurations exist. // in this example we first resolve all the keys that can be automatically // resolved (e.g. only one side was modified). This is exactly the use case of the // AutoMergeConfiguration. AutoMergeConfiguration configuration; configuration.configureMerger(merger); // Step 4: Perform the actual merge MergeResult result = merger.mergeKeySet ( MergeTask (BaseMergeKeys (base, baseRoot), OurMergeKeys (ours, oursRoot), TheirMergeKeys (theirs, theirsRoot), resultRoot)); // Step 5: work with the result. The merger will return an object containing information // about the merge result. if (!result.hasConflicts ()) { // output some statistical information cout << result.getMergedKeys().size() << " keys in the result" << endl; cout << result.getNumberOfEqualKeys() << " keys were equal" << endl; cout << result.getNumberOfResolvedKeys() << " keys were resolved" << endl; // write the result resultKeys.append(result.getMergedKeys()); kdb.set (resultKeys, resultRoot); return 0; } else { KeySet conflicts = result.getConflictSet(); cerr << conflicts.size() << " conflicts were detected that could not be resolved automatically:" << endl; conflicts.rewind(); Key current; while ((current = conflicts.next())) { // For each unresolved conflict there is a conflict key in the merge result. // This conflict key contains meta information about the reason of the conflict. // In particular the metakeys conflict/operation/our and conflict/operation/their contain // the operations done on our version of the key and their version of the key relative to // the base version of the key. string ourConflict = current.getMeta<string> ("conflict/operation/our"); string theirConflict = current.getMeta<string> ("conflict/operation/their"); cerr << current << endl; cerr << "ours: " << ourConflict << ", theirs: " << theirConflict << endl; cerr << endl; } cerr << "Merge unsuccessful." << endl; return -1; } }