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); }
public void XmlMergerDoesNotCallImmutableElementMergeServiceForMutableElement() { var listener = new ListenerForUnitTests(); var merger = new XmlMerger(new NullMergeSituation()) { EventListener = listener }; var elemStrat = new ElementStrategy(false) { IsImmutable = false }; merger.MergeStrategies.SetStrategy("MutableElemment", elemStrat); var ancestor = CreateNode("<MutableElemment attr='originalvalue' />"); var ours = CreateNode("<MutableElemment attr='ourvalue' />"); var theirs = CreateNode("<MutableElemment attr='originalvalue' />"); merger.MergeInner(ref ours, theirs, ancestor); Assert.AreNotSame(ancestor, ours); Assert.AreEqual("<MutableElemment attr=\"ourvalue\" />", ours.OuterXml); listener.AssertExpectedChangesCount(1); listener.AssertFirstChangeType<XmlAttributeChangedReport>(); listener.AssertExpectedConflictCount(0); }
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)); ours.AppendChild(theirChild); 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; }
public void XmlMergerCallsImmutableElementMergeServiceForImmutableElement() { var listener = new ListenerForUnitTests(); var merger = new XmlMerger(new NullMergeSituation()) { EventListener = listener }; var elemStrat = new ElementStrategy(false) { IsImmutable = true }; merger.MergeStrategies.SetStrategy("ImmutableElemment", elemStrat); var ancestor = CreateNode("<ImmutableElemment attr='originalvalue' />"); var ours = CreateNode("<ImmutableElemment attr='ourvalue' />"); var theirs = CreateNode("<ImmutableElemment attr='theirvalue' />"); merger.MergeInner(ref ours, theirs, ancestor); Assert.AreSame(ancestor, ours); Assert.AreEqual("<ImmutableElemment attr=\"originalvalue\" />", ancestor.OuterXml); listener.AssertExpectedChangesCount(0); listener.AssertExpectedConflictCount(0); }
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; }
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)); ours.AppendChild(theirChild); 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 Run(XmlMerger merger, XmlNode ours, XmlNode theirs, XmlNode ancestor) { if (ours == null && theirs == null && ancestor == null) return null; var ourChild = GetElementChildren(ours).FirstOrDefault(); var theirChild = GetElementChildren(theirs).FirstOrDefault(); var ancestorChild = GetElementChildren(ancestor).FirstOrDefault(); XmlNode ourReplacementChild; if (ancestor == null) { 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(merger.MergeSituation.PathToFileInRepository, theirs)); return theirs; } if (theirs == null) { // We added, they did nothing. // Route tested. merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(merger.MergeSituation.PathToFileInRepository, ours)); return ours; } // Both added the special containing node. // Route tested. merger.EventListener.ChangeOccurred(new XmlBothAddedSameChangeReport(merger.MergeSituation.PathToFileInRepository, ours)); if (ourChild == null && theirChild == null && ancestorChild == null) return null; // Route tested. if (ourChild == null && theirChild != null) { // Route tested. merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(merger.MergeSituation.PathToFileInRepository, theirChild)); ours.AppendChild(theirChild); return ours; } if (theirChild == null) { // Route tested. merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(merger.MergeSituation.PathToFileInRepository, ourChild)); return ours; } // Both ourChild and theirChild exist, and may or may not be the same. var mergeStrategy = merger.MergeStrategies.GetElementStrategy(ourChild); var match = mergeStrategy.MergePartnerFinder.GetNodeToMerge(ourChild, theirs, SetFromChildren.Get(theirs)); if (match == null) { XmlNode winner; string winnerId; XmlNode loser; // Work up conflict report (for both we and they win options), since the users didn't add the same thing. if (merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin) { winner = ourChild; winnerId = merger.MergeSituation.AlphaUserId; loser = theirChild; } else { winner = theirChild; winnerId = merger.MergeSituation.BetaUserId; loser = ourChild; ours = theirs; } // Route tested. merger.ConflictOccurred(new BothAddedMainElementButWithDifferentContentConflict(winner.Name, winner, loser, merger.MergeSituation, mergeStrategy, winnerId)); return ours; } // Matched nodes, as far as that goes. But, are they the same or not? if (XmlUtilities.AreXmlElementsEqual(ourChild, theirChild)) { // Both added the same thing. // Route tested. merger.EventListener.ChangeOccurred(new XmlBothAddedSameChangeReport(merger.MergeSituation.PathToFileInRepository, ourChild)); return ours; } // Move on down and merge them. // Both messed with the inner stuff, but not the same way. // Route tested. ourReplacementChild = ourChild; merger.MergeInner(ref ourReplacementChild, theirChild, ancestorChild); if (!ReferenceEquals(ourChild, ourReplacementChild)) ours.ReplaceChild(ours.OwnerDocument.ImportNode(ourReplacementChild, true), ourChild); return ours; } // ancestor is not null at this point. // Check the inner nodes 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(merger.MergeSituation.PathToFileInRepository, ancestor)); return null; } if (ours == null) { // We deleted it. // Route tested, but the MergeChildrenMethod adds the change report for us. merger.EventListener.ChangeOccurred(new XmlDeletionChangeReport(merger.MergeSituation.PathToFileInRepository, ancestor, theirs)); ours = theirs; return ours; } if (theirs == null) { // They deleted it. // Route tested, but the MergeChildrenMethod adds the change report for us. merger.EventListener.ChangeOccurred(new XmlDeletionChangeReport(merger.MergeSituation.PathToFileInRepository, ancestor, ours)); return ours; } // ours, theirs, and ancestor all exist here. new MergeChildrenMethod(ours, theirs, ancestor, merger).Run(); // Route tested. (UsingWith_NumberOfChildrenAllowed_ZeroOrOne_DoesNotThrowWhenParentHasOneChildNode) return ours; }
/// <summary> /// Merges the children into the "ours" xmlnode, and uses the merger's listener to publish what happened /// </summary> public void Run() { // Pre-process three nodes to handle duplicates in each node, But only if finder is one of these: // FindFirstElementWithSameName, FindByKeyAttribute, or FindByMultipleKeyAttributes. // Initialise lists of keepers to current ancestorChildren, ourChildren, theirChildren CopyChildrenToList(_ancestor, _childrenOfAncestorKeepers); CopyChildrenToList(_ours, _childrenOfOurKeepers); CopyChildrenToList(_theirs, _childrenOfTheirKeepers); // Deal with deletions. DoDeletions(); // Allow the most promising parent's strategy to determine whether order is significant for children. // The default configuration of ElementStrategy uses an AskChildren strategy, which means individual // children determine whether their order is significant. var parentNode = _ours ?? _theirs ?? _ancestor; var parentStrategy = _merger.MergeStrategies.GetElementStrategy(parentNode); var parentOrder = parentStrategy == null ? ChildOrder.AskChildren : parentStrategy.ChildOrderPolicy.OrderSignificance(parentNode); // Determine if child order is important, we shouldn't create reordering conflicts if the order of children is unimportant var childOrderMatters = parentOrder == ChildOrder.Significant; if (parentOrder == ChildOrder.AskChildren && parentNode.HasChildNodes) { var childStrategy = _merger.MergeStrategies.GetElementStrategy(parentNode.FirstChild); if (childStrategy != null && childStrategy.OrderIsRelevant) { childOrderMatters = true; } } ChildOrderer oursOrderer = new ChildOrderer(_childrenOfOurKeepers, _childrenOfTheirKeepers, MakeCorrespondences(_childrenOfOurKeepers, _childrenOfTheirKeepers, _theirs), _merger, parentOrder); ChildOrderer resultOrderer = oursOrderer; // default if (oursOrderer.OrderIsDifferent) { // The order of the two lists is not consistent. Compare with ancestor to see who changed. ChildOrderer ancestorOursOrderer = new ChildOrderer(_childrenOfAncestorKeepers, _childrenOfOurKeepers, MakeCorrespondences(_childrenOfAncestorKeepers, _childrenOfOurKeepers, _ours), _merger, parentOrder); ChildOrderer ancestorTheirsOrderer = new ChildOrderer(_childrenOfAncestorKeepers, _childrenOfTheirKeepers, MakeCorrespondences(_childrenOfAncestorKeepers, _childrenOfTheirKeepers, _theirs), _merger, parentOrder); if (ancestorTheirsOrderer.OrderIsDifferent) { if (ancestorOursOrderer.OrderIsDifferent && childOrderMatters) { // stick with our orderer (we win), but report conflict. // Route tested (XmlMergerTests). _merger.ConflictOccurred(new BothReorderedElementConflict(_ours.Name, _ours, _theirs, _ancestor, _merger.MergeSituation, _merger.MergeStrategies.GetElementStrategy(_ours), _merger.MergeSituation.AlphaUserId)); } else { // only they re-ordered; take their order as primary. resultOrderer = new ChildOrderer(_childrenOfTheirKeepers, _childrenOfOurKeepers, MakeCorrespondences(_childrenOfTheirKeepers, _childrenOfOurKeepers, _ours), _merger, parentOrder); } } else if (!ancestorOursOrderer.OrderIsDifferent) { // our order is different from theirs, but neither is different from the ancestor. // the only way this can be true is if both inserted the same thing, but in // different places. Stick with our orderer (we win), but report conflict. // Route tested (XmlMergerTests). _merger.ConflictOccurred(new BothInsertedAtDifferentPlaceConflict(_ours.Name, _ours, _theirs, _ancestor, _merger.MergeSituation, _merger.MergeStrategies.GetElementStrategy(_ours), _merger.MergeSituation.AlphaUserId)); } // otherwise we re-ordered, but they didn't. That's not a problem, unless it resulted // in inconsistency or ambiguity in other stuff that someone added. } if (!resultOrderer.OrderIsConsistent || (resultOrderer.OrderIsDifferent && resultOrderer.OrderIsAmbiguous)) { // Route tested (XmlMergerTests[x2]). _merger.ConflictOccurred(new AmbiguousInsertReorderConflict(_ours.Name, _ours, _theirs, _ancestor, _merger.MergeSituation, _merger.MergeStrategies.GetElementStrategy(_ours), _merger.MergeSituation.AlphaUserId)); } else if (resultOrderer.OrderIsAmbiguous) { // Route tested (MergeChildrenMethodTests, XmlMergerTests). _merger.ConflictOccurred(new AmbiguousInsertConflict(_ours.Name, _ours, _theirs, _ancestor, _merger.MergeSituation, _merger.MergeStrategies.GetElementStrategy(_ours), _merger.MergeSituation.AlphaUserId)); } // Merge corresponding nodes. // 'resultsChildren' may contain nodes from either 'ours', 'theirs', or both, // as the 'resultsChildren' collection has been combined in the ordereing operation. List <XmlNode> resultsChildren = resultOrderer.GetResultList(); for (int i = 0; i < resultsChildren.Count; i++) { XmlNode ourChild = resultsChildren[i]; // The 'DoDeletions' method call, above, possibly called MergeTextNodesMethod.DoDeletions, // which may have added 'ourChild' to _skipInnerMergeFor, // as it did some fairly exotic work with full and partial deletions. // So, don't do those again here. if (_skipInnerMergeFor.Contains(ourChild)) { continue; } XmlNode theirChild; var ancestorChild = FindMatchingNode(ourChild, _ancestor, new HashSet <XmlNode>(_childrenOfAncestorKeepers)); if (resultOrderer.Correspondences.TryGetValue(ourChild, out theirChild) && !ChildrenAreSame(ourChild, theirChild)) { // Both 'ourChild' and 'theirChild exist. 'ancestorChild' may, or, may not, exist. // There's a corresponding node and it isn't the same as ours... // Route tested: MergeChildrenMethod_DiffOnlyTests. _merger.MergeInner(ref ourChild, theirChild, ancestorChild); resultsChildren[i] = ourChild; } else { // 'theirChild' may, or may not, exist. But if it does exist, it is the same as 'ourChild'. //Review JohnT (jh): Is this the correct interpretation? if (ancestorChild == null) { if (XmlUtilities.IsTextNodeContainer(ourChild) == TextNodeStatus.IsTextNodeContainer) // No, it hasn't. MergeTextNodesMethod has already added the addition report. { if (theirChild == null) { // Route tested: MergeChildrenMethod_DiffOnlyTests & XmlMergerTests. _merger.EventListener.ChangeOccurred(new XmlTextAddedReport(_merger.MergeSituation.PathToFileInRepository, ourChild)); // Route tested (x2). } else { _merger.EventListener.ChangeOccurred(new XmlTextBothAddedReport(_merger.MergeSituation.PathToFileInRepository, ourChild)); // Route tested } } else if (!(ourChild is XmlCharacterData)) { if (theirChild == null) { // Route tested (MergeChildrenMethodTests, XmlMergerTests). _merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(_merger.MergeSituation.PathToFileInRepository, ourChild)); } else { _merger.EventListener.ChangeOccurred(new XmlBothAddedSameChangeReport(_merger.MergeSituation.PathToFileInRepository, ourChild)); } } } else { // ancestorChild is not null. if (XmlUtilities.IsTextNodeContainer(ourChild) == TextNodeStatus.IsTextNodeContainer) { if (theirChild != null && !XmlUtilities.AreXmlElementsEqual(ourChild, ancestorChild)) { _merger.EventListener.ChangeOccurred(new XmlTextBothMadeSameChangeReport(_merger.MergeSituation.PathToFileInRepository, ourChild)); // Route tested } } else { _merger.MergeInner(ref ourChild, theirChild, ancestorChild); resultsChildren[i] = ourChild; } } } } // Plug results back into 'ours' for (int i = 0; i < resultsChildren.Count; i++) { XmlNode ourChild = resultsChildren[i]; while (_ours.ChildNodes.Count > i && ourChild != _ours.ChildNodes[i]) { _ours.RemoveChild(_ours.ChildNodes[i]); } if (_ours.ChildNodes.Count > i) { continue; // we found the exact node already present, leave it alone. } if (ourChild.ParentNode == _ours) { _ours.AppendChild(ourChild); } else { if (ourChild is XmlElement) { _ours.AppendChild(_ours.OwnerDocument.ImportNode(ourChild, true)); } else if (ourChild is XmlText) { _ours.AppendChild(_ours.OwnerDocument.CreateTextNode(ourChild.OuterXml)); } else { Debug.Fail("so far we only know how to copy elements and text nodes at this point"); } } } // Remove any leftovers. while (_ours.ChildNodes.Count > resultsChildren.Count) { _ours.RemoveChild(_ours.ChildNodes[resultsChildren.Count]); } }
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)); ours.ReplaceChild(ours.OwnerDocument.ImportNode(theirChild, true), ourChild); 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)); ours.ReplaceChild(ours.OwnerDocument.ImportNode(theirChild, true), ourChild); 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(ref ourChild, theirChild, ancestorChild); // Route tested. (UsingWith_NumberOfChildrenAllowed_ZeroOrOne_DoesNotThrowWhenParentHasOneChildNode) return(ours); }