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); }
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); }
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); }
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); }
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 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 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 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); }
/// <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). }
/// <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]); } }
/// <summary> /// Remove from ancestorKeepers any node that does not correspond to anything (both deleted) /// Remove from ancestorKeepers and theirKeepers any pair that correspond to each other but not to anything in ours. Report conflict (delete/edit) if pair not identical. /// Remove from ancestorKeepers and ourKeepers any pair that correspond to each other and are identical, but don't correspond to anything in theirs (they deleted) /// Report conflict (edit/delete) on any pair that correspond in ours and ancestor, but nothing in theirs, and that are NOT identical. (but keep them...we win) /// </summary> private void DoDeletions() { // loop over a copy of the list, since we may modify ancestorKeepers. List <XmlNode> loopSource = new List <XmlNode>(_childrenOfAncestorKeepers); var ourChildSet = new HashSet <XmlNode>(_ours == null ? new XmlNode[0] : _ours.ChildNodes.Cast <XmlNode>()); var theirChildSet = new HashSet <XmlNode>(_theirs == null ? new XmlNode[0] : _theirs.ChildNodes.Cast <XmlNode>()); foreach (XmlNode ancestorChild in loopSource) { ElementStrategy mergeStrategy = _merger.MergeStrategies.GetElementStrategy(ancestorChild); IFindNodeToMerge finder = mergeStrategy.MergePartnerFinder; XmlNode ourChild = finder.GetNodeToMerge(ancestorChild, _ours, ourChildSet); XmlNode theirChild = finder.GetNodeToMerge(ancestorChild, _theirs, theirChildSet); var extantNode = ancestorChild ?? ourChild ?? theirChild; if (extantNode is XmlCharacterData) { return; // Already done. } if (XmlUtilities.IsTextLevel(ourChild, theirChild, ancestorChild)) { new MergeTextNodesMethod(_merger, mergeStrategy, _skipInnerMergeFor, ref ourChild, _childrenOfOurKeepers, theirChild, _childrenOfTheirKeepers, ancestorChild, _childrenOfAncestorKeepers).DoDeletions(); } else if (ourChild == null) { // We deleted it. if (theirChild == null) { // We both deleted it. Forget it ever existed. // Route tested: MergeChildrenMethodTests. _merger.EventListener.ChangeOccurred(new XmlBothDeletionChangeReport(_merger.MergeSituation.PathToFileInRepository, ancestorChild)); _childrenOfAncestorKeepers.Remove(ancestorChild); } else { if (!XmlUtilities.AreXmlElementsEqual(ancestorChild, theirChild)) { // We deleted, they modified, report conflict. if (theirChild.NodeType == XmlNodeType.Element) { // Route tested (XmlMergerTests). _merger.ConflictOccurred( new RemovedVsEditedElementConflict(theirChild.Name, null, theirChild, ancestorChild, _merger.MergeSituation, _merger.MergeStrategies.GetElementStrategy(theirChild), _merger.MergeSituation.BetaUserId), theirChild); _skipInnerMergeFor.Add(theirChild); } else { // Never used. But then, there isn't plain text in an xml file. _merger.ConflictOccurred( new RemovedVsEditedTextConflict(null, theirChild, ancestorChild, _merger.MergeSituation, _merger.MergeSituation.BetaUserId)); _skipInnerMergeFor.Add(theirChild); } _childrenOfAncestorKeepers.Remove(ancestorChild); //review hatton added dec 2009, wanting whoever edited it to win (previously "we" always won) } else { //We deleted it, they didn't edit it. So just make it go away. // Route tested in TextElementMergeTests, MergeChildrenMethod_DiffOnlyTests, XmlMergerTests _merger.EventListener.ChangeOccurred(new XmlDeletionChangeReport(_merger.MergeSituation.PathToFileInRepository, ancestorChild, theirChild)); _childrenOfAncestorKeepers.Remove(ancestorChild); _childrenOfTheirKeepers.Remove(theirChild); } } } else if (theirChild == null) { // they deleted it (and we didn't) if (XmlUtilities.AreXmlElementsEqual(ancestorChild, ourChild)) { // We didn't touch it, allow their deletion to go forward, forget it existed. // Route tested (XmlMergerTests). _merger.EventListener.ChangeOccurred(new XmlDeletionChangeReport(_merger.MergeSituation.PathToFileInRepository, ancestorChild, ourChild)); _childrenOfAncestorKeepers.Remove(ancestorChild); _childrenOfOurKeepers.Remove(ourChild); } else { // We changed it, ignore their deletion and report conflict. if (ourChild.NodeType == XmlNodeType.Element) { // Route tested (XmlMergerTests). _merger.ConflictOccurred( new EditedVsRemovedElementConflict(ourChild.Name, ourChild, null, ancestorChild, _merger.MergeSituation, _merger.MergeStrategies.GetElementStrategy(ourChild), _merger.MergeSituation.AlphaUserId), ourChild); _skipInnerMergeFor.Add(ourChild); } else { // Never used. But then, there isn't plain text in an xml file. _merger.ConflictOccurred( new EditedVsRemovedTextConflict(ourChild, null, ancestorChild, _merger.MergeSituation, _merger.MergeSituation.AlphaUserId)); _skipInnerMergeFor.Add(ourChild); } } } } }
private bool ChildrenAreSame(XmlNode ourChild, XmlNode theirChild) { return(_merger.MergeStrategies.GetElementStrategy(ourChild).IsImmutable || // don't bother comparing XmlUtilities.AreXmlElementsEqual(ourChild, theirChild)); }
public NodeMergeResult Merge(XmlNode ourParent, XmlNode ours, XmlNode theirs, XmlNode ancestor) { SendMergeHeartbeat(); if (ours == null && theirs == null && ancestor == null) { throw new InvalidOperationException("At least one node has to exist."); } var result = new NodeMergeResult(); var listener = EventListener as DispatchingMergeEventListener; if (listener == null) { var dispatcher = new DispatchingMergeEventListener(); dispatcher.AddEventListener(result); if (EventListener != null) { dispatcher.AddEventListener(EventListener); } EventListener = dispatcher; } else { listener.AddEventListener(result); } if (XmlMergeService.RemoveAmbiguousChildNodes) { // Remove any duplicate child nodes in all three. XmlMergeService.RemoveAmbiguousChildren(EventListener, MergeStrategies, ours); XmlMergeService.RemoveAmbiguousChildren(EventListener, MergeStrategies, theirs); XmlMergeService.RemoveAmbiguousChildren(EventListener, MergeStrategies, ancestor); } if (ancestor == null) { if (ours == null) { // tested EventListener.ChangeOccurred(new XmlAdditionChangeReport(MergeSituation.PathToFileInRepository, theirs)); result.MergedNode = theirs; } else if (theirs == null) { // tested EventListener.ChangeOccurred(new XmlAdditionChangeReport(MergeSituation.PathToFileInRepository, ours)); result.MergedNode = ours; } else { // Both added. if (XmlUtilities.AreXmlElementsEqual(ours, theirs)) { // Same thing. (tested) EventListener.ChangeOccurred(new XmlBothAddedSameChangeReport(MergeSituation.PathToFileInRepository, ours)); result.MergedNode = ours; } else { // But, not the same thing. if (MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin) { // tested ConflictOccurred(new BothAddedMainElementButWithDifferentContentConflict(ours.Name, ours, theirs, MergeSituation, MergeStrategies.GetElementStrategy(ours), MergeSituation.AlphaUserId)); result.MergedNode = ours; } else { // tested ConflictOccurred(new BothAddedMainElementButWithDifferentContentConflict(theirs.Name, theirs, ours, MergeSituation, MergeStrategies.GetElementStrategy(ours), MergeSituation.BetaUserId)); result.MergedNode = theirs; } } } return(result); } // ancestor exists if (ours == null && theirs == null) { // tested EventListener.ChangeOccurred(new XmlBothDeletionChangeReport(MergeSituation.PathToFileInRepository, ancestor)); result.MergedNode = null; return(result); } if (ours == null) { if (XmlUtilities.AreXmlElementsEqual(ancestor, theirs)) { // tested EventListener.ChangeOccurred(new XmlDeletionChangeReport(MergeSituation.PathToFileInRepository, ancestor, theirs)); result.MergedNode = null; } else { // tested ConflictOccurred(new RemovedVsEditedElementConflict(ancestor.Name, null, theirs, ancestor, MergeSituation, MergeStrategies.GetElementStrategy(ancestor), MergeSituation.BetaUserId)); result.MergedNode = theirs; } return(result); } if (theirs == null) { if (XmlUtilities.AreXmlElementsEqual(ancestor, ours)) { // tested EventListener.ChangeOccurred(new XmlDeletionChangeReport(MergeSituation.PathToFileInRepository, ancestor, ours)); result.MergedNode = null; } else { // tested ConflictOccurred(new EditedVsRemovedElementConflict(ancestor.Name, ours, null, ancestor, MergeSituation, MergeStrategies.GetElementStrategy(ancestor), MergeSituation.AlphaUserId)); result.MergedNode = ours; } return(result); } // All three nodes exist. MergeInner(ourParent, ref ours, theirs, ancestor); result.MergedNode = ours; return(result); }