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)); } } }
public bool VerifyContentsMatch(string sourceVersion, string targetVersion) { Stopwatch stopWatch = Stopwatch.StartNew(); Trace.WriteLine(String.Format(CultureInfo.InvariantCulture, "Entering VCDiffComparer.VerifyContentsMatch: sourceVersion: {0}, targetVersion: {1}", sourceVersion == null ? "latest" : sourceVersion, targetVersion == null ? "latest" : targetVersion)); List <IVCDiffItem> sourceRootDiffItems = new List <IVCDiffItem>(); List <IVCDiffItem> targetRootDiffItems = new List <IVCDiffItem>(); bool contentMatch = true; int foldersProcessed = 0; int filesProcessed = 0; try { Stack <IVCDiffItem> sourceFolders = new Stack <IVCDiffItem>(); Queue <IVCDiffItem> sourceFiles = new Queue <IVCDiffItem>(); Dictionary <string, IVCDiffItem> targetFolders = new Dictionary <string, IVCDiffItem>(StringComparer.InvariantCultureIgnoreCase); Dictionary <string, IVCDiffItem> targetFiles = new Dictionary <string, IVCDiffItem>(StringComparer.InvariantCultureIgnoreCase); List <string> sourceCloakList = new List <string>(); List <string> targetCloakList = new List <string>(); foreach (var filterPair in m_serverDiffEngine.Session.Filters.FilterPair) { if (filterPair.Neglect) { // TODO: Need to deal with translating paths for cloaked filter pairs into the canonical relative form used by IVCDiffItem.Path !!! sourceCloakList.Add(GetSourceFilterString(filterPair)); targetCloakList.Add(GetTargetFilterString(filterPair)); } } foreach (var filterPair in m_serverDiffEngine.Session.Filters.FilterPair) { if (!filterPair.Neglect) { sourceFolders.Clear(); sourceFiles.Clear(); targetFolders.Clear(); targetFiles.Clear(); // Always go 1 level down to avoid too large query string sourceFilterString = GetSourceFilterString(filterPair); IVCDiffItem sourceDiffRootItem = SourceVCDiffProvider.InitializeForDiff(sourceFilterString, sourceVersion); if (sourceDiffRootItem != null) { if (sourceDiffRootItem.VCItemType == VCItemType.Folder) { sourceFolders.Push(sourceDiffRootItem); } else { sourceFiles.Enqueue(sourceDiffRootItem); } sourceRootDiffItems.Add(sourceDiffRootItem); } string targetFilterString = GetTargetFilterString(filterPair); IVCDiffItem targetDiffRootItem = TargetVCDiffProvider.InitializeForDiff(targetFilterString, targetVersion); if (targetDiffRootItem != null) { if (targetDiffRootItem.VCItemType == VCItemType.Folder) { targetFolders.Add(targetDiffRootItem.ServerPath, targetDiffRootItem); } else { targetFiles.Add(targetDiffRootItem.ServerPath, targetDiffRootItem); } targetRootDiffItems.Add(targetDiffRootItem); } while (sourceFiles.Count > 0 | sourceFolders.Count > 0 | targetFiles.Count > 0 | targetFolders.Count > 0) { while (sourceFiles.Count > 0) { IVCDiffItem sourceItem = sourceFiles.Dequeue(); if (sourceItem.VCItemType != VCItemType.File) { Debug.Fail("VerifyContentMatch: Found IVCDiffItem that is not type File in the sourceFiles queue"); continue; } string targetPath = TranslationService.GetMappedPath(sourceItem.ServerPath, new Guid(m_serverDiffEngine.Session.LeftMigrationSourceUniqueId)); if (targetPath == null || !targetFiles.ContainsKey(targetPath)) { m_serverDiffEngine.LogError(string.Format(CultureInfo.InvariantCulture, ServerDiffResources.ItemOnlyFoundOnSource, sourceItem.ServerPath)); contentMatch = false; } else { bool fileContentsMatch = ContentsMatch(sourceItem.HashValue, targetFiles[targetPath].HashValue); if (fileContentsMatch) { m_serverDiffEngine.LogVerbose(String.Format(CultureInfo.InvariantCulture, ServerDiffResources.FilesMatch, sourceItem.ServerPath, targetPath)); } else { contentMatch = false; m_serverDiffEngine.LogError(string.Format(CultureInfo.InvariantCulture, ServerDiffResources.ItemContentDoesNotMatch, sourceItem.ServerPath)); } targetFiles.Remove(targetPath); } filesProcessed++; } foreach (KeyValuePair <string, IVCDiffItem> remainingFile in targetFiles) { m_serverDiffEngine.LogError(string.Format(CultureInfo.InvariantCulture, ServerDiffResources.ItemOnlyFoundOnTarget, remainingFile.Key)); contentMatch = false; } Debug.Assert(sourceFiles.Count == 0); targetFiles.Clear(); if (sourceFolders.Count > 0) { IVCDiffItem sourceFolder = sourceFolders.Pop(); m_serverDiffEngine.LogVerbose(string.Format(CultureInfo.InvariantCulture, ServerDiffResources.ProcessingSourceFolder, sourceFolder.ServerPath)); string targetFolder = TranslationService.GetMappedPath(sourceFolder.ServerPath, new Guid(m_serverDiffEngine.Session.LeftMigrationSourceUniqueId)); if (targetFolder != null && targetFolders.ContainsKey(targetFolder)) { // Always go 1 level down to avoid too large query IEnumerable <IVCDiffItem> sourceDiffItems = SourceVCDiffProvider.GetFolderSubDiffItems(sourceFolder); foreach (IVCDiffItem diffItem in sourceDiffItems) { if (isCloaked(SourceVCDiffProvider, diffItem, sourceCloakList)) { continue; } if (diffItem.VCItemType == VCItemType.File) { sourceFiles.Enqueue(diffItem); } else { sourceFolders.Push(diffItem); } } IEnumerable <IVCDiffItem> targetDiffItems = TargetVCDiffProvider.GetFolderSubDiffItems(targetFolders[targetFolder]); foreach (IVCDiffItem diffItem in targetDiffItems) { if (isCloaked(TargetVCDiffProvider, diffItem, targetCloakList)) { continue; } if (diffItem.VCItemType == VCItemType.File) { if (!targetFiles.ContainsKey(diffItem.ServerPath)) { targetFiles.Add(diffItem.ServerPath, diffItem); } } else { if (!targetFolders.ContainsKey(diffItem.ServerPath)) { targetFolders.Add(diffItem.ServerPath, diffItem); } } } targetFolders.Remove(targetFolder); if (++foldersProcessed % 100 == 0) { m_serverDiffEngine.LogInfo(String.Format(CultureInfo.InvariantCulture, "Processed {0} source folders containing {1} files ...", foldersProcessed, filesProcessed)); } } else { m_serverDiffEngine.LogError(string.Format(CultureInfo.InvariantCulture, ServerDiffResources.ItemOnlyFoundOnSource, sourceFolder.ServerPath)); contentMatch = false; } } else { foreach (KeyValuePair <string, IVCDiffItem> remainingFolder in targetFolders) { m_serverDiffEngine.LogError(string.Format(CultureInfo.InvariantCulture, ServerDiffResources.ItemOnlyFoundOnTarget, remainingFolder.Key)); contentMatch = false; } targetFolders.Clear(); } } } } } catch (Exception e) { Trace.WriteLine("Exception occurred while verifying contents match: " + e.ToString()); throw; } finally { if (SourceVCDiffProvider != null) { foreach (IVCDiffItem sourceRootDiffItem in sourceRootDiffItems) { SourceVCDiffProvider.Cleanup(sourceRootDiffItem); } } if (TargetVCDiffProvider != null) { foreach (IVCDiffItem targetRootDiffItem in targetRootDiffItems) { TargetVCDiffProvider.Cleanup(targetRootDiffItem); } } stopWatch.Stop(); m_serverDiffEngine.LogInfo(String.Format(CultureInfo.InvariantCulture, ServerDiffResources.VCServerDiffTimeToRun, stopWatch.Elapsed.TotalSeconds)); m_serverDiffEngine.LogInfo(String.Format(CultureInfo.InvariantCulture, "Processed a total of {0} source folders containing {1} files", foldersProcessed, filesProcessed)); } Trace.WriteLine("VCDiffComparer.VerifyContentsMatch result: " + contentMatch); return(contentMatch); }