public void Add(WitDiffPair pair)
        {
            m_witDiffPairs.Add(pair);
            switch (pair.DiffType)
            {
            case WitDiffType.AttachmentCount:
            case WitDiffType.AttachmentMismatch:
            case WitDiffType.AttachmentMissing:
                AttachmentMismatchCount++;
                break;

            case WitDiffType.DataMismatch:
            case WitDiffType.DefinitionMismatch:
            case WitDiffType.InvalidDefinition:
                ContentMismatchCount++;
                break;

            case WitDiffType.LinkCount:
            case WitDiffType.LinkMismatch:
            case WitDiffType.LinkMisssing:
                LinkMismatchCount++;
                break;

            case WitDiffType.NotMirrored:
                MissingWorkItemCount++;
                break;

            default:
                Debug.Fail("Unexpected WitDiffType: " + pair.DiffType.ToString());
                break;
            }
        }
        private bool HandleDifference(WitDiffPair diffPair, bool forceDiff)
        {
            // First check if the difference is a false alarm because a work item has been modified on the server since the last sync point
            bool ignoreDiff = false;

            if (!forceDiff)
            {
                ignoreDiff = IgnoreDifference(diffPair);
            }
            if (!ignoreDiff)
            {
                m_diffResult.Add(diffPair);
            }
            return(ignoreDiff);
        }
        private bool IgnoreDifference(WitDiffPair diffPair)
        {
            // TODO: This assumes the HighWaterMark value for all work items is a date time
            // Should pass HighWaterMark instead?
            DateTime sourceHighWaterMarkTime = DateTime.MinValue;

            if (m_latestSyncPoint != null && !DateTime.TryParse(m_latestSyncPoint.SourceHighWaterMarkValue, out sourceHighWaterMarkTime))
            {
                // There is no sync point for the session or we can't parse the DateTime value, so we need to assume the difference cannot be ignored
                return(false);
            }

            if (diffPair.Side1DiffItem != null && diffPair.Side1DiffItem.HasBeenModifiedSince(sourceHighWaterMarkTime))
            {
                string side2workItemId = (diffPair.Side2DiffItem == null) ?
                                         MigrationToolkitResources.WitDiffUnknownWorkItemId : diffPair.Side2DiffItem.WorkItemId;
                m_serverDiffEngine.LogInfo(String.Format(CultureInfo.InvariantCulture,
                                                         MigrationToolkitResources.WitDiffIngoringDiff1,
                                                         diffPair.Side1DiffItem.WorkItemId, diffPair.Side1Name, side2workItemId, diffPair.Side2Name));
                return(true);
            }

            MigrationItemId lastMigratedTargetItemId = new MigrationItemId();

            lastMigratedTargetItemId.ItemId      = m_latestSyncPoint.LastMigratedTargetItemId;
            lastMigratedTargetItemId.ItemVersion = m_latestSyncPoint.LastMigratedTargetItemVersion;
            if (diffPair.Side2DiffItem != null && diffPair.Side2DiffItem.HasBeenModifiedMoreRecentlyThan(lastMigratedTargetItemId))
            {
                string side1workItemId = (diffPair.Side1DiffItem == null) ?
                                         MigrationToolkitResources.WitDiffUnknownWorkItemId : diffPair.Side1DiffItem.WorkItemId;
                m_serverDiffEngine.LogInfo(String.Format(CultureInfo.InvariantCulture,
                                                         MigrationToolkitResources.WitDiffIngoringDiff2,
                                                         side1workItemId, diffPair.Side1Name, diffPair.Side2DiffItem.WorkItemId, diffPair.Side2Name));
                return(true);
            }

            return(false);
        }
 /// <summary>
 /// Handle a difference found between two work items; a difference does not necessarily cause the entire
 /// diff operation to result in a difference, because we want to compare the contents at the time of the
 /// last sync point (when one direction of the migration was completed) and not reported differences caused
 /// by changes to work items since then that have not been sync'd to the other side.
 /// </summary>
 /// <param name="diffPair">A DiffPair object describing the difference just identified</param>
 /// <returns>True if differences betweeen the two work items in the pair should be ignored</returns>
 private bool HandleDifference(WitDiffPair diffPair)
 {
     return(HandleDifference(diffPair, false));
 }
        private void CompareWorkItemsFromOneSideToTheOther(
            string side1FilterString,
            string side1MigrationSourceId,
            MigrationSource side1MigrationSource,
            MigrationSource side2MigrationSource,
            IWITDiffProvider side1DiffProvider,
            IWITDiffProvider side2DiffProvider,
            string side1QueryCondition,
            HashSet <string> moreSide1FieldNamesToIgnore,
            HashSet <string> moreSide2FieldNamesToIgnore)
        {
            // Only left query condition specified
            side1DiffProvider.InitializeForDiff(side1FilterString, !m_serverDiffEngine.NoContentComparison);
            side2DiffProvider.InitializeForDiff(string.Empty, !m_serverDiffEngine.NoContentComparison);

            if (m_serverDiffEngine.NoContentComparison)
            {
                m_serverDiffEngine.LogVerbose(String.Format(CultureInfo.InvariantCulture, MigrationToolkitResources.WitDiffNoContentComparison));
            }

            foreach (IWITDiffItem side1WitDiffItem in side1DiffProvider.GetWITDiffItems(side1QueryCondition))
            {
                bool skipCurrentWorkItem      = false;
                bool fieldValuesMismatch      = false;
                Guid side1MigrationSourceGuid = new Guid(side1MigrationSourceId);

                IWITDiffItem side2WitDiffItem = null;
                string       side2WorkItemId  = TranslationService.TryGetTargetItemId(side1WitDiffItem.WorkItemId, side1MigrationSourceGuid);
                if (!string.IsNullOrEmpty(side2WorkItemId))
                {
                    side2WitDiffItem = side2DiffProvider.GetWITDiffItem(side2WorkItemId);
                }
                if (side2WitDiffItem == null)
                {
                    HandleDifference(new WitDiffPair(WitDiffType.NotMirrored,
                                                     side1MigrationSource.FriendlyName, side1WitDiffItem, side2MigrationSource.FriendlyName, side2WitDiffItem));
                    continue;
                }

                if (m_serverDiffEngine.NoContentComparison)
                {
                    /* Uncomment for debugging
                     * m_serverDiffEngine.LogVerbose(String.Format(CultureInfo.InvariantCulture, "Corresponding work item with Id {0} found on '{1}' for work item with Id {2} on '{3}",
                     *  side2WorkItemId, side2MigrationSource.FriendlyName, side1WitDiffItem.WorkItemId, side1MigrationSource.FriendlyName));
                     */
                }
                else
                {
                    TranslationService.MapWorkItemTypeFieldValues(side1WitDiffItem.WorkItemId, side1WitDiffItem.WorkItemDetails, side1MigrationSourceGuid);

                    XmlElement       side1RootElement     = side1WitDiffItem.WorkItemDetails.DocumentElement;
                    XmlElement       side2RootElement     = side2WitDiffItem.WorkItemDetails.DocumentElement;
                    HashSet <string> side2AttributesFound = new HashSet <string>();
                    foreach (XmlAttribute side1Attribute in side1RootElement.Attributes)
                    {
                        if (side1DiffProvider.IgnoreFieldInComparison(side1Attribute.Name) ||
                            side2DiffProvider.IgnoreFieldInComparison(side1Attribute.Name) ||
                            moreSide1FieldNamesToIgnore.Contains(side1Attribute.Name) ||
                            moreSide2FieldNamesToIgnore.Contains(side1Attribute.Name))
                        {
                            continue;
                        }

                        /* Uncomment for debugging
                         * m_serverDiffEngine.LogVerbose(String.Format(CultureInfo.InvariantCulture,
                         *  "Comparing field '{0}' from source '{1}'",
                         *  side1Attribute.Name, side1MigrationSource.FriendlyName));
                         */

                        string side2AttributeValue = side2RootElement.GetAttribute(side1Attribute.Name);
                        if (string.IsNullOrEmpty(side2AttributeValue))
                        {
                            WitDiffPair diffPair = new WitDiffPair(WitDiffType.DefinitionMismatch,
                                                                   side1MigrationSource.FriendlyName, side1WitDiffItem, side2MigrationSource.FriendlyName, side2WitDiffItem);
                            diffPair.AddMissingField(new WitDiffMissingField(side1Attribute.Name, side1MigrationSource.FriendlyName));
                            skipCurrentWorkItem = HandleDifference(diffPair);
                            break;
                        }
                        side2AttributesFound.Add(side1Attribute.Name);
                        if (!string.Equals(side1Attribute.Value, side2AttributeValue, StringComparison.OrdinalIgnoreCase))
                        {
                            WitDiffPair diffPair = new WitDiffPair(WitDiffType.DataMismatch,
                                                                   side1MigrationSource.FriendlyName, side1WitDiffItem, side2MigrationSource.FriendlyName, side2WitDiffItem);
                            diffPair.AddMismatchField(new WitDiffField(side1Attribute.Name, side1Attribute.Value, side2AttributeValue));
                            skipCurrentWorkItem = HandleDifference(diffPair);
                            break;
                        }
                        else
                        {
                            /* Uncomment for debugging
                             * m_serverDiffEngine.LogVerbose(String.Format(CultureInfo.InvariantCulture, "Match: The header field '{0}' for work item {1} has value '{2}' on both sides",
                             *  side1Attribute.Name, side1WitDiffItem.WorkItemId, side1Attribute.Value));
                             */
                        }
                    }
                    if (skipCurrentWorkItem)
                    {
                        continue;
                    }

                    foreach (XmlAttribute side2Attribute in side2RootElement.Attributes)
                    {
                        if (!side1DiffProvider.IgnoreFieldInComparison(side2Attribute.Name) &&
                            !side2DiffProvider.IgnoreFieldInComparison(side2Attribute.Name) &&
                            !moreSide1FieldNamesToIgnore.Contains(side2Attribute.Name) &&
                            !moreSide2FieldNamesToIgnore.Contains(side2Attribute.Name) &&
                            !side2AttributesFound.Contains(side2Attribute.Name))
                        {
                            WitDiffPair diffPair = new WitDiffPair(WitDiffType.DefinitionMismatch,
                                                                   side1MigrationSource.FriendlyName, side1WitDiffItem, side2MigrationSource.FriendlyName, side2WitDiffItem);
                            diffPair.AddMissingField(new WitDiffMissingField(side2Attribute.Name, side2MigrationSource.FriendlyName));
                            skipCurrentWorkItem = HandleDifference(diffPair);
                            break;
                        }
                    }
                    if (skipCurrentWorkItem)
                    {
                        continue;
                    }

                    XmlNodeList side1Columns = side1RootElement.SelectNodes("/WorkItemChanges/Columns/Column");
                    if (null == side1Columns)
                    {
                        m_diffResult.ProcessingErrors.Add(String.Format(CultureInfo.InvariantCulture, ServerDiffResources.InvalidXMLDocumentForDiffItem,
                                                                        side1MigrationSource.FriendlyName));
                        continue;
                    }

                    XmlNodeList side2Columns = side2RootElement.SelectNodes("/WorkItemChanges/Columns/Column");
                    if (null == side2Columns)
                    {
                        m_diffResult.ProcessingErrors.Add(String.Format(CultureInfo.InvariantCulture, ServerDiffResources.InvalidXMLDocumentForDiffItem,
                                                                        side2MigrationSource.FriendlyName));
                        continue;
                    }

                    Dictionary <string, XmlNode> side2ColumnsByReferenceName = new Dictionary <string, XmlNode>();
                    foreach (XmlNode side2Column in side2Columns)
                    {
                        string referenceName = GetReferenceNameFromFieldColumn(side2Column);
                        if (referenceName != null)
                        {
                            if (!side2ColumnsByReferenceName.ContainsKey(referenceName))
                            {
                                side2ColumnsByReferenceName.Add(referenceName, side2Column);
                            }
                        }
                    }

                    for (int i = 0; i < side1Columns.Count; i++)
                    {
                        XmlNode side1Column        = side1Columns[i];
                        string  side1ReferenceName = GetReferenceNameFromFieldColumn(side1Column);
                        if (side1ReferenceName == null)
                        {
                            WitDiffPair diffPair = new WitDiffPair(WitDiffType.InvalidDefinition, side1MigrationSource.FriendlyName, side1WitDiffItem);
                            skipCurrentWorkItem = HandleDifference(diffPair, true);
                            break;
                        }
                        XmlNode side2Column;
                        if (!side2ColumnsByReferenceName.TryGetValue(side1ReferenceName, out side2Column))
                        {
                            WitDiffPair diffPair = new WitDiffPair(WitDiffType.DefinitionMismatch,
                                                                   side1MigrationSource.FriendlyName, side1WitDiffItem, side2MigrationSource.FriendlyName, side2WitDiffItem);

                            diffPair.AddMissingField(new WitDiffMissingField(side1ReferenceName, side1MigrationSource.FriendlyName));

                            skipCurrentWorkItem = HandleDifference(diffPair, true);
                            break;
                        }
                        string side2ReferenceName = GetReferenceNameFromFieldColumn(side2Column);
                        // Remove the side2Column from the Dictionary
                        side2ColumnsByReferenceName.Remove(side2ReferenceName);

                        string fieldDisplayName = null;

                        const string c_displayName    = "DisplayName";
                        XmlAttribute side1DisplayName = side1Column.Attributes[c_displayName];
                        if (side1DisplayName != null && !string.IsNullOrEmpty(side1DisplayName.Value))
                        {
                            fieldDisplayName = side1DisplayName.Value;
                        }
                        else
                        {
                            fieldDisplayName = side1ReferenceName;
                        }

                        if (!side1DiffProvider.IgnoreFieldInComparison(side1ReferenceName) &&
                            !side2DiffProvider.IgnoreFieldInComparison(side1ReferenceName) &&
                            !moreSide1FieldNamesToIgnore.Contains(side1ReferenceName) &&
                            !moreSide2FieldNamesToIgnore.Contains(side1ReferenceName))
                        {
                            if (!side1Column.HasChildNodes)
                            {
                                m_diffResult.ProcessingErrors.Add(String.Format(MigrationToolkitResources.WitDiffWorkItemDescriptionMissingChildNodes,
                                                                                side1WitDiffItem.WorkItemId, side1MigrationSource.FriendlyName, side1Column.Name));
                                continue;
                            }
                            if (!side2Column.HasChildNodes)
                            {
                                m_diffResult.ProcessingErrors.Add(String.Format(MigrationToolkitResources.WitDiffWorkItemDescriptionMissingChildNodes,
                                                                                side2WitDiffItem.WorkItemId, side2MigrationSource.FriendlyName, side2Column.Name));
                                continue;
                            }

                            XmlNode side1ValueNode = side1Column.FirstChild;
                            XmlNode side2ValueNode = side2Column.FirstChild;
                            if (!string.Equals(side1ValueNode.InnerText.Replace("\r\n", "\n"), side2ValueNode.InnerText.Replace("\r\n", "\n"), StringComparison.OrdinalIgnoreCase))
                            {
                                WitDiffPair diffPair = new WitDiffPair(WitDiffType.DataMismatch,
                                                                       side1MigrationSource.FriendlyName, side1WitDiffItem, side2MigrationSource.FriendlyName, side2WitDiffItem);
                                diffPair.AddMismatchField(new WitDiffField(fieldDisplayName, side1ValueNode.InnerText, side2ValueNode.InnerText));
                                skipCurrentWorkItem = HandleDifference(diffPair);
                                // If there's a data mismatch, continue to compare fields to report additional data mismatches
                                // unless HandleDifference returns true to skip the entire work item
                                if (skipCurrentWorkItem)
                                {
                                    break;
                                }
                                fieldValuesMismatch = true;
                            }
                            else
                            {
                                /* Uncomment for debugging
                                 * m_serverDiffEngine.LogVerbose(String.Format(CultureInfo.InvariantCulture, "Match: The field '{0}' for work item {1} has value '{2}' on both sides",
                                 *  fieldDisplayName, side1WitDiffItem.WorkItemId, side1ValueNode.InnerText));
                                 */
                            }
                        }
                    }

                    bool attachmentsMismatch = false;
                    bool linksMismatch       = false;
                    if (!skipCurrentWorkItem)
                    {
                        // Compare Attachments
                        if (side1WitDiffItem.Attachments.Count != side2WitDiffItem.Attachments.Count)
                        {
                            WitDiffPair diffPair = new WitDiffPair(WitDiffType.AttachmentCount,
                                                                   side1MigrationSource.FriendlyName, side1WitDiffItem, side2MigrationSource.FriendlyName, side2WitDiffItem);
                            skipCurrentWorkItem = HandleDifference(diffPair);
                            attachmentsMismatch = true;
                        }
                        else
                        {
                            Dictionary <string, IMigrationFileAttachment> side2AttachmentsById = new Dictionary <string, IMigrationFileAttachment>();
                            foreach (IMigrationFileAttachment side2Attachment in side2WitDiffItem.Attachments)
                            {
                                string attachmentId = GetAttachmentId(side2Attachment);
                                if (!side2AttachmentsById.ContainsKey(attachmentId))
                                {
                                    side2AttachmentsById.Add(attachmentId, side2Attachment);
                                }
                            }
                            HashSet <string> side1AttachmentIds = new HashSet <string>();
                            foreach (IMigrationFileAttachment side1Attachment in side1WitDiffItem.Attachments)
                            {
                                string side1AttachmentId = GetAttachmentId(side1Attachment);
                                if (side1AttachmentIds.Contains(side1AttachmentId))
                                {
                                    // This is a duplicate attachment; continue to ignore the duplicate
                                    continue;
                                }
                                side1AttachmentIds.Add(side1AttachmentId);
                                IMigrationFileAttachment side2Attachment;
                                if (side2AttachmentsById.TryGetValue(side1AttachmentId, out side2Attachment))
                                {
                                    side2AttachmentsById.Remove(side1AttachmentId);
                                    WitDiffAttachment diffAttachment;
                                    if (!AttachmentsMatch(side1Attachment, side2Attachment, out diffAttachment))
                                    {
                                        WitDiffPair diffPair = new WitDiffPair(WitDiffType.AttachmentMismatch,
                                                                               side1MigrationSource.FriendlyName, side1WitDiffItem, side2MigrationSource.FriendlyName, side2WitDiffItem);
                                        diffPair.AddMistmatchedAttachment(diffAttachment);
                                        skipCurrentWorkItem = HandleDifference(diffPair);
                                        attachmentsMismatch = true;
                                    }
                                }
                                else
                                {
                                    WitDiffPair diffPair = new WitDiffPair(WitDiffType.AttachmentMissing,
                                                                           side1MigrationSource.FriendlyName, side1WitDiffItem, side2MigrationSource.FriendlyName, side2WitDiffItem);
                                    diffPair.AddMissingAttachment(side1Attachment.Name);
                                    skipCurrentWorkItem = HandleDifference(diffPair);
                                    attachmentsMismatch = true;
                                }
                                if (skipCurrentWorkItem)
                                {
                                    break;
                                }
                            }

                            if (!skipCurrentWorkItem)
                            {
                                // Any attachments still in side2AttachmentsByKey were not in side1
                                foreach (IMigrationFileAttachment side2Attachment in side2AttachmentsById.Values)
                                {
                                    WitDiffPair diffPair = new WitDiffPair(WitDiffType.AttachmentMissing,
                                                                           side1MigrationSource.FriendlyName, side1WitDiffItem, side2MigrationSource.FriendlyName, side2WitDiffItem);
                                    diffPair.AddMissingAttachment(side2Attachment.Name);
                                    skipCurrentWorkItem = HandleDifference(diffPair);
                                    attachmentsMismatch = true;
                                }
                            }
                        }
                    }

                    if (!skipCurrentWorkItem)
                    {
                        // Compare links
                        if (side1WitDiffItem.Links.Count != side2WitDiffItem.Links.Count)
                        {
                            WitDiffPair diffPair = new WitDiffPair(WitDiffType.LinkCount,
                                                                   side1MigrationSource.FriendlyName, side1WitDiffItem, side2MigrationSource.FriendlyName, side2WitDiffItem);
                            skipCurrentWorkItem = HandleDifference(diffPair);
                            linksMismatch       = true;
                        }

                        /* Commenting out the detailed comparison of each link for now as it does not work because the link artifact URIs
                         * need to be translated.  This requires some refactoring of translation methods current embedded in the LinkEngine
                         * that are not easily used outside the context of a full running migration/sync session.
                         * else
                         * {
                         *  Dictionary<string, ILink> side2LinksById = new Dictionary<string, ILink>();
                         *  foreach (ILink side2Link in side2WitDiffItem.Links)
                         *  {
                         *      string side2LinkId = GetLinkId(side2Link);
                         *      if (!side2LinksById.ContainsKey(side2LinkId))
                         *      {
                         *          side2LinksById.Add(side2LinkId, side2Link);
                         *      }
                         *  }
                         *  foreach (ILink side1Link in side1WitDiffItem.Links)
                         *  {
                         *      ILink side2Link;
                         *      string side1LinkId = GetLinkId(side1Link);
                         *      if (side2LinksById.TryGetValue(side1LinkId, out side2Link))
                         *      {
                         *          side2LinksById.Remove(side1LinkId);
                         *          WitDiffLink diffLink;
                         *          if (!LinksMatch(new Guid(side1MigrationSource.InternalUniqueId), side1Link, new Guid(side2MigrationSource.InternalUniqueId), side2Link, out diffLink))
                         *          {
                         *              WitDiffPair diffPair = new WitDiffPair(WitDiffType.LinkMismatch,
                         *                  side1MigrationSource.FriendlyName, side1WitDiffItem, side2MigrationSource.FriendlyName, side2WitDiffItem);
                         *              diffPair.AddMistmatchedLink(diffLink);
                         *              skipCurrentWorkItem = HandleDifference(diffPair);
                         *              linksMismatch = true;
                         *              break;
                         *          }
                         *
                         *      }
                         *      else
                         *      {
                         *          WitDiffPair diffPair = new WitDiffPair(WitDiffType.LinkMisssing,
                         *              side1MigrationSource.FriendlyName, side1WitDiffItem, side2MigrationSource.FriendlyName, side2WitDiffItem);
                         *          diffPair.AddMissingLink(side1LinkId);
                         *          skipCurrentWorkItem = HandleDifference(diffPair);
                         *          linksMismatch = true;
                         *      }
                         *  }
                         *
                         *  if (!skipCurrentWorkItem)
                         *  {
                         *      // Any links still in side2LinksById were not in side1
                         *      foreach (ILink side2link in side2LinksById.Values)
                         *      {
                         *          WitDiffPair diffPair = new WitDiffPair(WitDiffType.LinkMisssing,
                         *              side1MigrationSource.FriendlyName, side1WitDiffItem, side2MigrationSource.FriendlyName, side2WitDiffItem);
                         *          diffPair.AddMissingLink(GetLinkId(side2link));
                         *          skipCurrentWorkItem = HandleDifference(diffPair);
                         *          linksMismatch = true;
                         *      }
                         *  }
                         * }
                         */
                    }

                    if (skipCurrentWorkItem || fieldValuesMismatch || attachmentsMismatch || linksMismatch)
                    {
                        continue;
                    }

                    m_serverDiffEngine.LogVerbose(String.Format(CultureInfo.InvariantCulture, MigrationToolkitResources.WitDiffWorkItemsMatch,
                                                                side1WitDiffItem.WorkItemId, side1MigrationSource.FriendlyName, side2WorkItemId, side2MigrationSource.FriendlyName));
                }
            }
        }
Ejemplo n.º 6
0
        public void LogDifference(WitDiffPair diffPair)
        {
            string side1WorkItemId = diffPair.Side1DiffItem == null ? string.Empty : diffPair.Side1DiffItem.WorkItemId;
            string side2WorkItemId = diffPair.Side2DiffItem == null ? string.Empty : diffPair.Side2DiffItem.WorkItemId;

            m_serverDiffEngine.LogError(String.Format(CultureInfo.InvariantCulture, MigrationToolkitResources.WitDiffResultWorkItemMismatch,
                                                      side1WorkItemId, diffPair.Side1Name, side2WorkItemId, diffPair.Side2Name,
                                                      diffPair.DiffType.ToString()));

            if (diffPair.MissingFields.Count > 0)
            {
                m_serverDiffEngine.LogError(MigrationToolkitResources.WitDiffResultWorkItemFieldMissingHeader);
                foreach (WitDiffMissingField missingField in diffPair.MissingFields)
                {
                    m_serverDiffEngine.LogError(String.Format(CultureInfo.InvariantCulture, MigrationToolkitResources.WitDiffResultWorkItemFieldMissing,
                                                              missingField.FieldName, missingField.SourceName));
                }
            }

            if (diffPair.DiffFields.Count > 0)
            {
                m_serverDiffEngine.LogError(MigrationToolkitResources.WitDiffResultWorkItemFieldDiffHeader);
                foreach (WitDiffField diffField in diffPair.DiffFields)
                {
                    m_serverDiffEngine.LogError(String.Format(CultureInfo.InvariantCulture, MigrationToolkitResources.WitDiffResultWorkItemFieldDiffDetail,
                                                              diffField.FieldName, diffField.SourceValue, diffField.TargetValue));
                }
            }

            if (diffPair.MissingAttachments.Count > 0)
            {
                m_serverDiffEngine.LogError(MigrationToolkitResources.WitDiffResultWorkItemAttachmentMissingHeader);
                foreach (string missingAttachment in diffPair.MissingAttachments)
                {
                    m_serverDiffEngine.LogError(String.Format(CultureInfo.InvariantCulture, MigrationToolkitResources.WitDiffResultWorkItemAttachmentMissingDetail,
                                                              missingAttachment));
                }
            }

            if (diffPair.DiffAttachments.Count > 0)
            {
                m_serverDiffEngine.LogError(MigrationToolkitResources.WitDiffResultWorkItemAttachmentDiffHeader);
                foreach (WitDiffAttachment diffAttachment in diffPair.DiffAttachments)
                {
                    m_serverDiffEngine.LogError(String.Format(CultureInfo.InvariantCulture, MigrationToolkitResources.WitDiffResultWorkItemAttachmentDiffDetail,
                                                              diffAttachment.SourceAttachmentName, diffAttachment.FieldName, diffAttachment.SourceValue, diffAttachment.TargetValue));
                }
            }

            if (diffPair.DiffLinks.Count > 0)
            {
                m_serverDiffEngine.LogError(MigrationToolkitResources.WitDiffResultWorkItemLinkDiffHeader);
                foreach (WitDiffLink diffLink in diffPair.DiffLinks)
                {
                    m_serverDiffEngine.LogError(String.Format(CultureInfo.InvariantCulture, MigrationToolkitResources.WitDiffResultWorkItemLinkDiffDetail,
                                                              diffLink.FieldName, diffLink.SourceValue, diffLink.TargetValue));
                }
            }

            if (diffPair.DiffType == WitDiffType.LinkCount)
            {
                m_serverDiffEngine.LogError(String.Format(CultureInfo.InvariantCulture, MigrationToolkitResources.WitDiffResultWorkItemLinkCountDiffDetail,
                                                          diffPair.Side1DiffItem.LinkCount, diffPair.Side2DiffItem.LinkCount));
                foreach (string linkUri in diffPair.Side1DiffItem.LinkUris)
                {
                    m_serverDiffEngine.LogError(String.Format(CultureInfo.InvariantCulture,
                                                              "Work Item {0} has link to {1}",
                                                              diffPair.Side1DiffItem.WorkItemId, linkUri));
                }
                foreach (string linkUri in diffPair.Side2DiffItem.LinkUris)
                {
                    m_serverDiffEngine.LogError(String.Format(CultureInfo.InvariantCulture,
                                                              "Work Item {0} has link to {1}",
                                                              diffPair.Side2DiffItem.WorkItemId, linkUri));
                }
            }
        }