private WorkItemData ReplayRevisions(List <RevisionItem> revisionsToMigrate, WorkItemData sourceWorkItem, WorkItemData targetWorkItem, ProjectData destProject, IWorkItemMigrationClient sourceStore,
                                             int current,
                                             IWorkItemMigrationClient targetStore)
        {
            try
            {
                var skipToFinalRevisedWorkItemType = _config.SkipToFinalRevisedWorkItemType;

                var last = sourceStore.GetRevision(sourceWorkItem, revisionsToMigrate.Last().Number);

                string finalDestType = last.Type;

                if (skipToFinalRevisedWorkItemType && Engine.TypeDefinitionMaps.Items.ContainsKey(finalDestType))
                {
                    finalDestType =
                        Engine.TypeDefinitionMaps.Items[finalDestType].Map();
                }

                //If work item hasn't been created yet, create a shell
                if (targetWorkItem == null)
                {
                    targetWorkItem = CreateWorkItem_Shell(destProject, sourceWorkItem, skipToFinalRevisedWorkItemType ? finalDestType : sourceStore.GetRevision(sourceWorkItem, revisionsToMigrate.First().Number).Type);
                }

                if (_config.CollapseRevisions)
                {
                    var data = revisionsToMigrate.Select(rev => sourceStore.GetRevision(sourceWorkItem, rev.Number)).Select(rev => new
                    {
                        rev.Id,
                        rev.Rev,
                        rev.RevisedDate,
                        Fields = rev.ToWorkItem().Fields.AsDictionary()
                    });

                    var fileData = JsonConvert.SerializeObject(data, new JsonSerializerSettings {
                        PreserveReferencesHandling = PreserveReferencesHandling.None
                    });
                    var filePath = Path.Combine(Path.GetTempPath(), $"{sourceWorkItem.Id}_PreMigrationHistory.json");

                    File.WriteAllText(filePath, fileData);
                    targetWorkItem.ToWorkItem().Attachments.Add(new Attachment(filePath, "History has been consolidated into the attached file."));

                    revisionsToMigrate = revisionsToMigrate.GetRange(revisionsToMigrate.Count - 1, 1);

                    TraceWriteLine(LogEventLevel.Information, " Attached a consolidated set of {RevisionCount} revisions.",
                                   new Dictionary <string, object>()
                    {
                        { "RevisionCount", data.Count() }
                    });
                }

                foreach (var revision in revisionsToMigrate)
                {
                    var currentRevisionWorkItem = sourceStore.GetRevision(sourceWorkItem, revision.Number);

                    TraceWriteLine(LogEventLevel.Information, " Processing Revision [{RevisionNumber}]",
                                   new Dictionary <string, object>()
                    {
                        { "RevisionNumber", revision.Number }
                    });

                    // Decide on WIT
                    string destType = currentRevisionWorkItem.Type;
                    if (Engine.TypeDefinitionMaps.Items.ContainsKey(destType))
                    {
                        destType =
                            Engine.TypeDefinitionMaps.Items[destType].Map();
                    }

                    //If the work item already exists and its type has changed, update its type. Done this way because there doesn't appear to be a way to do this through the store.
                    if (!skipToFinalRevisedWorkItemType && targetWorkItem.Type != finalDestType)

                    {
                        Debug.WriteLine($"Work Item type change! '{targetWorkItem.Title}': From {targetWorkItem.Type} to {destType}");
                        var typePatch = new JsonPatchOperation()
                        {
                            Operation = Microsoft.VisualStudio.Services.WebApi.Patch.Operation.Add,
                            Path      = "/fields/System.WorkItemType",
                            Value     = destType
                        };
                        var datePatch = new JsonPatchOperation()
                        {
                            Operation = Microsoft.VisualStudio.Services.WebApi.Patch.Operation.Add,
                            Path      = "/fields/System.ChangedDate",
                            Value     = currentRevisionWorkItem.ToWorkItem().Revisions[revision.Index].Fields["System.ChangedDate"].Value
                        };

                        var patchDoc = new JsonPatchDocument();
                        patchDoc.Add(typePatch);
                        patchDoc.Add(datePatch);
                        _witClient.UpdateWorkItemAsync(patchDoc, int.Parse(targetWorkItem.Id), bypassRules: true).Wait();
                    }

                    PopulateWorkItem(currentRevisionWorkItem, targetWorkItem, destType);
                    Engine.FieldMaps.ApplyFieldMappings(currentRevisionWorkItem, targetWorkItem);

                    targetWorkItem.ToWorkItem().Fields["System.ChangedBy"].Value =
                        currentRevisionWorkItem.ToWorkItem().Revisions[revision.Index].Fields["System.ChangedBy"].Value;

                    targetWorkItem.ToWorkItem().Fields["System.History"].Value =
                        currentRevisionWorkItem.ToWorkItem().Revisions[revision.Index].Fields["System.History"].Value;
                    //Debug.WriteLine("Discussion:" + currentRevisionWorkItem.Revisions[revision.Index].Fields["System.History"].Value);


                    var fails = targetWorkItem.ToWorkItem().Validate();

                    foreach (Field f in fails)
                    {
                        TraceWriteLine(LogEventLevel.Information,
                                       "{Current} - Invalid: {CurrentRevisionWorkItemId}-{CurrentRevisionWorkItemTypeName}-{FieldReferenceName}-{SourceWorkItemTitle} Value: {FieldValue}",
                                       new Dictionary <string, object>()
                        {
                            { "Current", current },
                            { "CurrentRevisionWorkItemId", currentRevisionWorkItem.Id },
                            { "CurrentRevisionWorkItemTypeName", currentRevisionWorkItem.Type },
                            { "FieldReferenceName", f.ReferenceName },
                            { "SourceWorkItemTitle", sourceWorkItem.Title },
                            { "FieldValue", f.Value }
                        });
                    }
                    //if (fails.Count > 0) // TODO Do we want to stop or continue?
                    //{
                    //    contextLog.Error("Unable to save revission due to the error in validation");
                    //    break;
                    //}

                    targetWorkItem.ToWorkItem().Save();
                    TraceWriteLine(LogEventLevel.Information,
                                   " Saved TargetWorkItem {TargetWorkItemId}. Replayed revision {RevisionNumber} of {RevisionsToMigrateCount}",
                                   new Dictionary <string, object>()
                    {
                        { "TargetWorkItemId", targetWorkItem.Id },
                        { "RevisionNumber", revision.Number },
                        { "RevisionsToMigrateCount", revisionsToMigrate.Count }
                    });
                }

                if (targetWorkItem != null)
                {
                    ProcessWorkItemAttachments(sourceWorkItem, targetWorkItem, false);
                    ProcessWorkItemLinks(sourceStore, targetStore, sourceWorkItem, targetWorkItem);
                    string reflectedUri = sourceStore.CreateReflectedWorkItemId(sourceWorkItem);
                    if (targetWorkItem.ToWorkItem().Fields.Contains(Engine.Target.Config.ReflectedWorkItemIDFieldName))
                    {
                        targetWorkItem.ToWorkItem().Fields[Engine.Target.Config.ReflectedWorkItemIDFieldName].Value = reflectedUri;
                    }
                    var history = new StringBuilder();
                    history.Append(
                        $"This work item was migrated from a different project or organization. You can find the old version at <a href=\"{reflectedUri}\">{reflectedUri}</a>.");
                    targetWorkItem.ToWorkItem().History = history.ToString();
                    this.SaveWorkItem(targetWorkItem);

                    attachmentEnricher.CleanUpAfterSave();
                    TraceWriteLine(LogEventLevel.Information, "...Saved as {TargetWorkItemId}", new Dictionary <string, object> {
                        { "TargetWorkItemId", targetWorkItem.Id }
                    });
                }
            }
            catch (Exception ex)
            {
                TraceWriteLine(LogEventLevel.Information, "...FAILED to Save");

                if (targetWorkItem != null)
                {
                    foreach (Field f in targetWorkItem.ToWorkItem().Fields)
                    {
                        TraceWriteLine(LogEventLevel.Information, "{FieldReferenceName} ({FieldName}) | {FieldValue}", new Dictionary <string, object>()
                        {
                            { "FieldReferenceName", f.ReferenceName }, { "FieldName", f.Name }, { "FieldValue", f.Value }
                        });
                    }
                }
                Log.Error(ex.ToString(), ex);
            }

            return(targetWorkItem);
        }
        private WorkItemData ReplayRevisions(List <RevisionItem> revisionsToMigrate, WorkItemData sourceWorkItem, WorkItemData targetWorkItem)
        {
            try
            {
                var skipToFinalRevisedWorkItemType = _config.SkipToFinalRevisedWorkItemType;
                var finalDestType = revisionsToMigrate.Last().Type;

                if (skipToFinalRevisedWorkItemType && Engine.TypeDefinitionMaps.Items.ContainsKey(finalDestType))
                {
                    finalDestType = Engine.TypeDefinitionMaps.Items[finalDestType].Map();
                }

                //If work item hasn't been created yet, create a shell
                if (targetWorkItem == null)
                {
                    var targetType = revisionsToMigrate.First().Type;
                    if (Engine.TypeDefinitionMaps.Items.ContainsKey(targetType))
                    {
                        targetType = Engine.TypeDefinitionMaps.Items[targetType].Map();
                    }
                    targetWorkItem = CreateWorkItem_Shell(Engine.Target.WorkItems.Project, sourceWorkItem, skipToFinalRevisedWorkItemType ? finalDestType : targetType);
                }

                if (_config.AttachRevisionHistory)
                {
                    revisionManager.AttachSourceRevisionHistoryJsonToTarget(sourceWorkItem, targetWorkItem);
                }

                foreach (var revision in revisionsToMigrate)
                {
                    var currentRevisionWorkItem = sourceWorkItem.GetRevision(revision.Number);

                    TraceWriteLine(LogEventLevel.Information, " Processing Revision [{RevisionNumber}]",
                                   new Dictionary <string, object>()
                    {
                        { "RevisionNumber", revision.Number }
                    });

                    // Decide on WIT
                    var destType = currentRevisionWorkItem.Type;
                    if (Engine.TypeDefinitionMaps.Items.ContainsKey(destType))
                    {
                        destType =
                            Engine.TypeDefinitionMaps.Items[destType].Map();
                    }

                    PopulateWorkItem(currentRevisionWorkItem, targetWorkItem, destType);

                    // Todo: Ensure all field maps use WorkItemData.Fields to apply a correct mapping
                    Engine.FieldMaps.ApplyFieldMappings(currentRevisionWorkItem, targetWorkItem);

                    // Todo: Think about an "UpdateChangedBy" flag as this is expensive! (2s/WI instead of 1,5s when writing "Migration")
                    var changedBy = targetWorkItem.ToWorkItem().Fields["System.ChangedBy"].Value.ToString();
                    targetWorkItem.ToWorkItem().Fields["System.ChangedBy"].Value = revision.Fields["System.ChangedBy"].Value;
                    targetWorkItem.ToWorkItem().Fields["System.History"].Value   = revision.Fields["System.History"].Value;

                    var reflectedUri = (TfsReflectedWorkItemId)Engine.Source.WorkItems.CreateReflectedWorkItemId(sourceWorkItem);
                    if (!targetWorkItem.ToWorkItem().Fields.Contains(Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIDFieldName))
                    {
                        var ex = new InvalidOperationException("ReflectedWorkItemIDField Field Missing");
                        Log.LogError(ex,
                                     " The WorkItemType {WorkItemType} does not have a Field called {ReflectedWorkItemID}",
                                     targetWorkItem.Type,
                                     Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIDFieldName);
                        throw ex;
                    }
                    targetWorkItem.ToWorkItem().Fields[Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIDFieldName].Value = reflectedUri.ToString();

                    ProcessHTMLFieldAttachements(targetWorkItem);
                    ProcessWorkItemEmbeddedLinks(sourceWorkItem, targetWorkItem);

                    targetWorkItem.SaveToAzureDevOps();
                    TraceWriteLine(LogEventLevel.Information,
                                   " Saved TargetWorkItem {TargetWorkItemId}. Replayed revision {RevisionNumber} of {RevisionsToMigrateCount}",
                                   new Dictionary <string, object>()
                    {
                        { "TargetWorkItemId", targetWorkItem.Id },
                        { "RevisionNumber", revision.Number },
                        { "RevisionsToMigrateCount", revisionsToMigrate.Count }
                    });

                    // Change this back to the original value as this object is mutated, and this value is needed elsewhere.
                    targetWorkItem.ToWorkItem().Fields["System.ChangedBy"].Value = changedBy;
                }

                if (targetWorkItem != null)
                {
                    ProcessWorkItemAttachments(sourceWorkItem, targetWorkItem, false);
                    if (!string.IsNullOrEmpty(targetWorkItem.Id))
                    {
                        ProcessWorkItemLinks(Engine.Source.WorkItems, Engine.Target.WorkItems, sourceWorkItem, targetWorkItem);
                    }

                    if (_config.GenerateMigrationComment)
                    {
                        var reflectedUri = targetWorkItem.ToWorkItem().Fields[Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIDFieldName].Value;
                        var history      = new StringBuilder();
                        history.Append(
                            $"This work item was migrated from a different project or organization. You can find the old version at <a href=\"{reflectedUri}\">{reflectedUri}</a>.");
                        targetWorkItem.ToWorkItem().History = history.ToString();
                    }
                    targetWorkItem.SaveToAzureDevOps();

                    attachmentEnricher.CleanUpAfterSave();
                    TraceWriteLine(LogEventLevel.Information, "...Saved as {TargetWorkItemId}", new Dictionary <string, object> {
                        { "TargetWorkItemId", targetWorkItem.Id }
                    });
                }
            }
            catch (Exception ex)
            {
                TraceWriteLine(LogEventLevel.Information, "...FAILED to Save");
                Log.LogInformation("===============================================================");
                if (targetWorkItem != null)
                {
                    foreach (Field f in targetWorkItem.ToWorkItem().Fields)
                    {
                        TraceWriteLine(LogEventLevel.Information, "{FieldReferenceName} ({FieldName}) | {FieldValue}",
                                       new Dictionary <string, object>()
                        {
                            { "FieldReferenceName", f.ReferenceName }, { "FieldName", f.Name },
                            { "FieldValue", f.Value }
                        });
                    }
                }
                Log.LogInformation("===============================================================");
                Log.LogError(ex.ToString(), ex);
                Log.LogInformation("===============================================================");
            }

            return(targetWorkItem);
        }
        private MigrationTools._EngineV1.DataContracts.WorkItemData ReplayRevisions(List <MigrationTools._EngineV1.DataContracts.RevisionItem> revisionsToMigrate, MigrationTools._EngineV1.DataContracts.WorkItemData sourceWorkItem, MigrationTools._EngineV1.DataContracts.WorkItemData targetWorkItem, int current)
        {
            try
            {
                var skipToFinalRevisedWorkItemType = _config.SkipToFinalRevisedWorkItemType;

                string finalDestType = revisionsToMigrate.Last().Type;

                if (skipToFinalRevisedWorkItemType && Engine.TypeDefinitionMaps.Items.ContainsKey(finalDestType))
                {
                    finalDestType =
                        Engine.TypeDefinitionMaps.Items[finalDestType].Map();
                }

                //If work item hasn't been created yet, create a shell
                if (targetWorkItem == null)
                {
                    string targetType = revisionsToMigrate.First().Type;
                    if (Engine.TypeDefinitionMaps.Items.ContainsKey(targetType))
                    {
                        targetType = Engine.TypeDefinitionMaps.Items[targetType].Map();
                    }
                    targetWorkItem = CreateWorkItem_Shell(Engine.Target.WorkItems.Project, sourceWorkItem, skipToFinalRevisedWorkItemType ? finalDestType : targetType);
                }

                if (_config.CollapseRevisions)
                {
                    var data = revisionsToMigrate.Select(rev =>
                    {
                        var revWi = sourceWorkItem.GetRevision(rev.Number);

                        return(new
                        {
                            revWi.Id,
                            revWi.Rev,
                            revWi.ChangedDate, // According to https://docs.microsoft.com/en-us/azure/devops/reference/xml/reportable-fields-reference?view=azure-devops-2020 Revised Date was misused here
                            revWi.Fields
                        });
                    });

                    var fileData = JsonConvert.SerializeObject(data, new JsonSerializerSettings {
                        PreserveReferencesHandling = PreserveReferencesHandling.None
                    });
                    var filePath = Path.Combine(Path.GetTempPath(), $"{sourceWorkItem.Id}_PreMigrationHistory.json");

                    // todo: Delete this file after (!) WorkItem has been saved
                    File.WriteAllText(filePath, fileData);
                    targetWorkItem.ToWorkItem().Attachments.Add(new Attachment(filePath, "History has been consolidated into the attached file."));

                    revisionsToMigrate = revisionsToMigrate.GetRange(revisionsToMigrate.Count - 1, 1);

                    TraceWriteLine(LogEventLevel.Information, " Attached a consolidated set of {RevisionCount} revisions.",
                                   new Dictionary <string, object>()
                    {
                        { "RevisionCount", data.Count() }
                    });
                }

                foreach (var revision in revisionsToMigrate)
                {
                    var currentRevisionWorkItem = sourceWorkItem.GetRevision(revision.Number);

                    TraceWriteLine(LogEventLevel.Information, " Processing Revision [{RevisionNumber}]",
                                   new Dictionary <string, object>()
                    {
                        { "RevisionNumber", revision.Number }
                    });

                    // Decide on WIT
                    string destType = currentRevisionWorkItem.Type;
                    if (Engine.TypeDefinitionMaps.Items.ContainsKey(destType))
                    {
                        destType =
                            Engine.TypeDefinitionMaps.Items[destType].Map();
                    }

                    WorkItemTypeChange(targetWorkItem, skipToFinalRevisedWorkItemType, finalDestType, revision, currentRevisionWorkItem, destType);

                    PopulateWorkItem(currentRevisionWorkItem, targetWorkItem, destType);

                    // Todo: Ensure all field maps use WorkItemData.Fields to apply a correct mapping
                    Engine.FieldMaps.ApplyFieldMappings(currentRevisionWorkItem, targetWorkItem);

                    // Todo: Think about an "UpdateChangedBy" flag as this is expensive! (2s/WI instead of 1,5s when writing "Migration")
                    targetWorkItem.ToWorkItem().Fields["System.ChangedBy"].Value = currentRevisionWorkItem.Fields["System.ChangedBy"];

                    targetWorkItem.ToWorkItem().Fields["System.History"].Value = currentRevisionWorkItem.Fields["System.History"];
                    //Debug.WriteLine("Discussion:" + currentRevisionWorkItem.Revisions[revision.Index].Fields["System.History"].Value);

                    TfsReflectedWorkItemId reflectedUri = (TfsReflectedWorkItemId)Engine.Source.WorkItems.CreateReflectedWorkItemId(sourceWorkItem);
                    if (!targetWorkItem.ToWorkItem().Fields.Contains(Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIDFieldName))
                    {
                        var ex = new InvalidOperationException("ReflectedWorkItemIDField Field Missing");
                        Log.LogError(ex, " The WorkItemType {WorkItemType} does not have a Field called {ReflectedWorkItemID}", targetWorkItem.Type, Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIDFieldName);
                        throw ex;
                    }
                    targetWorkItem.ToWorkItem().Fields[Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIDFieldName].Value = reflectedUri.ToString();

                    targetWorkItem.SaveToAzureDevOps();
                    TraceWriteLine(LogEventLevel.Information,
                                   " Saved TargetWorkItem {TargetWorkItemId}. Replayed revision {RevisionNumber} of {RevisionsToMigrateCount}",
                                   new Dictionary <string, object>()
                    {
                        { "TargetWorkItemId", targetWorkItem.Id },
                        { "RevisionNumber", revision.Number },
                        { "RevisionsToMigrateCount", revisionsToMigrate.Count }
                    });
                }

                if (targetWorkItem != null)
                {
                    ProcessWorkItemAttachments(sourceWorkItem, targetWorkItem, false);
                    ProcessWorkItemLinks(Engine.Source.WorkItems, Engine.Target.WorkItems, sourceWorkItem, targetWorkItem);

                    if (_config.GenerateMigrationComment)
                    {
                        var reflectedUri = targetWorkItem.ToWorkItem().Fields[Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIDFieldName].Value;
                        var history      = new StringBuilder();
                        history.Append(
                            $"This work item was migrated from a different project or organization. You can find the old version at <a href=\"{reflectedUri}\">{reflectedUri}</a>.");
                        targetWorkItem.ToWorkItem().History = history.ToString();
                    }
                    targetWorkItem.SaveToAzureDevOps();

                    attachmentEnricher.CleanUpAfterSave();
                    TraceWriteLine(LogEventLevel.Information, "...Saved as {TargetWorkItemId}", new Dictionary <string, object> {
                        { "TargetWorkItemId", targetWorkItem.Id }
                    });
                }
            }
            catch (Exception ex)
            {
                TraceWriteLine(LogEventLevel.Information, "...FAILED to Save");
                Log.LogInformation("===============================================================");
                if (targetWorkItem != null)
                {
                    foreach (Field f in targetWorkItem.ToWorkItem().Fields)
                    {
                        TraceWriteLine(LogEventLevel.Information, "{FieldReferenceName} ({FieldName}) | {FieldValue}", new Dictionary <string, object>()
                        {
                            { "FieldReferenceName", f.ReferenceName }, { "FieldName", f.Name }, { "FieldValue", f.Value }
                        });
                    }
                }
                Log.LogInformation("===============================================================");
                Log.LogError(ex.ToString(), ex);
                Log.LogInformation("===============================================================");
            }

            return(targetWorkItem);
        }
Exemplo n.º 4
0
        private WorkItemData ReplayRevisions(List <RevisionItem> revisionsToMigrate, WorkItemData sourceWorkItem, WorkItemData targetWorkItem, int current)
        {
            try
            {
                var skipToFinalRevisedWorkItemType = _config.SkipToFinalRevisedWorkItemType;

                var last = Engine.Source.WorkItems.GetRevision(sourceWorkItem, revisionsToMigrate.Last().Number);

                string finalDestType = last.Type;

                if (skipToFinalRevisedWorkItemType && Engine.TypeDefinitionMaps.Items.ContainsKey(finalDestType))
                {
                    finalDestType =
                        Engine.TypeDefinitionMaps.Items[finalDestType].Map();
                }

                //If work item hasn't been created yet, create a shell
                if (targetWorkItem == null)
                {
                    string targetType = Engine.Source.WorkItems.GetRevision(sourceWorkItem, revisionsToMigrate.First().Number).Type;
                    if (Engine.TypeDefinitionMaps.Items.ContainsKey(targetType))
                    {
                        targetType = Engine.TypeDefinitionMaps.Items[targetType].Map();
                    }
                    targetWorkItem = CreateWorkItem_Shell(Engine.Target.WorkItems.Project, sourceWorkItem, skipToFinalRevisedWorkItemType ? finalDestType : targetType);
                }

                if (_config.CollapseRevisions)
                {
                    var data = revisionsToMigrate.Select(rev => Engine.Source.WorkItems.GetRevision(sourceWorkItem, rev.Number)).Select(rev => new
                    {
                        rev.Id,
                        rev.Rev,
                        rev.RevisedDate,
                        Fields = rev.ToWorkItem().Fields.AsDictionary()
                    });

                    var fileData = JsonConvert.SerializeObject(data, new JsonSerializerSettings {
                        PreserveReferencesHandling = PreserveReferencesHandling.None
                    });
                    var filePath = Path.Combine(Path.GetTempPath(), $"{sourceWorkItem.Id}_PreMigrationHistory.json");

                    File.WriteAllText(filePath, fileData);
                    targetWorkItem.ToWorkItem().Attachments.Add(new Attachment(filePath, "History has been consolidated into the attached file."));

                    revisionsToMigrate = revisionsToMigrate.GetRange(revisionsToMigrate.Count - 1, 1);

                    TraceWriteLine(LogEventLevel.Information, " Attached a consolidated set of {RevisionCount} revisions.",
                                   new Dictionary <string, object>()
                    {
                        { "RevisionCount", data.Count() }
                    });
                }

                foreach (var revision in revisionsToMigrate)
                {
                    var currentRevisionWorkItem = Engine.Source.WorkItems.GetRevision(sourceWorkItem, revision.Number);

                    TraceWriteLine(LogEventLevel.Information, " Processing Revision [{RevisionNumber}]",
                                   new Dictionary <string, object>()
                    {
                        { "RevisionNumber", revision.Number }
                    });

                    // Decide on WIT
                    string destType = currentRevisionWorkItem.Type;
                    if (Engine.TypeDefinitionMaps.Items.ContainsKey(destType))
                    {
                        destType =
                            Engine.TypeDefinitionMaps.Items[destType].Map();
                    }

                    WorkItemTypeChange(targetWorkItem, skipToFinalRevisedWorkItemType, finalDestType, revision, currentRevisionWorkItem, destType);

                    PopulateWorkItem(currentRevisionWorkItem, targetWorkItem, destType);
                    Engine.FieldMaps.ApplyFieldMappings(currentRevisionWorkItem, targetWorkItem);

                    targetWorkItem.ToWorkItem().Fields["System.ChangedBy"].Value =
                        currentRevisionWorkItem.ToWorkItem().Revisions[revision.Index].Fields["System.ChangedBy"].Value;

                    targetWorkItem.ToWorkItem().Fields["System.History"].Value =
                        currentRevisionWorkItem.ToWorkItem().Revisions[revision.Index].Fields["System.History"].Value;
                    //Debug.WriteLine("Discussion:" + currentRevisionWorkItem.Revisions[revision.Index].Fields["System.History"].Value);

                    targetWorkItem.SaveToAzureDevOps();
                    TraceWriteLine(LogEventLevel.Information,
                                   " Saved TargetWorkItem {TargetWorkItemId}. Replayed revision {RevisionNumber} of {RevisionsToMigrateCount}",
                                   new Dictionary <string, object>()
                    {
                        { "TargetWorkItemId", targetWorkItem.Id },
                        { "RevisionNumber", revision.Number },
                        { "RevisionsToMigrateCount", revisionsToMigrate.Count }
                    });
                }

                if (targetWorkItem != null)
                {
                    ProcessWorkItemAttachments(sourceWorkItem, targetWorkItem, false);
                    ProcessWorkItemLinks(Engine.Source.WorkItems, Engine.Target.WorkItems, sourceWorkItem, targetWorkItem);
                    string reflectedUri = Engine.Source.WorkItems.CreateReflectedWorkItemId(sourceWorkItem);
                    if (targetWorkItem.ToWorkItem().Fields.Contains(Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIDFieldName))
                    {
                        targetWorkItem.ToWorkItem().Fields[Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIDFieldName].Value = reflectedUri;
                    }
                    if (_config.GenerateMigrationComment)
                    {
                        var history = new StringBuilder();
                        history.Append(
                            $"This work item was migrated from a different project or organization. You can find the old version at <a href=\"{reflectedUri}\">{reflectedUri}</a>.");
                        targetWorkItem.ToWorkItem().History = history.ToString();
                    }
                    targetWorkItem.SaveToAzureDevOps();

                    attachmentEnricher.CleanUpAfterSave();
                    TraceWriteLine(LogEventLevel.Information, "...Saved as {TargetWorkItemId}", new Dictionary <string, object> {
                        { "TargetWorkItemId", targetWorkItem.Id }
                    });
                }
            }
            catch (Exception ex)
            {
                TraceWriteLine(LogEventLevel.Information, "...FAILED to Save");

                if (targetWorkItem != null)
                {
                    foreach (Field f in targetWorkItem.ToWorkItem().Fields)
                    {
                        TraceWriteLine(LogEventLevel.Information, "{FieldReferenceName} ({FieldName}) | {FieldValue}", new Dictionary <string, object>()
                        {
                            { "FieldReferenceName", f.ReferenceName }, { "FieldName", f.Name }, { "FieldValue", f.Value }
                        });
                    }
                }
                Log.LogError(ex.ToString(), ex);
            }

            return(targetWorkItem);
        }