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. }
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> /// Merges the children into the "ours" xmlnode, and uses the merger's listener to publish what happened. /// </summary> public void Run() { var extantNode = _ours ?? _theirs ?? _ancestor; if (extantNode.NodeType != XmlNodeType.Element) { return; } // Deletions from ancestor have already been done, so _ours and _theirs ought never be null. // _ancestor could be null, however, for adds. var ourText = _ours.InnerText.Trim(); var theirText = _theirs.InnerText.Trim(); var ancestorText = _ancestor == null ? null : _ancestor.InnerText.Trim(); if (ourText == theirText && ourText == ancestorText) { return; // No changes by anyone. // Not used. } if (ancestorText == null) { // We both added something to ancestor. //if (ourText == theirText) //{ // // We both added the same thing. It could be content or empty strings. // // This case seems to be handled by MergeChildrenMethod. // _merger.EventListener.ChangeOccurred(new XmlTextBothAddedReport(_merger.MergeSituation.PathToFileInRepository, _ours)); // return; //} // At this point, ancestor is null, and ourText is *not* the same as theirText. if (ourText == string.Empty) { // We added empty node. They added content, so go with simple add report. // Route tested. _merger.EventListener.ChangeOccurred(new XmlTextAddedReport(_merger.MergeSituation.PathToFileInRepository, _theirs)); _ours.InnerText = _theirs.InnerText; return; } if (theirText == string.Empty) { // They added empty node. We added content, so go with simple add report. // Route tested. _merger.EventListener.ChangeOccurred(new XmlTextAddedReport(_merger.MergeSituation.PathToFileInRepository, _ours)); return; } // Add conflict and set winner to merge situation declaration. if (_merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin) { // Route tested. _merger.ConflictOccurred(new XmlTextBothAddedTextConflict(_ours.Name, _ours, _theirs, _merger.MergeSituation, _elementDescriber, _merger.MergeSituation.AlphaUserId)); return; } // Route tested. _merger.ConflictOccurred(new XmlTextBothAddedTextConflict(_ours.Name, _ours, _theirs, _merger.MergeSituation, _elementDescriber, _merger.MergeSituation.BetaUserId)); _ours.InnerText = _theirs.InnerText; return; } if (ancestorText == string.Empty) { if (ourText == string.Empty) { // Already checked, above. //if (theirText == string.Empty) // return; // No changes by anyone. if (theirText.Length > 0) { // They added text to otherwise empty node. We did nothing. // Route tested. _merger.EventListener.ChangeOccurred(new XmlTextAddedReport(_merger.MergeSituation.PathToFileInRepository, _theirs)); _ours.InnerText = _theirs.InnerText; //return; } //// They added text content into node. We did nothing. //// This case seems to be handled by MergeChildrenMethod. //_ours.InnerText = _theirs.InnerText; //_merger.EventListener.ChangeOccurred(new XmlTextAddedReport(_merger.MergeSituation.PathToFileInRepository, _theirs)); return; } if (theirText == string.Empty) { // Already checked, above. //if (ourText == string.Empty) // return; // No changes by anyone. if (ourText.Length > 0) { // We edited. They did nothing. Keep ours. // Route tested. _merger.EventListener.ChangeOccurred(new XmlTextAddedReport(_merger.MergeSituation.PathToFileInRepository, _ours)); } return; } if (ourText == theirText) { // Both added same text to empty node. // Route tested. _merger.EventListener.ChangeOccurred(new XmlTextBothAddedReport(_merger.MergeSituation.PathToFileInRepository, _ours)); return; } } if (ancestorText.Length <= 0) { return; } // ourText is not null nor an empty string. if (ourText == ancestorText && ourText != theirText) { _ours.InnerText = _theirs.InnerText; // They changed it, nobody else did anything. // Route tested. _merger.EventListener.ChangeOccurred(new XmlTextChangedReport(_merger.MergeSituation.PathToFileInRepository, _theirs)); return; } if (theirText == ancestorText && ourText != ancestorText) { // We changed. They did nothing. // Route tested. _merger.EventListener.ChangeOccurred(new XmlTextChangedReport(_merger.MergeSituation.PathToFileInRepository, _ours)); return; } if (theirText != ourText && ourText != ancestorText) { if (theirText == string.Empty) { // They deleted the text string, but we edited it, so we win. // Route tested. _merger.ConflictOccurred(new XmlTextEditVsRemovedConflict(_ancestor.Name, _ours, _theirs, _ancestor, _merger.MergeSituation, _elementDescriber, _merger.MergeSituation.AlphaUserId)); return; } if (ourText == string.Empty) { // We deleted the text string, but they edited it, so they win. // Route tested. _ours.InnerText = _theirs.InnerText; _merger.ConflictOccurred(new XmlTextRemovedVsEditConflict(_ancestor.Name, _ours, _theirs, _ancestor, _merger.MergeSituation, _elementDescriber, _merger.MergeSituation.BetaUserId)); return; } // Both edited, but not the same edit. if (_merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin) { // Route tested. _merger.ConflictOccurred(new XmlTextBothEditedTextConflict(_ours.Name, _ours, _theirs, _ancestor, _merger.MergeSituation, _elementDescriber, _merger.MergeSituation.AlphaUserId)); return; } // They win // Route tested. _ours.InnerText = _theirs.InnerText; _merger.ConflictOccurred(new XmlTextBothEditedTextConflict(_ours.Name, _ours, _theirs, _ancestor, _merger.MergeSituation, _elementDescriber, _merger.MergeSituation.BetaUserId)); //return; } //if (ourText == theirText && ourText != ancestorText) //{ // // Both did the same edit. // // This case seems to be handled by MergeChildrenMethod. // _merger.EventListener.ChangeOccurred(new XmlTextBothMadeSameChangeReport(_merger.MergeSituation.PathToFileInRepository, _ours)); //} }
private static XmlNode HandleCaseOfNoAncestor(XmlMerger merger, XmlNode ours, XmlNode theirs) { var mainNodeStrategy = merger.MergeStrategies.GetElementStrategy(ours ?? theirs); var mergeSituation = merger.MergeSituation; var pathToFileInRepository = mergeSituation.PathToFileInRepository; if (ours == null) { // They added, we did nothing. // Route tested, but the MergeChildrenMethod adds the change report for us. // So, theory has it one can't get here from any normal place. // But, keep it, in case MergeChildrenMethod gets 'fixed'. merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(pathToFileInRepository, theirs)); return(theirs); } if (theirs == null) { // We added, they did nothing. // Route tested. merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(pathToFileInRepository, ours)); return(ours); } // Both added the special containing node. // Remove children nodes to see if main containing nodes are the same. if (XmlUtilities.AreXmlElementsEqual(ours, theirs)) { // Route tested. // Same content. merger.EventListener.ChangeOccurred(new XmlBothAddedSameChangeReport(pathToFileInRepository, ours)); } else { // Different content. var ourChild = GetElementChildren(ours).FirstOrDefault(); var theirChild = GetElementChildren(theirs).FirstOrDefault(); var ourClone = MakeClone(ours); var theirClone = MakeClone(theirs); if (XmlUtilities.AreXmlElementsEqual(ourClone, theirClone)) { // new main elements are the same, but not the contained child merger.EventListener.ChangeOccurred(new XmlBothAddedSameChangeReport(pathToFileInRepository, ourClone)); if (ourChild == null && theirChild == null) { return(ours); // Nobody added the child node. } if (ourChild == null) { merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(pathToFileInRepository, theirChild)); // ReSharper disable once PossibleNullReferenceException -- if ours.OwnerDocument is null, we have other problems ours.AppendChild(ours.OwnerDocument.ImportNode(theirChild, true)); return(ours); } if (theirChild == null) { merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(pathToFileInRepository, ourChild)); return(ours); } // both children exist, but are different. var mergeStrategyForChild = merger.MergeStrategies.GetElementStrategy(ourChild); if (merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin) { // Do the clone thing on the two child nodes to see if the diffs are in the child or lower down. var ourChildClone = MakeClone(ourChild); var theirChildClone = MakeClone(theirChild); if (XmlUtilities.AreXmlElementsEqual(ourChildClone, theirChildClone)) { var ourChildReplacement = ourChild; merger.MergeInner(ours, ref ourChildReplacement, theirChild, null); if (!ReferenceEquals(ourChild, ourChildReplacement)) { XmlUtilities.ReplaceOursWithTheirs(ours, ref ourChild, ourChildReplacement); } } else { merger.ConflictOccurred(new BothAddedMainElementButWithDifferentContentConflict(ourChild.Name, ourChild, theirChild, mergeSituation, mergeStrategyForChild, mergeSituation.AlphaUserId)); } } else { // Do the clone thing on the two child nodes to see if the diffs are in the child or lower down. var ourChildClone = MakeClone(ourChild); var theirChildClone = MakeClone(theirChild); if (XmlUtilities.AreXmlElementsEqual(ourChildClone, theirChildClone)) { var ourChildReplacement = ourChild; merger.MergeInner(ours, ref ourChildReplacement, theirChild, null); if (!ReferenceEquals(ourChild, ourChildReplacement)) { XmlUtilities.ReplaceOursWithTheirs(ours, ref ourChild, ourChildReplacement); } } else { merger.ConflictOccurred(new BothAddedMainElementButWithDifferentContentConflict(ourChild.Name, ourChild, theirChild, mergeSituation, mergeStrategyForChild, mergeSituation.AlphaUserId)); XmlUtilities.ReplaceOursWithTheirs(ours, ref ourChild, theirChild); } } return(ours); } // Main containing element is not the same. Not to worry about child if (merger.MergeSituation.ConflictHandlingMode == MergeOrder.ConflictHandlingModeChoices.WeWin) { merger.ConflictOccurred(new BothAddedMainElementButWithDifferentContentConflict(ours.Name, ours, theirs, mergeSituation, mainNodeStrategy, mergeSituation.AlphaUserId)); } else { merger.ConflictOccurred(new XmlTextBothAddedTextConflict(theirs.Name, theirs, ours, mergeSituation, mainNodeStrategy, mergeSituation.BetaUserId)); XmlUtilities.ReplaceOursWithTheirs(ours.ParentNode, ref ours, theirs); } } return(ours); }
private static XmlNode Run(XmlMerger merger, XmlNode ours, XmlNode theirs, XmlNode ancestor) { if (ours == null && theirs == null && ancestor == null) { return(null); } if (ancestor == null) { return(HandleCaseOfNoAncestor(merger, ours, theirs)); } // ancestor is not null at this point. var mergeSituation = merger.MergeSituation; var pathToFileInRepository = mergeSituation.PathToFileInRepository; if (ours == null && theirs == null) { // We both deleted main node. // Route tested, but the MergeChildrenMethod adds the change report for us. merger.EventListener.ChangeOccurred(new XmlBothDeletionChangeReport(pathToFileInRepository, ancestor)); return(null); } if (ours == null) { return(HandleOursNotPresent(merger, ancestor, theirs)); } if (theirs == null) { return(HandleTheirsNotPresent(merger, ancestor, ours)); } // End of checking main parent node. // ancestor, ours, and theirs all exist here. var ourChild = GetElementChildren(ours).FirstOrDefault(); var theirChild = GetElementChildren(theirs).FirstOrDefault(); var ancestorChild = GetElementChildren(ancestor).FirstOrDefault(); if (ourChild == null && theirChild == null && ancestorChild == null) { return(ours); // All three are childless. } if (ancestorChild == null) { return(HandleCaseOfNoAncestorChild(merger, ours, ourChild, theirChild)); } var mergeStrategyForChild = merger.MergeStrategies.GetElementStrategy(ancestorChild); if (ourChild == null) { return(HandleOurChildNotPresent(merger, ours, ancestor, theirChild, pathToFileInRepository, ancestorChild, mergeSituation, mergeStrategyForChild)); } if (theirChild == null) { return(HandleTheirChildNotPresent(merger, ours, ancestor, ancestorChild, ourChild, mergeSituation, mergeStrategyForChild, pathToFileInRepository)); } // ancestorChild, ourChild, and theirChild all exist. // But, it could be that we or they deleted and added something. // Check for edit vs delete+add, or there can be two items in ours, which is not legal. var match = mergeStrategyForChild.MergePartnerFinder.GetNodeToMerge(ourChild, ancestor, SetFromChildren.Get(ancestor)); if (match == null) { // we deleted it and added a new one. if (XmlUtilities.AreXmlElementsEqual(theirChild, ancestorChild)) { // Our delete+add wins, since they did nothing. merger.EventListener.ChangeOccurred(new XmlDeletionChangeReport(pathToFileInRepository, ancestor, ancestorChild)); merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(pathToFileInRepository, ourChild)); return(ours); } // They edited old one, so they win over our delete+add. merger.ConflictOccurred(new RemovedVsEditedElementConflict(theirChild.Name, theirChild, null, ancestorChild, mergeSituation, mergeStrategyForChild, mergeSituation.BetaUserId)); XmlUtilities.ReplaceOursWithTheirs(ours, ref ourChild, theirChild); return(ours); } match = mergeStrategyForChild.MergePartnerFinder.GetNodeToMerge(theirChild, ancestor, SetFromChildren.Get(ancestor)); if (match == null) { // they deleted it and added a new one. if (XmlUtilities.AreXmlElementsEqual(ourChild, ancestorChild)) { // Their delete+add wins, since we did nothing. merger.EventListener.ChangeOccurred(new XmlDeletionChangeReport(pathToFileInRepository, ancestor, ancestorChild)); merger.EventListener.ChangeOccurred(new XmlAdditionChangeReport(pathToFileInRepository, theirChild)); XmlUtilities.ReplaceOursWithTheirs(ours, ref ourChild, theirChild); return(ours); } // We edited old one, so we win over their delete+add. merger.ConflictOccurred(new RemovedVsEditedElementConflict(ourChild.Name, ourChild, null, ancestorChild, mergeSituation, mergeStrategyForChild, mergeSituation.AlphaUserId)); return(ours); } merger.MergeInner(ours, ref ourChild, theirChild, ancestorChild); // Route tested. (UsingWith_NumberOfChildrenAllowed_ZeroOrOne_DoesNotThrowWhenParentHasOneChildNode) return(ours); }
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 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]); } }
/// <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). }
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; }
/// <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, 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 bene an expected 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)); 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)); 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)); 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)); 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) ours = theirs; } } // No changes. // Route tested (x2). }
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 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 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); }
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)); } }
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; }