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 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; } }
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); }
public string HtmlContext(XmlNode mergeElement) { return(XmlUtilities.GetXmlForShowingInHtml(mergeElement.OuterXml)); }
/// <summary> /// "Merge" elemement if it is 'atomic' and return true. Otherwise, do nothing and return false. /// </summary> /// <remarks> /// <param name="ours" /> may be changed to <param name="theirs"/>, /// if <param name="ours"/> is null and <param name="theirs"/> is not null. /// </remarks> /// <returns>'True' if the given elements were 'atomic'. Otherwise 'false'.</returns> internal static void Run(XmlMerger merger, XmlNode ourParent, ref XmlNode ours, XmlNode theirs, XmlNode commonAncestor) { if (merger == null) { throw new ArgumentNullException("merger"); // Route tested. } 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). }
public NodeMergeResult Merge(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(ref ours, theirs, ancestor); result.MergedNode = ours; 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)); } }
/// <summary> /// This method does the actual work for the various public entry points of XmlMerge /// and from the various Method-type classes, as it processes child nodes, if any. /// </summary> internal void MergeInner(ref XmlNode ours, XmlNode theirs, XmlNode ancestor) { SendMergeHeartbeat(); _oursContext = ours; _theirsContext = theirs; _ancestorContext = ancestor; var elementStrat = MergeStrategies.GetElementStrategy(ours ?? theirs ?? ancestor); // Step 0: Do anything special the strategy wants to do before regular merging. This may modify the nodes. // For instance clinets may want to ensure 'our' and 'their' have the latest date stamp available. elementStrat.Premerger.Premerge(EventListener, ref ours, theirs, ancestor); // Step 0.1: Set up a context, if available. // Listeners are set to use NullContextDescriptor as the default context, var contextDescriptorGenerator = elementStrat.ContextDescriptorGenerator; // May be null, which is fine in code, below, that uses it. //review: question: does this not get called at levels below the entry? //this would seem to fail at, say, a sense. I'm confused. (JH 30june09) var descriptor = GetContextDescriptor(ours, contextDescriptorGenerator); EventListener.EnteringContext(descriptor); // TODO: If the context is ever redone as a stack, then this call with push the context onto the stack. _htmlContextGenerator = (contextDescriptorGenerator as IGenerateHtmlContext) ?? new SimpleHtmlGenerator(); // Step 1: If the current set of nodes are immutable, // then make sure no changes took place (among a few other things). if (elementStrat.IsImmutable) { ImmutableElementMergeService.DoMerge(this, ref ours, theirs, ancestor); return; // Don't go any further, since it is immutable. } // Step 2: If the current set of elements is 'atomic' // (only one user can make changes to the node, or anything it contains), // then make sure only set of changes have been made. if (elementStrat.IsAtomic) { if (elementStrat.AllowAtomicTextMerge && XmlUtilities.IsTextLevel(ours, theirs, ancestor)) { DoTextMerge(ref ours, theirs, ancestor, elementStrat); return; } MergeAtomicElementService.Run(this, ref ours, theirs, ancestor); return; } // Step 3: Go ahead and merge the attributes, as needed. MergeXmlAttributesService.MergeAttributes(this, ref ours, theirs, ancestor); // Step 4: Hmm, trouble is, a node might be required to have one or more child nodes. // I suppose a real validator would have picked up on that, and not allowed // the first commit, so we wouldn't then be doing a merge operation. // So, not to worry here, if the validator doesn't care enough to prevent the first commit. // It could be possible for the elements to have no children, in which case, there is nothing more to merge, so just return. if (ours != null && !ours.HasChildNodes && theirs != null && !theirs.HasChildNodes && ancestor != null && !ancestor.HasChildNodes) { return; } // Step 5: Do some kind of merge on the child node. if (XmlUtilities.IsTextLevel(ours, theirs, ancestor)) { // Step 5A: Merge the text element. DoTextMerge(ref ours, theirs, ancestor, elementStrat); } else { switch (elementStrat.NumberOfChildren) { case NumberOfChildrenAllowed.Zero: case NumberOfChildrenAllowed.ZeroOrOne: // Step 5B: Merge the "special needs" nodes. MergeLimitedChildrenService.Run(this, elementStrat, ref ours, theirs, ancestor); break; case NumberOfChildrenAllowed.ZeroOrMore: // Step 5B: // Q: is this a level of the xml file that would consitute the minimal unit conflict-understanding // from a user perspecitve? // e.g., in a dictionary, this is the lexical entry. In a text, it might be a paragraph. // A (RandyR): Definitely it may not be such a level of node. new MergeChildrenMethod(ours, theirs, ancestor, this).Run(); break; } } // At some point, it may be necessary here to restore the pre-existing values of // _oursContext, _theirsContext, _ancestorContext, and _htmlContextGenerator. // and somehow restore the EventListener's Context. // Currently however no client generates further conflicts after calling MergeChildren. // Step 6: TODO: If the context is ever redone as a stack, then pop the stack here to return to the outer context via some new LeavingContext method on EventListener. }
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); }
/// <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); 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) { // 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)); }