예제 #1
0
TEST_F(ThreeWayMergeTest, CascadingParentsCauseNoCascadingKeys)
{
	Key root("/", KEY_END);
	MergeResult result = merger.mergeKeySet(MergeTask(BaseMergeKeys(base, Key("/parentb", KEY_END)),
			  OurMergeKeys(ours, Key("/parento", KEY_END)),
			  TheirMergeKeys (theirs, Key("/parentt", KEY_END)),
			  root));
	EXPECT_FALSE(result.hasConflicts()) << "Invalid conflict detected";

	Key current;
	KeySet merged = result.getMergedKeys ();
	merged.rewind();
	while ((current = merged.next ()))
	{
		EXPECT_FALSE(current.getNamespace() == "/");
	}
}
예제 #2
0
TEST_F(ThreeWayMergeTest, EqualKeySetsWontCauseSync)
{
	unsyncKeys(ours);
	unsyncKeys(theirs);
	unsyncKeys(base);

	MergeResult result = merger.mergeKeySet (base, ours, theirs, ourParent);
	EXPECT_FALSE(result.hasConflicts()) << "Invalid conflict detected";

	KeySet merged = result.getMergedKeys();

	Key current;
	merged.rewind();
	while ((current = merged.next ()))
	{
		EXPECT_FALSE(current.needSync());
	}
}
예제 #3
0
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);
}
예제 #4
0
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);
		}
	}
}
void OneSideValueStrategy::resolveConflict(const MergeTask& task, Key& conflictKey, MergeResult& result)
{
	ConflictOperation ourOperation = getOurConflictOperation (conflictKey);
	ConflictOperation theirOperation = getTheirConflictOperation (conflictKey);

	string ourLookup = rebasePath (conflictKey, task.mergeRoot, task.ourParent);
	string theirLookup = rebasePath (conflictKey, task.mergeRoot, task.theirParent);

	// TODO: this is a subset of the onesidestrategy
	// the onesidestrategy could be split up into several smaller strategies
	if ((ourOperation == CONFLICT_SAME && theirOperation == CONFLICT_MODIFY) || (ourOperation == CONFLICT_MODIFY && theirOperation == CONFLICT_SAME))
	{
		string lookupPath;
		Key winningKey;

		switch (winningSide)
		{
		case BASE:
			lookupPath = rebasePath (conflictKey, task.mergeRoot, task.baseParent);
			winningKey = task.base.lookup (lookupPath);
			break;
		case OURS:
			lookupPath = rebasePath (conflictKey, task.mergeRoot, task.ourParent);
			winningKey = task.ours.lookup (lookupPath);
			break;
		case THEIRS:
			lookupPath = rebasePath (conflictKey, task.mergeRoot, task.theirParent);
			winningKey = task.theirs.lookup (lookupPath);
			break;
		}

		if (winningKey)
		{
			conflictKey.setString (winningKey.getString ());
			result.resolveConflict (conflictKey);
			result.addMergeKey (conflictKey);
		}
	}
}
예제 #6
0
TEST (MergeResult, CountsEqualKeysCorrectly)
{
	Key mergedKey1 = Key ("user/test/config/key1", KEY_END);
	Key mergedKey2 = Key ("user/test/config/key2", KEY_END);
	Key mergedKey3 = Key ("user/test/config/key3", KEY_END);
	Key conflictKey1 = Key ("user/test/config/key4", KEY_END);
	KeySet conflicts;
	conflicts.append (conflictKey1);
	KeySet merged;
	merged.append (mergedKey1);
	merged.append (mergedKey2);
	MergeResult result (conflicts, merged);
	EXPECT_EQ (2, result.getNumberOfEqualKeys ()) << "Initially merged keys not counted";
	result.resolveConflict (conflictKey1);
	result.addMergeKey (conflictKey1);
	EXPECT_EQ (2, result.getNumberOfEqualKeys ()) << "Resolved key is counted as equal key";
	result.addMergeKey (mergedKey3);
	EXPECT_EQ (3, result.getNumberOfEqualKeys ()) << "Merged key is not counted as equal key";
}
예제 #7
0
void AutoMergeStrategy::resolveConflict(const MergeTask& task, Key& conflictKey, MergeResult& result)
{

	ConflictOperation ourOperation = getOurConflictOperation(conflictKey);
	ConflictOperation theirOperation = getTheirConflictOperation(conflictKey);

	string ourLookup = rebasePath (conflictKey, task.mergeRoot, task.ourParent);
	string theirLookup = rebasePath (conflictKey, task.mergeRoot, task.theirParent);

	switch (ourOperation)
	{
	case SAME:
		if (theirOperation == MODIFY || theirOperation == ADD)
		{
			Key source = task.theirs.lookup(theirLookup);
			conflictKey.setString(source.getString());
			result.resolveConflict(conflictKey);
			result.addMergeKey(conflictKey);
		}

		if (theirOperation == DELETE)
		{
			result.resolveConflict(conflictKey);
		}
		break;
	case MODIFY:
	case ADD:
		if (theirOperation == SAME)
		{
			Key source = task.ours.lookup(ourLookup);
			conflictKey.setString(source.getString());
			result.resolveConflict(conflictKey);
			result.addMergeKey(conflictKey);
		}
		break;
	case DELETE:
		if (theirOperation == SAME)
		{
			result.resolveConflict(conflictKey);
		}
		break;
	case META:
		break;
	}

}
예제 #8
0
void ThreeWayMerge::detectConflicts (const MergeTask & task, MergeResult & mergeResult, bool reverseConflictMeta = false)
{
	Key our;
	cursor_t savedCursor = task.ours.getCursor ();
	task.ours.rewind ();

	while ((our = task.ours.next ()))
	{
		string theirLookup = rebasePath (our, task.ourParent, task.theirParent);
		Key theirLookupResult = task.theirs.lookup (theirLookup);

		// we have to copy it to obtain owner etc...
		Key mergeKey = rebaseKey (our, task.ourParent, task.mergeRoot);

		if (keyDataEqual (our, theirLookupResult))
		{
			// keydata matches, see if metakeys match
			if (keyMetaEqual (our, theirLookupResult))
			{
				if (task.ourParent.getFullName () == task.mergeRoot.getFullName ())
				{
					// the key was not rebased, we can reuse our (prevents that the key is rewritten)
					mergeResult.addMergeKey (our);
				}
				else
				{
					// the key causes no merge conflict, but the merge result is below a new parent
					mergeResult.addMergeKey (mergeKey);
				}
			}
			else
			{
				// metakeys are different
				mergeResult.addConflict (mergeKey, CONFLICT_META, CONFLICT_META);
			}
		}
		else
		{
			string baseLookup = rebasePath (our, task.ourParent, task.baseParent);
			Key baseLookupResult = task.base.lookup (baseLookup);

			// check if the keys was newly added in ours
			if (baseLookupResult)
			{
				// the key exists in base, check if the key still exists in theirs
				if (theirLookupResult)
				{
					// check if only they modified it
					if (!keyDataEqual (our, baseLookupResult) && keyDataEqual (theirLookupResult, baseLookupResult))
					{
						// the key was only modified in ours
						addAsymmetricConflict (mergeResult, mergeKey, CONFLICT_MODIFY, CONFLICT_SAME,
								       reverseConflictMeta);
					}
					else
					{
						// check if both modified it
						if (!keyDataEqual (our, baseLookupResult) &&
						    !keyDataEqual (theirLookupResult, baseLookupResult))
						{
							// the key was modified on both sides
							mergeResult.addConflict (mergeKey, CONFLICT_MODIFY, CONFLICT_MODIFY);
						}
					}
				}
				else
				{
					// the key does not exist in theirs anymore, check if ours has modified it
					if (keyDataEqual (our, baseLookupResult))
					{
						// the key was deleted in theirs, and not modified in ours
						addAsymmetricConflict (mergeResult, mergeKey, CONFLICT_SAME, CONFLICT_DELETE,
								       reverseConflictMeta);
					}
					else
					{
						// the key was deleted in theirs, but modified in ours
						addAsymmetricConflict (mergeResult, mergeKey, CONFLICT_MODIFY, CONFLICT_DELETE,
								       reverseConflictMeta);
					}
				}
			}
			else
			{
				// the key does not exist in base, check if the key was added in theirs
				if (theirLookupResult)
				{
					// check if the key was added with the same value in theirs
					if (keyDataEqual (mergeKey, theirLookupResult))
					{
						if (keyMetaEqual (our, theirLookupResult))
						{
							// the key was added on both sides with the same value
							if (task.ourParent.getFullName () == task.mergeRoot.getFullName ())
							{
								// the key was not rebased, we can reuse our and prevent the sync flag being
								// set
								mergeResult.addMergeKey (our);
							}
							else
							{
								// the key causes no merge conflict, but the merge result is below a new
								// parent
								mergeResult.addMergeKey (mergeKey);
							}
						}
						else
						{
							// metakeys are different
							mergeResult.addConflict (mergeKey, CONFLICT_META, CONFLICT_META);
						}
					}
					else
					{
						// the key was added on both sides with different values
						mergeResult.addConflict (mergeKey, CONFLICT_ADD, CONFLICT_ADD);
					}
				}
				else
				{
					// the key was only added to ours
					addAsymmetricConflict (mergeResult, mergeKey, CONFLICT_ADD, CONFLICT_SAME, reverseConflictMeta);
				}
			}
		}
	}

	task.ours.setCursor (savedCursor);
}
예제 #9
0
void ThreeWayMerge::detectConflicts(const MergeTask& task, MergeResult& mergeResult, bool reverseConflictMeta = false)
{
	Key our;
	cursor_t savedCursor = task.ours.getCursor ();
	task.ours.rewind ();

	while ((our = task.ours.next ()))
	{
		if (our.getName() == task.ourParent.getName())
			continue;

		string theirLookup = rebasePath (our, task.ourParent, task.theirParent);
		Key theirLookupResult = task.theirs.lookup (theirLookup);

		// we have to copy it to obtain owner etc...
		Key mergeKey = rebaseKey (our, task.ourParent, task.mergeRoot);

		if (keyDataEqual (our, theirLookupResult))
		{
			// keydata matches, see if metakeys match
			if (keyMetaEqual (our, theirLookupResult))
			{
				mergeResult.addMergeKey (mergeKey);
			}
			else
			{
				// metakeys are different
				mergeResult.addConflict (mergeKey, META, META);
			}
		}
		else
		{
			string baseLookup = rebasePath (our, task.ourParent, task.baseParent);
			Key baseLookupResult = task.base.lookup (baseLookup);

			// check if the keys was newly added in ours
			if (baseLookupResult)
			{
				// the key exists in base, check if the key still exists in theirs
				if (theirLookupResult)
				{
					// check if only they modified it
					if (!keyDataEqual (our, baseLookupResult) && keyDataEqual (theirLookupResult, baseLookupResult))
					{
						// the key was only modified in ours
						addAsymmetricConflict (mergeResult, mergeKey, MODIFY, SAME, reverseConflictMeta);
					}
					else
					{
						// check if both modified it
						if (!keyDataEqual (our, baseLookupResult) && !keyDataEqual (theirLookupResult, baseLookupResult))
						{
							// the key was modified on both sides
							mergeResult.addConflict (mergeKey, MODIFY, MODIFY);
						}
					}
				}
				else
				{
					// the key does not exist in theirs anymore, check if ours has modified it
					if (keyDataEqual (our, baseLookupResult))
					{
						// the key was deleted in theirs, and not modified in ours
						addAsymmetricConflict (mergeResult, mergeKey, SAME, DELETE, reverseConflictMeta);
					}
					else
					{
						// the key was deleted in theirs, but modified in ours
						addAsymmetricConflict (mergeResult, mergeKey, MODIFY, DELETE, reverseConflictMeta);
					}
				}
			}
			else
			{
				// the key does not exist in base, check if the key was added in theirs
				if (theirLookupResult)
				{
					// check if the key was added with the same value in theirs
					if (keyDataEqual (mergeKey, theirLookupResult))
					{
						if (keyMetaEqual (our, theirLookupResult))
						{
							// the key was added on both sides with the same value
							mergeResult.addMergeKey (mergeKey);
						}
						else
						{
							// metakeys are different
							mergeResult.addConflict (mergeKey, META, META);
						}
					}
					else
					{
						// the key was added on both sides with different values
						mergeResult.addConflict (mergeKey, ADD, ADD);
					}
				}
				else
				{
					// the key was only added to ours
					addAsymmetricConflict (mergeResult, mergeKey, ADD, SAME, reverseConflictMeta);
				}
			}
		}
	}

	task.ours.setCursor (savedCursor);
}
예제 #10
0
int ImportCommand::execute (Cmdline const & cl)
{
	size_t argc = cl.arguments.size ();
	if (argc != 1 && argc != 2 && argc != 3)
	{
		throw invalid_argument ("need 1 to 3 arguments");
	}

	Key root = cl.createKey (0);
	if (!root.isValid ())
	{
		throw invalid_argument ("root key \"" + cl.arguments[0] + "\" is not a valid key name");
	}

	KeySet originalKeys;
	kdb.get (originalKeys, root);
	KeySet base = originalKeys.cut (root);
	printWarnings (cerr, root);

	string format = cl.format;
	if (argc > 1) format = cl.arguments[1];

	string file = "/dev/stdin";
	if (argc > 2 && cl.arguments[2] != "-") file = cl.arguments[2];

	Modules modules;
	PluginPtr plugin = modules.load (format, cl.getPluginsConfig ());

	Key errorKey (root);
	errorKey.setString (file);

	KeySet importedKeys;
	plugin->get (importedKeys, errorKey);
	importedKeys = importedKeys.cut (root);

	printWarnings (cerr, errorKey);
	printError (cerr, errorKey);

	ThreeWayMerge merger;
	MergeHelper helper;

	helper.configureMerger (cl, merger);
	MergeResult result = merger.mergeKeySet (
		MergeTask (BaseMergeKeys (base, root), OurMergeKeys (base, root), TheirMergeKeys (importedKeys, root), root));

	helper.reportResult (cl, result, cout, cerr);

	int ret = -1;
	if (!result.hasConflicts ())
	{
		if (cl.verbose)
		{
			cout << "The merged keyset with strategy " << cl.strategy << " is:" << endl;
			cout << result.getMergedKeys ();
		}

		KeySet resultKeys = result.getMergedKeys ();
		originalKeys.append (resultKeys);
		kdb.set (originalKeys, root);
		ret = 0;
	}

	return ret;
}
예제 #11
0
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;
	}
}
예제 #12
0
파일: editor.cpp 프로젝트: tryge/libelektra
int EditorCommand::execute(Cmdline const& cl)
{
#ifdef _WIN32
	throw EditorNotAvailable();
#endif

	int argc = cl.arguments.size ();
	if (argc < 1)
	{
		throw invalid_argument ("wrong number of arguments, 1 needed");
	}
	Key root = cl.createKey(0);

	KeySet ours;
	KDB kdb;
	kdb.get (ours, root);
	KeySet oursToEdit = ours.cut (root);

	// export it to file
	string format = cl.format;
	if (argc > 1) format = cl.arguments[1];

	Modules modules;
	PluginPtr plugin = modules.load(format);

	tmpFile();
	if (cl.verbose) std::cout << "filename set to " << filename << std::endl;
	Key errorKey(root);
	errorKey.setString(filename);

	if (plugin->set(oursToEdit, errorKey) == -1)
	{
		printWarnings(cerr, errorKey);
		printError(cerr, errorKey);
		return 11;
	}

	printWarnings(cerr, errorKey);


	// start editor
	if (cl.verbose) std::cout << "running editor with " << filename << std::endl;
	if (!cl.editor.empty())
	{
		if (!runEditor (cl.editor, filename))
		{
			std::cerr << "Could not run editor " << cl.editor << std::endl;
			return 12;
		}
	} else {
		if (!runAllEditors(filename))
		{
			std::cerr << "Could not run any editor, please change /sw/elektra/kdb/#0/current/editor" << std::endl;
			return 12;
		}
	}

	// import from the file
	KeySet importedKeys;
	plugin->get(importedKeys, errorKey);
	importedKeys = importedKeys.cut(root);

	printWarnings (cerr, errorKey);
	printError (cerr, errorKey);

	ThreeWayMerge merger;
	MergeHelper helper;

	helper.configureMerger (cl, merger);
	MergeResult result = merger.mergeKeySet (
			MergeTask (BaseMergeKeys (oursToEdit, root), OurMergeKeys (oursToEdit, root),
					TheirMergeKeys (importedKeys, root), root));

	helper.reportResult (cl, result, cout, cerr);

	int ret = 13;
	if (!result.hasConflicts ())
	{
		if (cl.verbose)
		{
			cout << "The merged keyset with strategy " << cl.strategy << " is:" << endl;
			cout << result.getMergedKeys();
		}

		KeySet resultKeys = result.getMergedKeys();
		if (cl.verbose) std::cout << "about to write result keys " << resultKeys << std::endl;
		ours.append(resultKeys);
		kdb.set (ours, root);
		if (cl.verbose) std::cout << "successful, cleaning up " << filename << std::endl;
		unlink(filename.c_str());
		ret = 0;
	}
	else
	{
		std::cout << "Import not successful, please import and remove \"" << filename << '"' << std::endl;
	}

	return ret;
}
예제 #13
0
파일: merge.cpp 프로젝트: tryge/libelektra
int MergeCommand::execute(Cmdline const& cl)
{

	if (cl.arguments.size () < 4)
	{
		throw invalid_argument ("wrong number of arguments, 4 needed");
	}

	Key oursRoot = cl.createKey(0);
	Key theirsRoot = cl.createKey(1);
	Key baseRoot  = cl.createKey(2);
	Key resultRoot = cl.createKey(3);

	KeySet ours;
	KeySet theirs;
	KeySet base;

	{
		KDB lkdb;
		lkdb.get (ours, oursRoot);
		ours = ours.cut (oursRoot);
		ours.lookup(oursRoot, KDB_O_POP);
		if (cl.verbose) std::cout << "we got ours: " << oursRoot << " with keys " << ours << std::endl;
	}
	{
		KDB lkdb;
		lkdb.get (theirs, theirsRoot);
		theirs = theirs.cut (theirsRoot);
		ours.lookup(oursRoot, KDB_O_POP);
		if (cl.verbose) std::cout << "we got theirs: " << theirsRoot << " with keys " << theirs << std::endl;
	}
	{
		KDB lkdb;
		lkdb.get (base, baseRoot);
		base = base.cut (baseRoot);
		ours.lookup(oursRoot, KDB_O_POP);
		if (cl.verbose) std::cout << "we got base: " << baseRoot << " with keys " << base << std::endl;
	}

	KeySet resultKeys;
	kdb.get (resultKeys, resultRoot);

	KeySet discard = resultKeys.cut (resultRoot);
	if (discard.size () != 0)
	{
		if (cl.force)
		{
			if (cl.verbose)
			{
				std::cout << "will remove " << discard.size () << " keys, because -f was given" << std::endl;
			}
		}
		else
		{
			std::cerr << discard.size ()
					<< " keys exist in merge resultroot, will quit. Use -f to override the keys there." << std::endl;
		}
	}

	MergeHelper helper;
	ThreeWayMerge merger;

	helper.configureMerger (cl, merger);

	MergeResult result = merger.mergeKeySet (
			MergeTask (BaseMergeKeys (base, baseRoot), OurMergeKeys (ours, oursRoot),
					TheirMergeKeys (theirs, theirsRoot), resultRoot));

	helper.reportResult (cl, result, cout, cerr);

	int ret = 0;
	if (!result.hasConflicts ())
	{
		resultKeys.append(result.getMergedKeys());
		kdb.set (resultKeys, resultRoot);
	}
	else
	{
		ret = -1;
	}

	return ret;
}