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));
 }
Exemple #5
0
        /// <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).
        }
Exemple #6
0
        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);
        }
Exemple #7
0
        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));
            }
        }
Exemple #8
0
        /// <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);
        }
Exemple #10
0
        /// <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]);
            }
        }
Exemple #11
0
        /// <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);
                        }
                    }
                }
            }
        }
Exemple #12
0
 private bool ChildrenAreSame(XmlNode ourChild, XmlNode theirChild)
 {
     return(_merger.MergeStrategies.GetElementStrategy(ourChild).IsImmutable ||           // don't bother comparing
            XmlUtilities.AreXmlElementsEqual(ourChild, theirChild));
 }