private static XmlNode HandleOurChildNotPresent(XmlMerger merger, XmlNode ours, XmlNode ancestor, XmlNode theirChild, string pathToFileInRepository, XmlNode ancestorChild, MergeSituation mergeSituation, IElementDescriber mergeStrategyForChild) { if (theirChild == null) { // Both deleted it. merger.EventListener.ChangeOccurred(new XmlBothDeletionChangeReport(pathToFileInRepository, ancestorChild)); } else { if (XmlUtilities.AreXmlElementsEqual(ancestorChild, theirChild)) { // We deleted it. They did nothing. merger.EventListener.ChangeOccurred(new XmlDeletionChangeReport(pathToFileInRepository, ancestor, ancestorChild)); } else { // delete vs edit conflict. merger.ConflictOccurred(new RemovedVsEditedElementConflict(theirChild.Name, theirChild, null, ancestorChild, mergeSituation, mergeStrategyForChild, mergeSituation.BetaUserId)); // ReSharper disable once PossibleNullReferenceException -- if ours.OwnerDocument is null, we have other problems ours.AppendChild(ours.OwnerDocument.ImportNode(theirChild, true)); } } return(ours); }
public MergeChildrenMethod(XmlNode ours, XmlNode theirs, XmlNode ancestor, XmlMerger merger) { _ours = ours; _theirs = theirs; _ancestor = ancestor; _merger = merger; }
private static XmlNode HandleOurChildNotPresent(XmlMerger merger, XmlNode ours, XmlNode ancestor, XmlNode theirChild, string pathToFileInRepository, XmlNode ancestorChild, MergeSituation mergeSituation, IElementDescriber mergeStrategyForChild) { if (theirChild == null) { // Both deleted it. merger.EventListener.ChangeOccurred(new XmlBothDeletionChangeReport(pathToFileInRepository, ancestorChild)); } else { if (XmlUtilities.AreXmlElementsEqual(ancestorChild, theirChild)) { // We deleted it. They did nothing. merger.EventListener.ChangeOccurred(new XmlDeletionChangeReport(pathToFileInRepository, ancestor, ancestorChild)); } else { // delete vs edit conflict. merger.ConflictOccurred(new RemovedVsEditedElementConflict(theirChild.Name, theirChild, null, ancestorChild, mergeSituation, mergeStrategyForChild, mergeSituation.BetaUserId)); ours.AppendChild(theirChild); } } return(ours); }
/// <summary> /// Produce a string that represents the 3-way merger of the given three elements. /// </summary> public ChorusNotesAnnotationMergingStrategy(MergeOrder order) { _annotationMerger = new XmlMerger(order.MergeSituation) { EventListener = order.EventListener }; SetupElementStrategies(); }
internal static void DoMerge(MergeOrder mergeOrder, XmlMerger merger) { XmlNode ours; XmlNode theirs; XmlNode common; DoPreMerge(mergeOrder, out ours, out theirs, out common); var results = merger.Merge(ours, theirs, common); DoPostMerge(mergeOrder.pathToOurs, results.MergedNode); }
internal static XmlMerger CreateXmlMergerForFieldWorksData(MergeOrder mergeOrder, MetadataCache mdc) { var merger = new XmlMerger(mergeOrder.MergeSituation) { EventListener = mergeOrder.EventListener }; BootstrapSystem(mdc, merger); return merger; }
public void Do3WayMerge(MetadataCache mdc, MergeOrder mergeOrder) { var merger = new XmlMerger(mergeOrder.MergeSituation) { EventListener = mergeOrder.EventListener }; CustomLayoutMergeStrategiesMethod.AddElementStrategies(merger.MergeStrategies); CustomLayoutMergeService.DoMerge(mergeOrder, merger); }
public void FixtureSetup() { _mdc = MetadataCache.TestOnlyNewCache; var mergeOrder = new MergeOrder(null, null, null, new NullMergeSituation()) { EventListener = new ListenerForUnitTests() }; _merger = FieldWorksMergeServices.CreateXmlMergerForFieldWorksData(mergeOrder, _mdc); }
/// <summary> /// Produce a string that represents the 3-way merger of the given three elements. /// </summary> public LiftEntryMergingStrategy(MergeOrder mergeOrder) { _entryMerger = new XmlMerger(mergeOrder.MergeSituation) { MergeStrategies = {ElementToMergeStrategyKeyMapper = new LiftElementToMergeStrategyKeyMapper()}, EventListener = mergeOrder.EventListener }; LiftElementStrategiesMethod.AddLiftElementStrategies(_entryMerger.MergeStrategies); }
public override void TestSetup() { base.TestSetup(); Mdc.UpgradeToVersion(MetadataCache.MaximumModelVersion); var mergeOrder = new MergeOrder(null, null, null, new NullMergeSituation()) { EventListener = new ListenerForUnitTests() }; _merger = FieldWorksMergeServices.CreateXmlMergerForFieldWorksData(mergeOrder, Mdc); }
internal static void DoMerge(MergeOrder mergeOrder, XmlMerger merger) { XmlNode ours; XmlNode theirs; XmlNode common; DoPreMerge(mergeOrder, out ours, out theirs, out common); // The document element is being returned here, so our parent isn't relevant and won't be used by the merge var results = merger.Merge(null, ours, theirs, common); DoPostMerge(mergeOrder.pathToOurs, results.MergedNode); }
/// <summary> /// Gets the collection of element merge strategies. /// </summary> public MergeStrategies GetStrategies() { var merger = new XmlMerger(new MergeSituation(null, null, null, null, null, MergeOrder.ConflictHandlingModeChoices.WeWin)); var def = new ElementStrategy(true) { MergePartnerFinder = new FindByEqualityOfTree() }; merger.MergeStrategies.SetStrategy("def", def); return merger.MergeStrategies; }
/// <summary> /// Constructor /// </summary> public LiftRangesMergingStrategy(MergeOrder mergeOrder) { _merger = new XmlMerger(mergeOrder.MergeSituation) { EventListener = mergeOrder.EventListener }; LiftBasicElementStrategiesMethod.AddLiftBasicElementStrategies(_merger.MergeStrategies); LiftRangesElementStrategiesMethod.AddLiftRangeElementStrategies(_merger.MergeStrategies); }
internal ChildOrderer(List <XmlNode> primary, List <XmlNode> others, Dictionary <XmlNode, XmlNode> correspondences, XmlMerger merger, ChildOrder parentOrder) { _primary = primary; _others = others; _correspondences = correspondences; _positions = new PositionRecord[others.Count]; Debug.Assert(merger != null); _merger = merger; _parentOrder = parentOrder; }
/// <summary> /// Gets the collection of element merge strategies. /// </summary> public MergeStrategies GetStrategies() { var merger = new XmlMerger(new MergeSituation(null, null, null, null, null, MergeOrder.ConflictHandlingModeChoices.WeWin)); var def = new ElementStrategy(true) { MergePartnerFinder = new FindByEqualityOfTree() }; merger.MergeStrategies.SetStrategy("def", def); return(merger.MergeStrategies); }
internal ChildOrderer(List<XmlNode> primary, List<XmlNode> others, Dictionary<XmlNode, XmlNode> correspondences, XmlMerger merger, ChildOrder parentOrder) { _primary = primary; _others = others; _correspondences = correspondences; _positions = new PositionRecord[others.Count]; Debug.Assert(merger != null); _merger = merger; _parentOrder = parentOrder; }
public void AllHadProperty_ButNothingElse() { const string commonAncestor = @"<Lexicon> <LexEntry guid='c1ed94c5-e382-11de-8a39-0800200c9a66'> <Etymology> </Etymology> </LexEntry> </Lexicon>"; const string ours = @"<Lexicon> <LexEntry guid='c1ed94c5-e382-11de-8a39-0800200c9a66'> <Etymology> </Etymology> </LexEntry> </Lexicon>"; const string theirs = @"<Lexicon> <LexEntry guid='c1ed94c5-e382-11de-8a39-0800200c9a66'> <Etymology> </Etymology> </LexEntry> </Lexicon>"; var listener = new ListenerForUnitTests(); var merger = new XmlMerger(new NullMergeSituation()) { EventListener = listener }; merger.MergeStrategies.SetStrategy("Lexicon", ElementStrategy.CreateSingletonElement()); var strat = ElementStrategy.CreateForKeyedElement("guid", false); strat.AttributesToIgnoreForMerging.Add("guid"); merger.MergeStrategies.SetStrategy("LexEntry", strat); strat = ElementStrategy.CreateSingletonElement(); strat.NumberOfChildren = NumberOfChildrenAllowed.ZeroOrOne; merger.MergeStrategies.SetStrategy("Etymology", strat); strat = ElementStrategy.CreateForKeyedElement("guid", false); strat.AttributesToIgnoreForMerging.Add("guid"); merger.MergeStrategies.SetStrategy("LexEtymology", strat); XmlTestHelper.DoMerge(merger.MergeStrategies, merger.MergeSituation, commonAncestor, ours, theirs, new[] { "Lexicon/LexEntry/Etymology" }, null, 0, new List<Type>(), 0, new List<Type>()); }
/// <summary> /// Add conflict. If RecordContextInConflict fails to set a context, and nodeToFindGeneratorFrom is non-null, /// attempt to add a context based on the argument. /// </summary> public static void AddConflictToListener(IMergeEventListener listener, IConflict conflict, XmlNode oursContext, XmlNode theirsContext, XmlNode ancestorContext, IGenerateHtmlContext htmlContextGenerator, XmlMerger merger, XmlNode nodeToFindGeneratorFrom) { // NB: All these steps are crucially ordered. listener.RecordContextInConflict(conflict); if ((conflict.Context == null || conflict.Context is NullContextDescriptor) && nodeToFindGeneratorFrom != null) { // We are too far up the stack for the listener to have been told a context. // Make one out of the current node. conflict.Context = merger.GetContext(nodeToFindGeneratorFrom); } conflict.MakeHtmlDetails(oursContext, theirsContext, ancestorContext, htmlContextGenerator); listener.ConflictOccurred(conflict); }
/// <summary> /// This is for regular three-way merges. /// </summary> public MergeTextNodesMethod(XmlMerger merger, IElementDescriber elementDescriber, HashSet <XmlNode> skipInnerMergeFor, ref XmlNode ours, List <XmlNode> ourKeepers, XmlNode theirs, List <XmlNode> theirKeepers, XmlNode ancestor, List <XmlNode> ancestorKeepers) { _ours = ours; _ourKeepers = ourKeepers; _theirs = theirs; _theirKeepers = theirKeepers; _ancestor = ancestor; _ancestorKeepers = ancestorKeepers; _merger = merger; _elementDescriber = elementDescriber; _skipInnerMergeFor = skipInnerMergeFor; }
/// <summary> /// This is for regular three-way merges. /// </summary> public MergeTextNodesMethod(XmlMerger merger, IElementDescriber elementDescriber, HashSet<XmlNode> skipInnerMergeFor, ref XmlNode ours, List<XmlNode> ourKeepers, XmlNode theirs, List<XmlNode> theirKeepers, XmlNode ancestor, List<XmlNode> ancestorKeepers) { _ours = ours; _ourKeepers = ourKeepers; _theirs = theirs; _theirKeepers = theirKeepers; _ancestor = ancestor; _ancestorKeepers = ancestorKeepers; _merger = merger; _elementDescriber = elementDescriber; _skipInnerMergeFor = skipInnerMergeFor; }
private static XmlNode HandleTheirsNotPresent(XmlMerger merger, XmlNode ancestor, XmlNode ours) { // They deleted it, var mergeSituation = merger.MergeSituation; if (XmlUtilities.AreXmlElementsEqual(ancestor, ours)) { // and we did nothing // Route tested, but the MergeChildrenMethod adds the change report for us. merger.EventListener.ChangeOccurred(new XmlDeletionChangeReport(mergeSituation.PathToFileInRepository, ancestor, ours)); return(null); } // but we edited it. merger.ConflictOccurred(new RemovedVsEditedElementConflict(ancestor.Name, ours, null, ancestor, mergeSituation, merger.MergeStrategies.GetElementStrategy(ancestor), mergeSituation.AlphaUserId)); return(ours); }
/// <summary> /// Bootstrap a merger for the new-styled (nested) files. /// </summary> /// <remarks> /// 1. A generic 'header' element will be handled, although it may not appear in the file. /// 2. All classes will be included. /// 3. Merge strategies for class properties (regular or custom) will have keys of "classname+propname" to make them unique, system-wide. /// </remarks> private static void BootstrapSystem(MetadataCache metadataCache, XmlMerger merger) { merger.MergeStrategies.ElementToMergeStrategyKeyMapper = new FieldWorksElementToMergeStrategyKeyMapper(); var sharedElementStrategies = new Dictionary<string, ElementStrategy>(); CreateSharedElementStrategies(sharedElementStrategies); var strategiesForMerger = merger.MergeStrategies; ContextGen.MergeStrategies = strategiesForMerger; foreach (var sharedKvp in sharedElementStrategies) strategiesForMerger.SetStrategy(sharedKvp.Key, sharedKvp.Value); var customPropDefnStrat = new ElementStrategy(false) { MergePartnerFinder = new FindByMultipleKeyAttributes(new List<string> { SharedConstants.Name, SharedConstants.Class }), ContextDescriptorGenerator = new FieldWorksCustomPropertyContextGenerator(), IsAtomic = true, NumberOfChildren = NumberOfChildrenAllowed.Zero }; strategiesForMerger.SetStrategy(SharedConstants.CustomField, customPropDefnStrat); var headerStrategy = CreateSingletonElementType(false); headerStrategy.ContextDescriptorGenerator = ContextGen; strategiesForMerger.SetStrategy(SharedConstants.Header, headerStrategy); // There are two abstract class names used: CmAnnotation and DsChart. // Chorus knows how to find the matching element for these, as they use <CmAnnotation class='concreteClassname'. // So, add a keyed strategy for each of them. var keyedStrat = ElementStrategy.CreateForKeyedElement(SharedConstants.GuidStr, false); keyedStrat.AttributesToIgnoreForMerging.Add(SharedConstants.Class); keyedStrat.AttributesToIgnoreForMerging.Add(SharedConstants.GuidStr); strategiesForMerger.SetStrategy(SharedConstants.CmAnnotation, keyedStrat); keyedStrat = ElementStrategy.CreateForKeyedElement(SharedConstants.GuidStr, false); keyedStrat.AttributesToIgnoreForMerging.Add(SharedConstants.Class); keyedStrat.AttributesToIgnoreForMerging.Add(SharedConstants.GuidStr); strategiesForMerger.SetStrategy(SharedConstants.DsChart, keyedStrat); foreach (var classInfo in metadataCache.AllConcreteClasses) { MakeClassStrategy(strategiesForMerger, classInfo, ContextGen); AddPropertyStrategiesForClass(strategiesForMerger, classInfo); } }
private static XmlNode HandleTheirChildNotPresent(XmlMerger merger, XmlNode ours, XmlNode ancestor, XmlNode ancestorChild, XmlNode ourChild, MergeSituation mergeSituation, IElementDescriber mergeStrategyForChild, string pathToFileInRepository) { if (XmlUtilities.AreXmlElementsEqual(ancestorChild, ourChild)) { // They deleted it. We did nothing. merger.EventListener.ChangeOccurred(new XmlDeletionChangeReport(pathToFileInRepository, ancestor, ancestorChild)); } else { // edit vs delete conflict. merger.ConflictOccurred(new EditedVsRemovedElementConflict(ourChild.Name, ourChild, null, ancestorChild, mergeSituation, mergeStrategyForChild, mergeSituation.AlphaUserId)); } return(ours); }
public void DuplicateWritingSystemsElementsAreRemoved() { const string badData = @"<ProjectLexiconSettings> <WritingSystems addToSldr='true' /> <WritingSystems addToSldr='true' goner='true' /> </ProjectLexiconSettings>"; var doc = new XmlDocument(); var badRootNode = XmlUtilities.GetDocumentNodeFromRawXml(badData, doc); var merger = new XmlMerger(new NullMergeSituation()); merger.EventListener = new ListenerForUnitTests(); ProjectLexiconSettingsFileHandler.SetupElementStrategies(merger); var oldValue = XmlMergeService.RemoveAmbiguousChildNodes; XmlMergeService.RemoveAmbiguousChildNodes = true; XmlMergeService.RemoveAmbiguousChildren(merger.EventListener, merger.MergeStrategies, badRootNode); XmlMergeService.RemoveAmbiguousChildNodes = oldValue; var childNodes = badRootNode.SelectNodes("WritingSystems"); Assert.IsNotNull(childNodes); Assert.IsTrue(childNodes.Count == 1); Assert.IsNull(childNodes[0].Attributes["goner"]); }
public void DuplicateSpecialElementsAreRemoved() { const string badData = @"<ldml> <special xmlns:palaso='urn://palaso.org/ldmlExtensions/v1' /> <special xmlns:palaso='urn://palaso.org/ldmlExtensions/v1' goner='true' /> <special xmlns:palaso2='urn://palaso.org/ldmlExtensions/v2' /> <special xmlns:palaso2='urn://palaso.org/ldmlExtensions/v2' goner='true' /> <special xmlns:fw='urn://fieldworks.sil.org/ldmlExtensions/v1' /> <special xmlns:fw='urn://fieldworks.sil.org/ldmlExtensions/v1' goner='true' /> </ldml>"; var doc = new XmlDocument(); var badRootNode = XmlUtilities.GetDocumentNodeFromRawXml(badData, doc); var merger = new XmlMerger(new NullMergeSituation()); merger.EventListener = new ListenerForUnitTests(); LdmlFileHandler.SetupElementStrategies(merger); var oldValue = XmlMergeService.RemoveAmbiguousChildNodes; XmlMergeService.RemoveAmbiguousChildNodes = true; XmlMergeService.RemoveAmbiguousChildren(merger.EventListener, merger.MergeStrategies, badRootNode); XmlMergeService.RemoveAmbiguousChildNodes = oldValue; var childNodes = badRootNode.SelectNodes("special"); Assert.IsTrue(childNodes.Count == 3); for (var idx = 0; idx < 3; ++idx) { XmlNode currentNode = childNodes[idx]; switch (idx) { case 0: Assert.IsNotNull(currentNode.Attributes["xmlns:palaso"]); break; case 1: Assert.IsNotNull(currentNode.Attributes["xmlns:palaso2"]); break; case 2: Assert.IsNotNull(currentNode.Attributes["xmlns:fw"]); break; } Assert.IsNull(currentNode.Attributes["goner"]); } }
/// <summary> /// Do a 3-file merge, placing the result over the "ours" file and returning an error status /// </summary> /// <remarks>Implementations can exit with an exception, which the caller will catch and deal with. /// The must not have any UI, no interaction with the user.</remarks> public void Do3WayMerge(MergeOrder mergeOrder) { Guard.AgainstNull(mergeOrder, "mergeOrder"); if (mergeOrder == null) throw new ArgumentNullException("mergeOrder"); var merger = new XmlMerger(mergeOrder.MergeSituation); SetupElementStrategies(merger); merger.EventListener = mergeOrder.EventListener; using(var oursXml = new HtmlFileForMerging(mergeOrder.pathToOurs)) using(var theirsXml = new HtmlFileForMerging(mergeOrder.pathToTheirs)) using (var ancestorXml = new HtmlFileForMerging(mergeOrder.pathToCommonAncestor)) { var result = merger.MergeFiles(oursXml.GetPathToXHtml(), theirsXml.GetPathToXHtml(), ancestorXml.GetPathToXHtml()); CarefullyWriteOutResultingXml(oursXml, result); //now convert back to html oursXml.SaveHtml(); } }
private ChangeAndConflictAccumulator CheckOneWay(string ours, string theirs, string ancestor, params string[] xpaths) { XmlMerger m = new XmlMerger(new NullMergeSituation()); m.MergeStrategies.ElementStrategies.Add("a", ElementStrategy.CreateForKeyedElementInList("key")); m.MergeStrategies.ElementStrategies.Add("b", ElementStrategy.CreateForKeyedElementInList("key")); m.MergeStrategies.ElementStrategies.Add("c", ElementStrategy.CreateForKeyedElementInList("key")); m.MergeStrategies.ElementStrategies.Add("d", ElementStrategy.CreateForKeyedElementInList("key")); var result = m.Merge(ours, theirs, ancestor); foreach (string xpath in xpaths) { XmlTestHelper.AssertXPathMatchesExactlyOne(result.MergedNode, xpath); } return result; }
internal static void MergeAttributes(XmlMerger merger, ref XmlNode ours, XmlNode theirs, XmlNode ancestor) { // Review: What happens if 'ours' is null? // My (RandyR) theory is that deletions are handled in the MergeChildrenMethod code, as are additions. // That being the case, then that code will only call back to the XmlMerger MergerInner code when all three nodes are present, // and will thus never get here. var skipProcessingInOurs = new HashSet<string>(); // Deletions from ancestor, no matter who did it. foreach (var ancestorAttr in XmlUtilities.GetAttrs(ancestor)) { var ourAttr = XmlUtilities.GetAttributeOrNull(ours, ancestorAttr.Name); var theirAttr = XmlUtilities.GetAttributeOrNull(theirs, ancestorAttr.Name); if (theirAttr == null) { if (ourAttr == null) { // Both deleted. // Route tested (x1). merger.EventListener.ChangeOccurred(new XmlAttributeBothDeletedReport(merger.MergeSituation.PathToFileInRepository, ancestorAttr)); ancestor.Attributes.Remove(ancestorAttr); continue; } if (ourAttr.Value != ancestorAttr.Value) { // They deleted, but we changed, so we win under the principle of // least data loss (an attribute can be a huge text element). // Route tested (x1). merger.ConflictOccurred(new EditedVsRemovedAttributeConflict(ourAttr.Name, ourAttr.Value, null, ancestorAttr.Value, merger.MergeSituation, merger.MergeSituation.AlphaUserId)); continue; } // They deleted. We did zip. // Route tested (x1). if(theirs != null) //if there is no theirs node, then attributes weren't actually removed { //Route tested (x1) merger.EventListener.ChangeOccurred(new XmlAttributeDeletedReport(merger.MergeSituation.PathToFileInRepository, ancestorAttr)); ancestor.Attributes.Remove(ancestorAttr); ours.Attributes.Remove(ourAttr); } continue; } if (ourAttr != null) continue; // Route used. // ourAttr == null if (ancestorAttr.Value != theirAttr.Value) { // We deleted it, but at the same time, they changed it. So just add theirs in, under the principle of // least data loss (an attribute can be a huge text element) // Route tested (x1). skipProcessingInOurs.Add(theirAttr.Name); // Make sure we don't process it again in 'ours' loop, below. var importedAttribute = (XmlAttribute)ours.OwnerDocument.ImportNode(theirAttr.CloneNode(true), true); ours.Attributes.Append(importedAttribute); merger.ConflictOccurred(new RemovedVsEditedAttributeConflict(theirAttr.Name, null, theirAttr.Value, ancestorAttr.Value, merger.MergeSituation, merger.MergeSituation.BetaUserId)); continue; } // We deleted it. They did nothing. // Route tested (x1). merger.EventListener.ChangeOccurred(new XmlAttributeDeletedReport(merger.MergeSituation.PathToFileInRepository, ancestorAttr)); ancestor.Attributes.Remove(ancestorAttr); theirs.Attributes.Remove(theirAttr); } var extantNode = ours ?? theirs ?? ancestor; foreach (var theirAttr in XmlUtilities.GetAttrs(theirs)) { // Will never return null, since it will use the default one, if it can't find a better one. var mergeStrategy = merger.MergeStrategies.GetElementStrategy(extantNode); var ourAttr = XmlUtilities.GetAttributeOrNull(ours, theirAttr.Name); var ancestorAttr = XmlUtilities.GetAttributeOrNull(ancestor, theirAttr.Name); if (ourAttr == null) { if (ancestorAttr == null) { // Route tested (x1). skipProcessingInOurs.Add(theirAttr.Name); // Make sure we don't process it again in 'ours loop, below. var importedAttribute = (XmlAttribute)ours.OwnerDocument.ImportNode(theirAttr.CloneNode(true), true); ours.Attributes.Append(importedAttribute); merger.EventListener.ChangeOccurred(new XmlAttributeAddedReport(merger.MergeSituation.PathToFileInRepository, theirAttr)); } // NB: Deletes are all handled above in first loop. continue; } if (ancestorAttr == null) // Both introduced this attribute { if (ourAttr.Value == theirAttr.Value) { // Route tested (x1). merger.EventListener.ChangeOccurred(new XmlAttributeBothAddedReport(merger.MergeSituation.PathToFileInRepository, ourAttr)); continue; } // Both added, but not the same. if (!mergeStrategy.AttributesToIgnoreForMerging.Contains(ourAttr.Name)) { if (merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin) { // Route tested (x1). merger.ConflictOccurred(new BothAddedAttributeConflict(theirAttr.Name, ourAttr.Value, theirAttr.Value, merger.MergeSituation, merger.MergeSituation.AlphaUserId)); } else { // Route tested (x1). ourAttr.Value = theirAttr.Value; merger.ConflictOccurred(new BothAddedAttributeConflict(theirAttr.Name, ourAttr.Value, theirAttr.Value, merger.MergeSituation, merger.MergeSituation.BetaUserId)); } } continue; // Route used. } if (ancestorAttr.Value == ourAttr.Value) { if (ourAttr.Value == theirAttr.Value) { // Route used. continue; // Nothing to do. } // They changed. if (!mergeStrategy.AttributesToIgnoreForMerging.Contains(ourAttr.Name)) { // Route tested (x1). skipProcessingInOurs.Add(theirAttr.Name); merger.EventListener.ChangeOccurred(new XmlAttributeChangedReport(merger.MergeSituation.PathToFileInRepository, theirAttr)); ourAttr.Value = theirAttr.Value; } continue; } if (ourAttr.Value == theirAttr.Value) { // Both changed to same value if (skipProcessingInOurs.Contains(theirAttr.Name)) continue; // Route used. // Route tested (x1). merger.EventListener.ChangeOccurred(new XmlAttributeBothMadeSameChangeReport(merger.MergeSituation.PathToFileInRepository, ourAttr)); continue; } if (ancestorAttr.Value == theirAttr.Value) { // We changed the value. They did nothing. if (!mergeStrategy.AttributesToIgnoreForMerging.Contains(ourAttr.Name)) { // Route tested (x1). merger.EventListener.ChangeOccurred(new XmlAttributeChangedReport(merger.MergeSituation.PathToFileInRepository, ourAttr)); } continue; } if (mergeStrategy.AttributesToIgnoreForMerging.Contains(ourAttr.Name)) continue; // Route used (FileLevelMergeTests) //for unit test see Merge_RealConflictPlusModDateConflict_ModDateNotReportedAsConflict() if (merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin) { // Route tested (x1). merger.ConflictOccurred(new BothEditedAttributeConflict(theirAttr.Name, ourAttr.Value, theirAttr.Value, ancestorAttr.Value, merger.MergeSituation, merger.MergeSituation.AlphaUserId)); } else { // Route tested (x1). ourAttr.Value = theirAttr.Value; merger.ConflictOccurred(new BothEditedAttributeConflict(theirAttr.Name, ourAttr.Value, theirAttr.Value, ancestorAttr.Value, merger.MergeSituation, merger.MergeSituation.BetaUserId)); } } foreach (var ourAttr in XmlUtilities.GetAttrs(ours)) { if (skipProcessingInOurs.Contains(ourAttr.Name)) continue; var theirAttr = XmlUtilities.GetAttributeOrNull(theirs, ourAttr.Name); var ancestorAttr = XmlUtilities.GetAttributeOrNull(ancestor, ourAttr.Name); if (ancestorAttr != null || theirAttr != null) continue; merger.EventListener.ChangeOccurred(new XmlAttributeAddedReport(merger.MergeSituation.PathToFileInRepository, ourAttr)); } }
public void MergeChildren_UsesNodeToGenerateContextDescriptorIfPossible() { string ancestor = @"<a> <b key='one'> <c key='a'>first</c> </b> </a>"; string red = ancestor; string blue = @"<a> <b key='one'> <c key='a'>first</c> <c key='b'>second</c> </b> </a>"; XmlMerger m = new XmlMerger(new NullMergeSituation()); m.MergeStrategies.ElementStrategies.Add("a", ElementStrategy.CreateForKeyedElementInList("key")); var strategy = ElementStrategy.CreateForKeyedElementInList("key"); var contextGenerator = new MockContextGenerator(); strategy.ContextDescriptorGenerator = contextGenerator; m.MergeStrategies.ElementStrategies.Add("b", strategy); m.MergeStrategies.ElementStrategies.Add("c", ElementStrategy.CreateForKeyedElementInList("key")); m.MergeStrategies.ElementStrategies.Add("d", ElementStrategy.CreateForKeyedElementInList("key")); m.Merge(red, blue, ancestor); Assert.That(contextGenerator.InputNode, Is.Not.Null); Assert.That(contextGenerator.InputNode.Name, Is.EqualTo("b")); }
public void AllFilesMissingThrows() { var merger = new XmlMerger(new NullMergeSituation()); Assert.Throws<InvalidOperationException>(() => merger.MergeFiles(null, null, null)); }
internal static void MergeAttributes(XmlMerger merger, ref XmlNode ours, XmlNode theirs, XmlNode ancestor) { // Review: What happens if 'ours' is null? // My (RandyR) theory is that deletions are handled in the MergeChildrenMethod code, as are additions. // That being the case, then that code will only call back to the XmlMerger MergerInner code when all three nodes are present, // and will thus never get here. var skipProcessingInOurs = new HashSet <string>(); // Deletions from ancestor, no matter who did it. foreach (var ancestorAttr in XmlUtilities.GetAttrs(ancestor)) { var ourAttr = XmlUtilities.GetAttributeOrNull(ours, ancestorAttr.Name); var theirAttr = XmlUtilities.GetAttributeOrNull(theirs, ancestorAttr.Name); if (theirAttr == null) { if (ourAttr == null) { // Both deleted. // Route tested (x1). merger.EventListener.ChangeOccurred(new XmlAttributeBothDeletedReport(merger.MergeSituation.PathToFileInRepository, ancestorAttr)); ancestor.Attributes.Remove(ancestorAttr); continue; } if (ourAttr.Value != ancestorAttr.Value) { // They deleted, but we changed, so we win under the principle of // least data loss (an attribute can be a huge text element). // Route tested (x1). merger.ConflictOccurred(new EditedVsRemovedAttributeConflict(ourAttr.Name, ourAttr.Value, null, ancestorAttr.Value, merger.MergeSituation, merger.MergeSituation.AlphaUserId)); continue; } // They deleted. We did zip. // Route tested (x1). if (theirs != null) //if there is no theirs node, then attributes weren't actually removed { //Route tested (x1) merger.EventListener.ChangeOccurred(new XmlAttributeDeletedReport(merger.MergeSituation.PathToFileInRepository, ancestorAttr)); ancestor.Attributes.Remove(ancestorAttr); ours.Attributes.Remove(ourAttr); } continue; } if (ourAttr != null) { continue; // Route used. } // ourAttr == null if (ancestorAttr.Value != theirAttr.Value) { // We deleted it, but at the same time, they changed it. So just add theirs in, under the principle of // least data loss (an attribute can be a huge text element) // Route tested (x1). skipProcessingInOurs.Add(theirAttr.Name); // Make sure we don't process it again in 'ours' loop, below. var importedAttribute = (XmlAttribute)ours.OwnerDocument.ImportNode(theirAttr.CloneNode(true), true); ours.Attributes.Append(importedAttribute); merger.ConflictOccurred(new RemovedVsEditedAttributeConflict(theirAttr.Name, null, theirAttr.Value, ancestorAttr.Value, merger.MergeSituation, merger.MergeSituation.BetaUserId)); continue; } // We deleted it. They did nothing. // Route tested (x1). merger.EventListener.ChangeOccurred(new XmlAttributeDeletedReport(merger.MergeSituation.PathToFileInRepository, ancestorAttr)); ancestor.Attributes.Remove(ancestorAttr); theirs.Attributes.Remove(theirAttr); } var extantNode = ours ?? theirs ?? ancestor; foreach (var theirAttr in XmlUtilities.GetAttrs(theirs)) { // Will never return null, since it will use the default one, if it can't find a better one. var mergeStrategy = merger.MergeStrategies.GetElementStrategy(extantNode); var ourAttr = XmlUtilities.GetAttributeOrNull(ours, theirAttr.Name); var ancestorAttr = XmlUtilities.GetAttributeOrNull(ancestor, theirAttr.Name); if (ourAttr == null) { if (ancestorAttr == null) { // Route tested (x1). skipProcessingInOurs.Add(theirAttr.Name); // Make sure we don't process it again in 'ours loop, below. var importedAttribute = (XmlAttribute)ours.OwnerDocument.ImportNode(theirAttr.CloneNode(true), true); ours.Attributes.Append(importedAttribute); merger.EventListener.ChangeOccurred(new XmlAttributeAddedReport(merger.MergeSituation.PathToFileInRepository, theirAttr)); } // NB: Deletes are all handled above in first loop. continue; } if (ancestorAttr == null) // Both introduced this attribute { if (ourAttr.Value == theirAttr.Value) { // Route tested (x1). merger.EventListener.ChangeOccurred(new XmlAttributeBothAddedReport(merger.MergeSituation.PathToFileInRepository, ourAttr)); continue; } // Both added, but not the same. if (!mergeStrategy.AttributesToIgnoreForMerging.Contains(ourAttr.Name)) { if (merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin) { // Route tested (x1). merger.ConflictOccurred(new BothAddedAttributeConflict(theirAttr.Name, ourAttr.Value, theirAttr.Value, merger.MergeSituation, merger.MergeSituation.AlphaUserId)); } else { // Route tested (x1). ourAttr.Value = theirAttr.Value; merger.ConflictOccurred(new BothAddedAttributeConflict(theirAttr.Name, ourAttr.Value, theirAttr.Value, merger.MergeSituation, merger.MergeSituation.BetaUserId)); } } continue; // Route used. } if (ancestorAttr.Value == ourAttr.Value) { if (ourAttr.Value == theirAttr.Value) { // Route used. continue; // Nothing to do. } // They changed. if (!mergeStrategy.AttributesToIgnoreForMerging.Contains(ourAttr.Name)) { // Route tested (x1). skipProcessingInOurs.Add(theirAttr.Name); merger.EventListener.ChangeOccurred(new XmlAttributeChangedReport(merger.MergeSituation.PathToFileInRepository, theirAttr)); ourAttr.Value = theirAttr.Value; } continue; } if (ourAttr.Value == theirAttr.Value) { // Both changed to same value if (skipProcessingInOurs.Contains(theirAttr.Name)) { continue; // Route used. } // Route tested (x1). merger.EventListener.ChangeOccurred(new XmlAttributeBothMadeSameChangeReport(merger.MergeSituation.PathToFileInRepository, ourAttr)); continue; } if (ancestorAttr.Value == theirAttr.Value) { // We changed the value. They did nothing. if (!mergeStrategy.AttributesToIgnoreForMerging.Contains(ourAttr.Name)) { // Route tested (x1). merger.EventListener.ChangeOccurred(new XmlAttributeChangedReport(merger.MergeSituation.PathToFileInRepository, ourAttr)); } continue; } if (mergeStrategy.AttributesToIgnoreForMerging.Contains(ourAttr.Name)) { continue; // Route used (FileLevelMergeTests) } //for unit test see Merge_RealConflictPlusModDateConflict_ModDateNotReportedAsConflict() if (merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin) { // Route tested (x1). merger.ConflictOccurred(new BothEditedAttributeConflict(theirAttr.Name, ourAttr.Value, theirAttr.Value, ancestorAttr.Value, merger.MergeSituation, merger.MergeSituation.AlphaUserId)); } else { // Route tested (x1). ourAttr.Value = theirAttr.Value; merger.ConflictOccurred(new BothEditedAttributeConflict(theirAttr.Name, ourAttr.Value, theirAttr.Value, ancestorAttr.Value, merger.MergeSituation, merger.MergeSituation.BetaUserId)); } } foreach (var ourAttr in XmlUtilities.GetAttrs(ours)) { if (skipProcessingInOurs.Contains(ourAttr.Name)) { continue; } var theirAttr = XmlUtilities.GetAttributeOrNull(theirs, ourAttr.Name); var ancestorAttr = XmlUtilities.GetAttributeOrNull(ancestor, ourAttr.Name); if (ancestorAttr != null || theirAttr != null) { continue; } merger.EventListener.ChangeOccurred(new XmlAttributeAddedReport(merger.MergeSituation.PathToFileInRepository, ourAttr)); } }
internal static void SetupElementStrategies(XmlMerger merger) { merger.MergeStrategies.ElementToMergeStrategyKeyMapper = new LdmlElementToMergeStrategyKeyMapper(); // See: Palaso repo: SIL.WritingSystems\LdmlDataMapper.cs var strategy = ElementStrategy.CreateSingletonElement(); strategy.ContextDescriptorGenerator = new LdmlContextGenerator(); merger.MergeStrategies.SetStrategy("ldml", strategy); // Child elements of ldml root. merger.MergeStrategies.SetStrategy("identity", ElementStrategy.CreateSingletonElement()); // Child elements of "identity". merger.MergeStrategies.SetStrategy("version", ElementStrategy.CreateSingletonElement()); merger.MergeStrategies.SetStrategy("generation", ElementStrategy.CreateSingletonElement()); merger.MergeStrategies.SetStrategy("language", ElementStrategy.CreateSingletonElement()); merger.MergeStrategies.SetStrategy("script", ElementStrategy.CreateSingletonElement()); merger.MergeStrategies.SetStrategy("territory", ElementStrategy.CreateSingletonElement()); merger.MergeStrategies.SetStrategy("variant", ElementStrategy.CreateSingletonElement()); // sil:special can occur several times throughout the file merger.MergeStrategies.SetStrategy("special_xmlns:sil", new ElementStrategy(false) { MergePartnerFinder = new FindByMatchingAttributeNames(new HashSet<string> { "xmlns:sil" }) }); merger.MergeStrategies.SetStrategy("sil:identity", ElementStrategy.CreateSingletonElement()); merger.MergeStrategies.SetStrategy("localeDisplayNames", ElementStrategy.CreateSingletonElement()); merger.MergeStrategies.SetStrategy("layout", ElementStrategy.CreateSingletonElement()); // Child element of "layout". merger.MergeStrategies.SetStrategy("orientation", new ElementStrategy(false) { IsAtomic = true, MergePartnerFinder = new FindFirstElementWithSameName() }); merger.MergeStrategies.SetStrategy("contextTransforms", ElementStrategy.CreateSingletonElement()); merger.MergeStrategies.SetStrategy("characters", new ElementStrategy(false) { IsAtomic = true, MergePartnerFinder = new FindFirstElementWithSameName() }); merger.MergeStrategies.SetStrategy("delimiters", new ElementStrategy(false) { IsAtomic = true, MergePartnerFinder = new FindFirstElementWithSameName() }); merger.MergeStrategies.SetStrategy("dates", ElementStrategy.CreateSingletonElement()); merger.MergeStrategies.SetStrategy("numbers", new ElementStrategy(false) { IsAtomic = true, MergePartnerFinder = new FindFirstElementWithSameName() }); merger.MergeStrategies.SetStrategy("units", ElementStrategy.CreateSingletonElement()); merger.MergeStrategies.SetStrategy("listPatterns", ElementStrategy.CreateSingletonElement()); merger.MergeStrategies.SetStrategy("collations", ElementStrategy.CreateSingletonElement()); // Child element of collations strategy = new ElementStrategy(false) { IsAtomic = true, // I (RBR) think it would be suicidal to try and merge this element. MergePartnerFinder = new FindByKeyAttribute("type") }; merger.MergeStrategies.SetStrategy("collation", strategy); // Child of 'collation' element (They exist, but we don't care what they are, as long as the parent is 'atomic'. merger.MergeStrategies.SetStrategy("posix", ElementStrategy.CreateSingletonElement()); merger.MergeStrategies.SetStrategy("segmentations", ElementStrategy.CreateSingletonElement()); merger.MergeStrategies.SetStrategy("rbnf", ElementStrategy.CreateSingletonElement()); merger.MergeStrategies.SetStrategy("metadata", ElementStrategy.CreateSingletonElement()); // See: Palaso repo: SIL.WritingSystems\LdmlDataMapper.cs // There currently are up to three 'special' child elements of the 'ldml' root element. // Special "xmlns:palaso" attr strategy = new ElementStrategy(false) { IsAtomic = true, // May not be needed... MergePartnerFinder = new FindByMatchingAttributeNames(new HashSet<string> { "xmlns:palaso" }) }; merger.MergeStrategies.SetStrategy("special_xmlns:palaso", strategy); /* Not needed, as long as the parent is 'atomic'. // Children of 'special' xmlns:palaso // palaso:abbreviation merger.MergeStrategies.SetStrategy("palaso:abbreviation", ElementStrategy.CreateSingletonElement()); // palaso:defaultFontFamily merger.MergeStrategies.SetStrategy("palaso:defaultFontFamily", ElementStrategy.CreateSingletonElement()); // palaso:defaultFontSize merger.MergeStrategies.SetStrategy("palaso:defaultFontSize", ElementStrategy.CreateSingletonElement()); // palaso:defaultKeyboard merger.MergeStrategies.SetStrategy("palaso:defaultKeyboard", ElementStrategy.CreateSingletonElement()); // palaso:isLegacyEncoded merger.MergeStrategies.SetStrategy("palaso:isLegacyEncoded", ElementStrategy.CreateSingletonElement()); // palaso:languageName merger.MergeStrategies.SetStrategy("palaso:languageName", ElementStrategy.CreateSingletonElement()); // palaso:spellCheckingId merger.MergeStrategies.SetStrategy("palaso:spellCheckingId", ElementStrategy.CreateSingletonElement()); // palaso:version merger.MergeStrategies.SetStrategy("palaso:version", ElementStrategy.CreateSingletonElement()); */ // See: Palaso repo: SIL.WritingSystems\LdmlDataMapper.cs // special "xmlns:palaso2" attr: want to merge knownKeyboards child. So the root element is not atomic. strategy = new ElementStrategy(false) { MergePartnerFinder = new FindByMatchingAttributeNames(new HashSet<string> {"xmlns:palaso2"}) }; merger.MergeStrategies.SetStrategy("special_xmlns:palaso2", strategy); // Children of 'strategy' xmlns:palaso2 // palaso2:knownKeyboards: merger.MergeStrategies.SetStrategy("palaso2:knownKeyboards", ElementStrategy.CreateSingletonElement()); // Multiple children of "palaso2:knownKeyboards" element strategy = new ElementStrategy(false) { MergePartnerFinder = new FindByMultipleKeyAttributes(new List<string> {"layout", "locale"}) }; merger.MergeStrategies.SetStrategy("palaso2:keyboard", strategy); merger.MergeStrategies.SetStrategy("palaso2:version", ElementStrategy.CreateSingletonElement()); // Special "xmlns:fw" attr (See FW source file: Src\Common\CoreImpl\PalasoWritingSystemManager.cs strategy = new ElementStrategy(false) { IsAtomic = true, // Really is needed. At least it is for some child elements. MergePartnerFinder = new FindByMatchingAttributeNames(new HashSet<string> { "xmlns:fw" }) }; merger.MergeStrategies.SetStrategy("special_xmlns:fw", strategy); /* Not needed, as long as the parent is 'atomic'. // Children for 'special' xmlns:fw merger.MergeStrategies.SetStrategy("fw:defaultFontFeatures", ElementStrategy.CreateSingletonElement()); merger.MergeStrategies.SetStrategy("fw:graphiteEnabled", ElementStrategy.CreateSingletonElement()); merger.MergeStrategies.SetStrategy("fw:legacyMapping", ElementStrategy.CreateSingletonElement()); merger.MergeStrategies.SetStrategy("fw:matchedPairs", ElementStrategy.CreateSingletonElement()); merger.MergeStrategies.SetStrategy("fw:punctuationPatterns", ElementStrategy.CreateSingletonElement()); merger.MergeStrategies.SetStrategy("fw:quotationMarks", ElementStrategy.CreateSingletonElement()); merger.MergeStrategies.SetStrategy("fw:regionName", ElementStrategy.CreateSingletonElement()); merger.MergeStrategies.SetStrategy("fw:scriptName", ElementStrategy.CreateSingletonElement()); merger.MergeStrategies.SetStrategy("fw:validChars", ElementStrategy.CreateSingletonElement()); merger.MergeStrategies.SetStrategy("fw:variantName", ElementStrategy.CreateSingletonElement()); merger.MergeStrategies.SetStrategy("fw:windowsLCID", ElementStrategy.CreateSingletonElement()); */ // Children for top level 'special' xmlns:sil merger.MergeStrategies.SetStrategy("sil:external-resources", ElementStrategy.CreateSingletonElement()); merger.MergeStrategies.SetStrategy("sil:kbd", new ElementStrategy(false) { IsAtomic = true, MergePartnerFinder = new FindByMultipleKeyAttributes(new List<string>{"id", "alt"}) } ); merger.MergeStrategies.SetStrategy("sil:font", new ElementStrategy(false) { IsAtomic = true, MergePartnerFinder = new FindByMultipleKeyAttributes(new List<string>{"name", "alt"}) } ); merger.MergeStrategies.SetStrategy("sil:spellcheck", new ElementStrategy(false) { IsAtomic = true, MergePartnerFinder = new FindByMultipleKeyAttributes(new List<string>{"type", "alt"}) } ); merger.MergeStrategies.SetStrategy("sil:transform", new ElementStrategy(false) { IsAtomic = true, MergePartnerFinder = new FindByMultipleKeyAttributes(new List<string> { "from", "to", "type", "direction", "function", "alt" }) } ); }
private static XmlMerger GetMerger(MergeSituation mergeSituation, out ListenerForUnitTests listener) { var elementStrategy = new ElementStrategy(false) { IsAtomic = true }; var merger = new XmlMerger(mergeSituation); merger.MergeStrategies.SetStrategy("topatomic", elementStrategy); listener = new ListenerForUnitTests(); merger.EventListener = listener; return merger; }
/// <summary> /// Use this one for a diff of one xml node against another /// </summary> public MergeChildrenMethod(XmlNode after, XmlNode before, XmlMerger merger) : this(after, before, before, merger) { }
internal static void DoMerge(XmlMerger merger, ref XmlNode ours, XmlNode theirs, XmlNode ancestor) { if (ours == null && theirs == null && ancestor == null) { return; // I don't think this is possible, but.... } if (ancestor == null) { // Somebody added it. if (ours == null && theirs != null) { // They added it. merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(merger.MergeSituation.PathToFileInRepository, theirs)); ours = theirs; return; } if (theirs == null && ours != null) { // We added it. merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(merger.MergeSituation.PathToFileInRepository, ours)); return; } // Both added. if (XmlUtilities.AreXmlElementsEqual(ours, theirs)) { // Both added the same thing. merger.EventListener.ChangeOccurred(new XmlBothAddedSameChangeReport(merger.MergeSituation.PathToFileInRepository, ours)); return; } // Both added it, but it isn't the same thing. if (merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin) { // We win. merger.ConflictOccurred(new BothAddedMainElementButWithDifferentContentConflict(ours.Name, ours, theirs, merger.MergeSituation, merger.MergeStrategies.GetElementStrategy(ours), merger.MergeSituation.AlphaUserId)); } else { // They win. merger.ConflictOccurred(new BothAddedMainElementButWithDifferentContentConflict(ours.Name, theirs, ours, merger.MergeSituation, merger.MergeStrategies.GetElementStrategy(theirs), merger.MergeSituation.BetaUserId)); ours = theirs; return; } return; } // ancestor is not null here. if (ours == null) { if (theirs == null) { // Both deleted it. Add change report. merger.EventListener.ChangeOccurred(new XmlBothDeletionChangeReport(merger.MergeSituation.PathToFileInRepository, ancestor)); } else { // We deleted. We don't care if they made any changes, since such changes aren't legal. merger.EventListener.ChangeOccurred(new XmlDeletionChangeReport(merger.MergeSituation.PathToFileInRepository, ancestor.ParentNode, ancestor)); } return; } if (theirs == null) { // They deleted. We don't care if we made any changes, since such changes aren't legal. merger.EventListener.ChangeOccurred(new XmlDeletionChangeReport(merger.MergeSituation.PathToFileInRepository, ancestor.ParentNode, ancestor)); ours = null; return; } // Nothing is null here. if (!XmlUtilities.AreXmlElementsEqual(ours, ancestor)) { ours = ancestor; // Restore ours to ancestor, since ours was changed by some buggy client code or by a hand edit. } // At this point, we don't need to test if ancestor and theirs are the same, or not, // since we have restored current (ours) to the original, or ours was the same as ancestor. // There is probably no point to adding some kind of warning. }
private static XmlNode HandleCaseOfNoAncestorChild(XmlMerger merger, XmlNode ours, XmlNode ourChild, XmlNode theirChild) { var mergeSituation = merger.MergeSituation; var pathToFileInRepository = mergeSituation.PathToFileInRepository; var mergeStrategyForChild = merger.MergeStrategies.GetElementStrategy(ourChild ?? theirChild); if (ourChild == null) { // they added child. merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(pathToFileInRepository, theirChild)); // ReSharper disable once PossibleNullReferenceException -- if ours.OwnerDocument is null, we have other problems ours.AppendChild(ours.OwnerDocument.ImportNode(theirChild, true)); return(ours); } if (theirChild == null) { // We added child. merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(pathToFileInRepository, ourChild)); return(ours); } // Both added child. if (XmlUtilities.AreXmlElementsEqual(ourChild, theirChild)) { // Both are the same. merger.EventListener.ChangeOccurred(new XmlBothAddedSameChangeReport(pathToFileInRepository, ourChild)); return(ours); } // Both are different. if (XmlUtilities.IsTextLevel(ourChild, theirChild, null)) { if (merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin) { merger.ConflictOccurred(new XmlTextBothAddedTextConflict(ourChild.Name, ourChild, theirChild, mergeSituation, mergeStrategyForChild, mergeSituation.AlphaUserId)); } else { merger.ConflictOccurred(new XmlTextBothAddedTextConflict(theirChild.Name, theirChild, ourChild, mergeSituation, mergeStrategyForChild, mergeSituation.BetaUserId)); XmlUtilities.ReplaceOursWithTheirs(ours, ref ourChild, theirChild); } } else { if (merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin) { var ourChildClone = MakeClone(ourChild); var theirChildClone = MakeClone(theirChild); if (XmlUtilities.AreXmlElementsEqual(ourChildClone, theirChildClone)) { merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(pathToFileInRepository, ourChild)); var ourChildReplacement = ourChild; merger.MergeInner(ours, ref ourChildReplacement, theirChild, null); if (!ReferenceEquals(ourChild, ourChildReplacement)) { XmlUtilities.ReplaceOursWithTheirs(ours, ref ourChild, ourChildReplacement); } } else { merger.ConflictOccurred(new BothAddedMainElementButWithDifferentContentConflict(ourChild.Name, ourChild, theirChild, mergeSituation, mergeStrategyForChild, mergeSituation.AlphaUserId)); } } else { var ourChildClone = MakeClone(ourChild); var theirChildClone = MakeClone(theirChild); if (XmlUtilities.AreXmlElementsEqual(ourChildClone, theirChildClone)) { merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(pathToFileInRepository, theirChild)); var ourChildReplacement = ourChild; merger.MergeInner(ours, ref ourChildReplacement, theirChild, null); if (!ReferenceEquals(ourChild, ourChildReplacement)) { XmlUtilities.ReplaceOursWithTheirs(ours, ref ourChild, ourChildReplacement); } } else { merger.ConflictOccurred(new BothAddedMainElementButWithDifferentContentConflict(theirChild.Name, theirChild, ourChild, mergeSituation, mergeStrategyForChild, mergeSituation.BetaUserId)); XmlUtilities.ReplaceOursWithTheirs(ours, ref ourChild, theirChild); } } } return(ours); }
private static XmlNode HandleCaseOfNoAncestor(XmlMerger merger, XmlNode ours, XmlNode theirs) { var mainNodeStrategy = merger.MergeStrategies.GetElementStrategy(ours ?? theirs); var mergeSituation = merger.MergeSituation; var pathToFileInRepository = mergeSituation.PathToFileInRepository; if (ours == null) { // They added, we did nothing. // Route tested, but the MergeChildrenMethod adds the change report for us. // So, theory has it one can't get here from any normal place. // But, keep it, in case MergeChildrenMethod gets 'fixed'. merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(pathToFileInRepository, theirs)); return(theirs); } if (theirs == null) { // We added, they did nothing. // Route tested. merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(pathToFileInRepository, ours)); return(ours); } // Both added the special containing node. // Remove children nodes to see if main containing nodes are the same. if (XmlUtilities.AreXmlElementsEqual(ours, theirs)) { // Route tested. // Same content. merger.EventListener.ChangeOccurred(new XmlBothAddedSameChangeReport(pathToFileInRepository, ours)); } else { // Different content. var ourChild = GetElementChildren(ours).FirstOrDefault(); var theirChild = GetElementChildren(theirs).FirstOrDefault(); var ourClone = MakeClone(ours); var theirClone = MakeClone(theirs); if (XmlUtilities.AreXmlElementsEqual(ourClone, theirClone)) { // new main elements are the same, but not the contained child merger.EventListener.ChangeOccurred(new XmlBothAddedSameChangeReport(pathToFileInRepository, ourClone)); if (ourChild == null && theirChild == null) { return(ours); // Nobody added the child node. } if (ourChild == null) { merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(pathToFileInRepository, theirChild)); // ReSharper disable once PossibleNullReferenceException -- if ours.OwnerDocument is null, we have other problems ours.AppendChild(ours.OwnerDocument.ImportNode(theirChild, true)); return(ours); } if (theirChild == null) { merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(pathToFileInRepository, ourChild)); return(ours); } // both children exist, but are different. var mergeStrategyForChild = merger.MergeStrategies.GetElementStrategy(ourChild); if (merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin) { // Do the clone thing on the two child nodes to see if the diffs are in the child or lower down. var ourChildClone = MakeClone(ourChild); var theirChildClone = MakeClone(theirChild); if (XmlUtilities.AreXmlElementsEqual(ourChildClone, theirChildClone)) { var ourChildReplacement = ourChild; merger.MergeInner(ours, ref ourChildReplacement, theirChild, null); if (!ReferenceEquals(ourChild, ourChildReplacement)) { XmlUtilities.ReplaceOursWithTheirs(ours, ref ourChild, ourChildReplacement); } } else { merger.ConflictOccurred(new BothAddedMainElementButWithDifferentContentConflict(ourChild.Name, ourChild, theirChild, mergeSituation, mergeStrategyForChild, mergeSituation.AlphaUserId)); } } else { // Do the clone thing on the two child nodes to see if the diffs are in the child or lower down. var ourChildClone = MakeClone(ourChild); var theirChildClone = MakeClone(theirChild); if (XmlUtilities.AreXmlElementsEqual(ourChildClone, theirChildClone)) { var ourChildReplacement = ourChild; merger.MergeInner(ours, ref ourChildReplacement, theirChild, null); if (!ReferenceEquals(ourChild, ourChildReplacement)) { XmlUtilities.ReplaceOursWithTheirs(ours, ref ourChild, ourChildReplacement); } } else { merger.ConflictOccurred(new BothAddedMainElementButWithDifferentContentConflict(ourChild.Name, ourChild, theirChild, mergeSituation, mergeStrategyForChild, mergeSituation.AlphaUserId)); XmlUtilities.ReplaceOursWithTheirs(ours, ref ourChild, theirChild); } } return(ours); } // Main containing element is not the same. Not to worry about child if (merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin) { merger.ConflictOccurred(new BothAddedMainElementButWithDifferentContentConflict(ours.Name, ours, theirs, mergeSituation, mainNodeStrategy, mergeSituation.AlphaUserId)); } else { merger.ConflictOccurred(new XmlTextBothAddedTextConflict(theirs.Name, theirs, ours, mergeSituation, mainNodeStrategy, mergeSituation.BetaUserId)); XmlUtilities.ReplaceOursWithTheirs(ours.ParentNode, ref ours, theirs); } } return(ours); }
private static XmlNode Run(XmlMerger merger, XmlNode ours, XmlNode theirs, XmlNode ancestor) { if (ours == null && theirs == null && ancestor == null) { return(null); } if (ancestor == null) { return(HandleCaseOfNoAncestor(merger, ours, theirs)); } // ancestor is not null at this point. var mergeSituation = merger.MergeSituation; var pathToFileInRepository = mergeSituation.PathToFileInRepository; if (ours == null && theirs == null) { // We both deleted main node. // Route tested, but the MergeChildrenMethod adds the change report for us. merger.EventListener.ChangeOccurred(new XmlBothDeletionChangeReport(pathToFileInRepository, ancestor)); return(null); } if (ours == null) { return(HandleOursNotPresent(merger, ancestor, theirs)); } if (theirs == null) { return(HandleTheirsNotPresent(merger, ancestor, ours)); } // End of checking main parent node. // ancestor, ours, and theirs all exist here. var ourChild = GetElementChildren(ours).FirstOrDefault(); var theirChild = GetElementChildren(theirs).FirstOrDefault(); var ancestorChild = GetElementChildren(ancestor).FirstOrDefault(); if (ourChild == null && theirChild == null && ancestorChild == null) { return(ours); // All three are childless. } if (ancestorChild == null) { return(HandleCaseOfNoAncestorChild(merger, ours, ourChild, theirChild)); } var mergeStrategyForChild = merger.MergeStrategies.GetElementStrategy(ancestorChild); if (ourChild == null) { return(HandleOurChildNotPresent(merger, ours, ancestor, theirChild, pathToFileInRepository, ancestorChild, mergeSituation, mergeStrategyForChild)); } if (theirChild == null) { return(HandleTheirChildNotPresent(merger, ours, ancestor, ancestorChild, ourChild, mergeSituation, mergeStrategyForChild, pathToFileInRepository)); } // ancestorChild, ourChild, and theirChild all exist. // But, it could be that we or they deleted and added something. // Check for edit vs delete+add, or there can be two items in ours, which is not legal. var match = mergeStrategyForChild.MergePartnerFinder.GetNodeToMerge(ourChild, ancestor, SetFromChildren.Get(ancestor)); if (match == null) { // we deleted it and added a new one. if (XmlUtilities.AreXmlElementsEqual(theirChild, ancestorChild)) { // Our delete+add wins, since they did nothing. merger.EventListener.ChangeOccurred(new XmlDeletionChangeReport(pathToFileInRepository, ancestor, ancestorChild)); merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(pathToFileInRepository, ourChild)); return(ours); } // They edited old one, so they win over our delete+add. merger.ConflictOccurred(new RemovedVsEditedElementConflict(theirChild.Name, theirChild, null, ancestorChild, mergeSituation, mergeStrategyForChild, mergeSituation.BetaUserId)); XmlUtilities.ReplaceOursWithTheirs(ours, ref ourChild, theirChild); return(ours); } match = mergeStrategyForChild.MergePartnerFinder.GetNodeToMerge(theirChild, ancestor, SetFromChildren.Get(ancestor)); if (match == null) { // they deleted it and added a new one. if (XmlUtilities.AreXmlElementsEqual(ourChild, ancestorChild)) { // Their delete+add wins, since we did nothing. merger.EventListener.ChangeOccurred(new XmlDeletionChangeReport(pathToFileInRepository, ancestor, ancestorChild)); merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(pathToFileInRepository, theirChild)); XmlUtilities.ReplaceOursWithTheirs(ours, ref ourChild, theirChild); return(ours); } // We edited old one, so we win over their delete+add. merger.ConflictOccurred(new RemovedVsEditedElementConflict(ourChild.Name, ourChild, null, ancestorChild, mergeSituation, mergeStrategyForChild, mergeSituation.AlphaUserId)); return(ours); } merger.MergeInner(ours, ref ourChild, theirChild, ancestorChild); // Route tested. (UsingWith_NumberOfChildrenAllowed_ZeroOrOne_DoesNotThrowWhenParentHasOneChildNode) return(ours); }
protected override void AddMergeStrategies(XmlMerger m) { m.MergeStrategies.ElementStrategies.Add("a", ElementStrategy.CreateForKeyedElementInList("key")); m.MergeStrategies.ElementStrategies.Add("b", ElementStrategy.CreateForKeyedElementInList("key")); m.MergeStrategies.ElementStrategies.Add("c", ElementStrategy.CreateForKeyedElementInList("key")); m.MergeStrategies.ElementStrategies.Add("d", ElementStrategy.CreateForKeyedElementInList("key")); }
public static void Run(XmlMerger merger, ElementStrategy strategy, ref XmlNode ours, XmlNode theirs, XmlNode ancestor) { // All routes tested in this method. Guard.AgainstNull(merger, "merger"); // Route tested. Guard.AgainstNull(strategy, "strategy"); // Route tested. if (ours == null && theirs == null && ancestor == null) { throw new ArgumentNullException(); // Route tested. } if (XmlUtilities.IsTextLevel(ours, theirs, ancestor)) { // Route tested. new MergeTextNodesMethod(merger, merger.MergeStrategies.GetElementStrategy(ours ?? theirs ?? ancestor), new HashSet <XmlNode>(), ref ours, new List <XmlNode>(), theirs, new List <XmlNode>(), ancestor, new List <XmlNode>()).Run(); return; } List <XmlNode> ourChildren; List <XmlNode> theirChildren; List <XmlNode> ancestorChildren; switch (strategy.NumberOfChildren) { default: throw new InvalidOperationException("Using strategy with NumberOfChildren property of NumberOfChildrenAllowed.ZeroOrMore is not legal."); // Route tested. case NumberOfChildrenAllowed.Zero: ourChildren = GetElementChildren(ours).ToList(); if (ourChildren.Any()) { throw new InvalidOperationException("Using strategy with NumberOfChildren property of NumberOfChildrenAllowed.Zero is not legal, when there are child element nodes."); // Route tested. } theirChildren = GetElementChildren(theirs).ToList(); if (theirChildren.Any()) { throw new InvalidOperationException("Using strategy with NumberOfChildren property of NumberOfChildrenAllowed.Zero is not legal, when there are child element nodes."); // Route tested. } ancestorChildren = GetElementChildren(ancestor).ToList(); if (ancestorChildren.Any()) { throw new InvalidOperationException("Using strategy with NumberOfChildren property of NumberOfChildrenAllowed.Zero is not legal, when there are child element nodes."); // Route tested. } // Don't merge deeper than merging the attributes, since there aren't supposed to be any children. // Already done by caller MergeXmlAttributesService.MergeAttributes(merger, ref ours, theirs, ancestor); // Route tested. break; case NumberOfChildrenAllowed.ZeroOrOne: ourChildren = GetElementChildren(ours).ToList(); if (ourChildren.Count > 1) { throw new InvalidOperationException("Using strategy with NumberOfChildren property of NumberOfChildrenAllowed.ZeroOrOne is not legal, when there are multiple child nodes."); // Route tested. } theirChildren = GetElementChildren(theirs).ToList(); if (theirChildren.Count > 1) { throw new InvalidOperationException("Using strategy with NumberOfChildren property of NumberOfChildrenAllowed.ZeroOrOne is not legal, when there are multiple child nodes."); // Route tested. } ancestorChildren = GetElementChildren(ancestor).ToList(); if (ancestorChildren.Count > 1) { throw new InvalidOperationException("Using strategy with NumberOfChildren property of NumberOfChildrenAllowed.ZeroOrOne is not legal, when there are child element nodes."); // Route tested. } // Already done by caller MergeXmlAttributesService.MergeAttributes(merger, ref ours, theirs, ancestor); if (!ourChildren.Any() && !theirChildren.Any() && ancestor != null) { return; // Route tested. } // The return value of Run may be the original 'ours', a replacement for it, or null. ours = Run(merger, ours, theirs, ancestor); // Route tested. break; } }
public void DeleteAtomicElementVsModifyHasConflict() { const string commonAncestor = @"<Lexicon> <LexEntry guid='ffdc58c9-5cc3-469f-9118-9f18c0138d02'> <MorphoSyntaxAnalyses> <MoStemMsa guid='33adabe9-a02e-42cb-b942-277a7be5c841'> <PartOfSpeech> <objsur guid='e72dbc59-e93f-4df2-b6bd-39a53e331201' t='r' /> </PartOfSpeech> </MoStemMsa> </MorphoSyntaxAnalyses> <Senses/> </LexEntry> </Lexicon>"; const string matthew = @"<Lexicon> <LexEntry guid='ffdc58c9-5cc3-469f-9118-9f18c0138d02'> <MorphoSyntaxAnalyses> <MoStemMsa guid='33adabe9-a02e-42cb-b942-277a7be5c841'> <PartOfSpeech> <objsur guid='f92dbc59-e93f-4df2-b6bd-39a53e331201' t='r' /> </PartOfSpeech> </MoStemMsa> </MorphoSyntaxAnalyses> <Senses/> </LexEntry> </Lexicon>"; const string lee = @"<Lexicon> <LexEntry guid='ffdc58c9-5cc3-469f-9118-9f18c0138d02'> <MorphoSyntaxAnalyses> <MoStemMsa guid='33adabe9-a02e-42cb-b942-277a7be5c841'> <PartOfSpeech /> </MoStemMsa> </MorphoSyntaxAnalyses> <Senses/> </LexEntry> </Lexicon>"; var listener = new ListenerForUnitTests(); var merger = new XmlMerger(new NullMergeSituation()) { EventListener = listener }; merger.MergeStrategies.SetStrategy("Lexicon", ElementStrategy.CreateSingletonElement()); var strat = ElementStrategy.CreateForKeyedElement("guid", false); strat.AttributesToIgnoreForMerging.Add("guid"); merger.MergeStrategies.SetStrategy("LexEntry", strat); strat = ElementStrategy.CreateSingletonElement(); strat.NumberOfChildren = NumberOfChildrenAllowed.ZeroOrOne; merger.MergeStrategies.SetStrategy("MorphoSyntaxAnalyses", strat); strat = ElementStrategy.CreateForKeyedElement("guid", false); strat.AttributesToIgnoreForMerging.Add("guid"); merger.MergeStrategies.SetStrategy("MoStemMsa", strat); strat = ElementStrategy.CreateSingletonElement(); strat.NumberOfChildren = NumberOfChildrenAllowed.ZeroOrOne; merger.MergeStrategies.SetStrategy("PartOfSpeech", strat); strat = ElementStrategy.CreateSingletonElement(); strat.IsAtomic = true; merger.MergeStrategies.SetStrategy("objsur", strat); XmlTestHelper.DoMerge(merger.MergeStrategies, merger.MergeSituation, commonAncestor, lee, matthew, new[] { "Lexicon/LexEntry/MorphoSyntaxAnalyses/MoStemMsa/PartOfSpeech/objsur[@guid='f92dbc59-e93f-4df2-b6bd-39a53e331201']" }, null, 1, new List<Type> { typeof(RemovedVsEditedElementConflict) }, 0, new List<Type>()); }
public void AncestorIsEmptyFileAndWeBothAddedDifferentContentWithTheyWinHasOneConflictReport() { const string data = @"<?xml version='1.0' encoding='utf-8'?> <data />"; using (var ancestor = new TempFile()) using (var ours = new TempFile()) using (var theirs = new TempFile()) { File.WriteAllText(ancestor.Path, ""); File.WriteAllText(ours.Path, data.Replace("<data />", "<data>our addition</data>")); File.WriteAllText(theirs.Path, data.Replace("<data />", "<data>their addition</data>")); var listener = new ListenerForUnitTests(); var merger = new XmlMerger(new NullMergeSituationTheyWin()) { EventListener = listener }; var result = merger.MergeFiles(ours.Path, theirs.Path, ancestor.Path); Assert.IsNotNull(result); listener.AssertExpectedChangesCount(0); listener.AssertExpectedConflictCount(1); listener.AssertFirstConflictType<BothAddedMainElementButWithDifferentContentConflict>(); Assert.IsTrue(result.MergedNode.OuterXml.Contains("their addition")); } }
private static XmlMerger GetMerger(out ListenerForUnitTests listener, bool isAtomic) { var elementStrategy = new ElementStrategy(false) { IsAtomic = isAtomic }; var merger = new XmlMerger(new NullMergeSituation()); merger.MergeStrategies.SetStrategy("topatomic", elementStrategy); listener = new ListenerForUnitTests(); merger.EventListener = listener; return merger; }
public void AncestorIsNotXmlThrows() { const string data = @"<?xml version='1.0' encoding='utf-8'?> <data />"; using (var ancestor = new TempFile()) using (var ours = new TempFile()) using (var theirs = new TempFile()) { File.WriteAllText(ancestor.Path, "Not xml stuff."); File.WriteAllText(ours.Path, data); File.WriteAllText(theirs.Path, data); var merger = new XmlMerger(new NullMergeSituation()); Assert.Throws<XmlException>(() => merger.MergeFiles(ours.Path, theirs.Path, ancestor.Path)); } }
/// <summary> /// Do a 3-file merge, placing the result over the "ours" file and returning an error status /// </summary> /// <remarks>Implementations can exit with an exception, which the caller will catch and deal with. /// The must not have any UI, no interaction with the user.</remarks> public void Do3WayMerge(MergeOrder mergeOrder) { if (mergeOrder == null) throw new ArgumentNullException("mergeOrder"); bool addedCollationAttr; PreMergeFile(mergeOrder, out addedCollationAttr); var merger = new XmlMerger(mergeOrder.MergeSituation); SetupElementStrategies(merger); merger.EventListener = mergeOrder.EventListener; XmlMergeService.RemoveAmbiguousChildNodes = true; var result = merger.MergeFiles(mergeOrder.pathToOurs, mergeOrder.pathToTheirs, mergeOrder.pathToCommonAncestor); using (var writer = XmlWriter.Create(mergeOrder.pathToOurs, CanonicalXmlSettings.CreateXmlWriterSettings())) { var nameSpaceManager = new XmlNamespaceManager(new NameTable()); nameSpaceManager.AddNamespace("palaso", "urn://palaso.org/ldmlExtensions/v1"); nameSpaceManager.AddNamespace("palaso2", "urn://palaso.org/ldmlExtensions/v2"); nameSpaceManager.AddNamespace("fw", "urn://fieldworks.sil.org/ldmlExtensions/v1"); nameSpaceManager.AddNamespace("sil", "urn://www.sil.org/ldml/0.1"); var readerSettings = CanonicalXmlSettings.CreateXmlReaderSettings(ConformanceLevel.Auto); readerSettings.NameTable = nameSpaceManager.NameTable; readerSettings.XmlResolver = null; readerSettings.ProhibitDtd = false; if (addedCollationAttr) { // Remove the optional 'key' attr we added. var adjustedCollation = result.MergedNode.SelectSingleNode("collations") .SelectNodes("collation") .Cast<XmlNode>().FirstOrDefault(collation => collation.Attributes["type"].Value == "standard"); if (adjustedCollation != null) { adjustedCollation.Attributes.Remove(adjustedCollation.Attributes["type"]); } } using (var nodeReader = XmlReader.Create(new MemoryStream(Encoding.UTF8.GetBytes(result.MergedNode.OuterXml)), readerSettings)) { writer.WriteNode(nodeReader, false); } } }
/// <summary> /// "Merge" element if it is 'atomic' and return true. Otherwise, do nothing and return false. /// </summary> /// <remarks> /// <c>ours</c> may be changed to <c>theirs</c> if <c>ours</c> is null and <c>theirs</c> is not null. /// </remarks> internal static void Run(XmlMerger merger, XmlNode ourParent, ref XmlNode ours, XmlNode theirs, XmlNode commonAncestor) { // Route tested. Guard.AgainstNull(merger, nameof(merger)); if (ours == null && theirs == null && commonAncestor == null) { throw new ArgumentNullException(); // Route tested. } // One or two of the elements may be null. // If commonAncestor is null and one of the others is null, then the other one added a new element. // if ours and theirs are both null, they each deleted the element. var nodeForStrategy = ours ?? (theirs ?? commonAncestor); // Here is where we sort out the new 'IsAtomic' business of ElementStrategy. // 1. Fetch the relevant ElementStrategy var elementStrategy = merger.MergeStrategies.GetElementStrategy(nodeForStrategy); if (!elementStrategy.IsAtomic) { throw new InvalidOperationException("This method class only handles elements that are atomic (basically binary type data that can't really be merged.)"); } if (commonAncestor == null) { if (ours == null) { // They can't all be null, or there would have been an exception thrown, above. //if (theirs == null) //{ // // Nobody did anything. // return true; //} // They seem to have added a new one. // Route tested (x2). merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(merger.MergeSituation.PathToFileInRepository, theirs)); XmlUtilities.ReplaceOursWithTheirs(ourParent, ref ours, theirs); // They added it. } else { // Ours is not null. if (theirs != null) { // Neither is theirs. if (XmlUtilities.AreXmlElementsEqual(ours, theirs)) { // Both added the same thing. // Route tested (x2). merger.EventListener.ChangeOccurred(new BothChangedAtomicElementReport(merger.MergeSituation.PathToFileInRepository, ours)); } else { // Both added, but not the same thing. if (merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin) { // Route tested. merger.ConflictOccurred(new BothEditedTheSameAtomicElement(ours.Name, ours, theirs, null, merger.MergeSituation, elementStrategy, merger.MergeSituation.AlphaUserId)); } else { // Route tested. merger.ConflictOccurred(new BothEditedTheSameAtomicElement(ours.Name, ours, theirs, null, merger.MergeSituation, elementStrategy, merger.MergeSituation.BetaUserId)); XmlUtilities.ReplaceOursWithTheirs(ourParent, ref ours, theirs); } } } else { // We added. They are still null. // Route tested (x2). merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(merger.MergeSituation.PathToFileInRepository, ours)); } } return; // Routed used (x2). } // commonAncestor != null from here on out. if (ours == null && theirs == null) { // No problemo, since both deleted it. // Route tested (x2). merger.EventListener.ChangeOccurred(new XmlBothDeletionChangeReport(merger.MergeSituation.PathToFileInRepository, commonAncestor)); return; } // 2A1. Compare 'ours' with 'theirs'. // If one is null, keep the other one, but only if it was edited. var theirsAndCommonAreEqual = theirs != null && XmlUtilities.AreXmlElementsEqual(theirs, commonAncestor); if (ours == null && !theirsAndCommonAreEqual) { // We deleted, they edited, so keep theirs under the least loss principle. // Route tested (x2 WeWin & !WeWin). merger.ConflictOccurred(new RemovedVsEditedElementConflict(theirs.Name, null, theirs, commonAncestor, merger.MergeSituation, elementStrategy, merger.MergeSituation.BetaUserId)); XmlUtilities.ReplaceOursWithTheirs(ourParent, ref ours, theirs); return; } var oursAndCommonAreEqual = ours != null && XmlUtilities.AreXmlElementsEqual(ours, commonAncestor); if (theirs == null && !oursAndCommonAreEqual) { // We edited, they deleted, so keep ours under the least loss principle. Debug.Assert(ours != null, "We shoudn't come here if ours is also null...both deleted is handled elsewhere"); // Route tested (x2 WeWin & !WeWin) merger.ConflictOccurred(new EditedVsRemovedElementConflict(ours.Name, ours, null, commonAncestor, merger.MergeSituation, elementStrategy, merger.MergeSituation.AlphaUserId)); return; } var oursAndTheirsAreEqual = ours != null && theirs != null && XmlUtilities.AreXmlElementsEqual(ours, theirs); if (oursAndTheirsAreEqual && !oursAndCommonAreEqual) { // Both made same changes. // Route tested (x2). merger.EventListener.ChangeOccurred(new BothChangedAtomicElementReport(merger.MergeSituation.PathToFileInRepository, ours)); return; } if (!oursAndTheirsAreEqual) { if (ours == null) { // We deleted. They did nothing. Debug.Assert(theirs != null, "both deleted should be handled before this"); Debug.Assert(theirsAndCommonAreEqual, "we deleted and they edited should be handled before this"); // leave ours null, preserving the deletion. // We could plausibly call ChangeOccurred with an XmlDeletionChangeReport, but we are phasing those out. return; // Route tested } if (theirs == null) { // They deleted. We did nothing. Debug.Assert(oursAndCommonAreEqual, "we edited and they deleted should be handled before this"); // Let the delete win. ours = null; // We could plausibly call ChangeOccurred with an XmlDeletionChangeReport, but we are phasing those out. return; // Route tested } // Compare with common ancestor to see who made the change, if only one made it.\ if (!oursAndCommonAreEqual && theirsAndCommonAreEqual) { // We edited it. They did nothing. // Route tested (x2). merger.EventListener.ChangeOccurred(new XmlChangedRecordReport(null, null, ours.ParentNode, ours)); } else if (!theirsAndCommonAreEqual && oursAndCommonAreEqual) { // They edited it. We did nothing. // Route tested (x2). merger.EventListener.ChangeOccurred(new XmlChangedRecordReport(null, null, theirs.ParentNode, theirs)); XmlUtilities.ReplaceOursWithTheirs(ourParent, ref ours, theirs); } else { // Both edited. // 2A1b. If different, then report a conflict and then stop. // Route tested (x2 WeWin & !WeWin). merger.ConflictOccurred(merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin ? new BothEditedTheSameAtomicElement(ours.Name, ours, theirs, commonAncestor, merger.MergeSituation, elementStrategy, merger.MergeSituation.AlphaUserId) : new BothEditedTheSameAtomicElement(theirs.Name, ours, theirs, commonAncestor, merger.MergeSituation, elementStrategy, merger.MergeSituation.BetaUserId)); if (merger.MergeSituation.ConflictHandlingMode != MergeOrder.ConflictHandlingModeChoices.WeWin) { XmlUtilities.ReplaceOursWithTheirs(ourParent, ref ours, theirs); } } } // No changes. // Route tested (x2). }