private void CreateHyperlink(Hyperlink sourceLink, WorkItemData target) { var sourceLinkAbsoluteUri = GetAbsoluteUri(sourceLink); if (string.IsNullOrEmpty(sourceLinkAbsoluteUri)) { Log.LogWarning($" [SKIP] Unable to create a hyperlink to [{sourceLink.Location}]"); return; } var exist = (from hyperlink in target.ToWorkItem().Links.Cast <Link>().Where(l => l is Hyperlink).Cast <Hyperlink>() let absoluteUri = GetAbsoluteUri(hyperlink) where sourceLinkAbsoluteUri == absoluteUri select hyperlink).SingleOrDefault(); if (exist != null) { return; } var hl = new Hyperlink(sourceLinkAbsoluteUri) // Use AbsoluteUri here as a possible \\UNC\Path\Link will be converted to file://UNC/Path/Link this way { Comment = sourceLink.Comment }; target.ToWorkItem().Links.Add(hl); if (_save) { target.SaveToAzureDevOps(); } }
private void CreateExternalLink(ExternalLink sourceLink, WorkItemData target) { var exist = (from Link l in target.ToWorkItem().Links where l is ExternalLink && ((ExternalLink)l).LinkedArtifactUri == ((ExternalLink)sourceLink).LinkedArtifactUri select(ExternalLink) l).SingleOrDefault(); if (exist == null) { Log.LogInformation("Creating new {SourceLinkType} on {TargetId}", sourceLink.GetType().Name, target.Id); ExternalLink el = new ExternalLink(sourceLink.ArtifactLinkType, sourceLink.LinkedArtifactUri) { Comment = sourceLink.Comment }; target.ToWorkItem().Links.Add(el); if (_save) { target.SaveToAzureDevOps(); } } else { Log.LogInformation("Link {SourceLinkType} on {TargetId} already exists", sourceLink.GetType().Name, target.Id); } }
private void CreateHyperlink(Hyperlink sourceLink, WorkItemData target) { var sourceLinkAbsoluteUri = GetAbsoluteUri(sourceLink); if (string.IsNullOrEmpty(sourceLinkAbsoluteUri)) { Log.LogWarning(" [SKIP] Unable to create a hyperlink to [{0}]", sourceLink.Location); return; } var exist = (from hyperlink in target.ToWorkItem().Links.OfType <Hyperlink>() let absoluteUri = GetAbsoluteUri(hyperlink) where string.Equals(sourceLinkAbsoluteUri, absoluteUri, StringComparison.OrdinalIgnoreCase) select hyperlink).Any(); if (exist) { return; } var hl = new Hyperlink(sourceLinkAbsoluteUri) // Use AbsoluteUri here as a possible \\UNC\Path\Link will be converted to file://UNC/Path/Link this way { Comment = sourceLink.Comment }; target.ToWorkItem().Links.Add(hl); if (_save) { target.SaveToAzureDevOps(); } }
private void MigrateSharedSteps(WorkItemData wiSourceL, WorkItemData wiTargetL) { const string microsoftVstsTcmSteps = "Microsoft.VSTS.TCM.Steps"; var oldSteps = wiTargetL.ToWorkItem().Fields[microsoftVstsTcmSteps].Value.ToString(); var newSteps = oldSteps; var sourceSharedStepLinks = wiSourceL.ToWorkItem().Links.OfType <RelatedLink>() .Where(x => x.LinkTypeEnd.Name == "Shared Steps").ToList(); var sourceSharedSteps = sourceSharedStepLinks.Select(x => Engine.Source.WorkItems.GetWorkItem(x.RelatedWorkItemId.ToString())); foreach (WorkItemData sourceSharedStep in sourceSharedSteps) { WorkItemData matchingTargetSharedStep = Engine.Target.WorkItems.FindReflectedWorkItemByReflectedWorkItemId(sourceSharedStep); if (matchingTargetSharedStep != null) { newSteps = newSteps.Replace($"ref=\"{sourceSharedStep.Id}\"", $"ref=\"{matchingTargetSharedStep.Id}\""); wiTargetL.ToWorkItem().Fields[microsoftVstsTcmSteps].Value = newSteps; } } if (wiTargetL.ToWorkItem().IsDirty&& _save) { wiTargetL.SaveToAzureDevOps(); } }
public void ProcessAttachemnts(WorkItemData source, WorkItemData target, bool save = true) { _exportWiPath = Path.Combine(_exportBasePath, source.ToWorkItem().Id.ToString()); if (System.IO.Directory.Exists(_exportWiPath)) { System.IO.Directory.Delete(_exportWiPath, true); } System.IO.Directory.CreateDirectory(_exportWiPath); foreach (Attachment wia in source.ToWorkItem().Attachments) { try { string filepath = null; filepath = ExportAttachment(source.ToWorkItem(), wia, _exportWiPath); Log.Information("Exported {Filename} to disk", System.IO.Path.GetFileName(filepath)); if (filepath != null) { ImportAttachemnt(target.ToWorkItem(), filepath, save); Log.Information("Imported {Filename} from disk", System.IO.Path.GetFileName(filepath)); } } catch (Exception ex) { Log.Error("ERROR: Unable to process atachment from source wi {SourceWorkItemId} called {AttachmentName}", source.ToWorkItem().Id, wia.Name, ex); } } if (save) { target.SaveToAzureDevOps(); Log.Information("Work iTem now has {AttachmentCount} attachemnts", source.ToWorkItem().Attachments.Count); CleanUpAfterSave(); } }
public void ProcessAttachemnts(WorkItemData source, WorkItemData target, bool save = true) { if (source is null) { throw new ArgumentNullException(nameof(source)); } if (target is null) { throw new ArgumentNullException(nameof(target)); } Log.Information("AttachmentMigrationEnricher: Migrating {AttachmentCount} attachemnts from {SourceWorkItemID} to {TargetWorkItemID}", source.ToWorkItem().Attachments.Count, source.Id, target.Id); _exportWiPath = Path.Combine(_exportBasePath, source.ToWorkItem().Id.ToString()); if (System.IO.Directory.Exists(_exportWiPath)) { System.IO.Directory.Delete(_exportWiPath, true); } System.IO.Directory.CreateDirectory(_exportWiPath); int count = 0; foreach (Attachment wia in source.ToWorkItem().Attachments) // TODO#1 Limit to 100 attachements { count++; if (count > 100) { break; } try { string filepath = null; System.IO.Directory.CreateDirectory(Path.Combine(_exportWiPath, wia.Id.ToString())); filepath = ExportAttachment(source.ToWorkItem(), wia, _exportWiPath); Log.Debug("AttachmentMigrationEnricher: Exported {Filename} to disk", System.IO.Path.GetFileName(filepath)); if (filepath != null) { ImportAttachemnt(target.ToWorkItem(), filepath, save); Log.Debug("AttachmentMigrationEnricher: Imported {Filename} from disk", System.IO.Path.GetFileName(filepath)); } } catch (Exception ex) { Log.Error(ex, "AttachmentMigrationEnricher:Unable to process atachment from source wi {SourceWorkItemId} called {AttachmentName}", source.ToWorkItem().Id, wia.Name); } } if (save) { target.SaveToAzureDevOps(); Log.Information("Work iTem now has {AttachmentCount} attachemnts", source.ToWorkItem().Attachments.Count); CleanUpAfterSave(); } }
private void CreateHyperlink(Hyperlink sourceLink, WorkItemData target) { var exist = (from Link l in target.ToWorkItem().Links where l is Hyperlink && ((Hyperlink)l).Location == ((Hyperlink)sourceLink).Location select(Hyperlink) l).SingleOrDefault(); if (exist == null) { Hyperlink hl = new Hyperlink(sourceLink.Location); hl.Comment = sourceLink.Comment; target.ToWorkItem().Links.Add(hl); if (_save) { target.SaveToAzureDevOps(); } } }
private void CreateExternalLink(ExternalLink sourceLink, WorkItemData target) { var exist = (from Link l in target.ToWorkItem().Links where l is ExternalLink && ((ExternalLink)l).LinkedArtifactUri == ((ExternalLink)sourceLink).LinkedArtifactUri select(ExternalLink) l).SingleOrDefault(); if (exist == null) { Log.LogInformation("Creating new {SourceLinkType} on {TargetId}", sourceLink.GetType().Name, target.Id); ExternalLink el = new ExternalLink(sourceLink.ArtifactLinkType, sourceLink.LinkedArtifactUri) { Comment = sourceLink.Comment }; target.ToWorkItem().Links.Add(el); if (_save) { try { target.SaveToAzureDevOps(); } catch (Exception ex) { // Ignore this link because the TFS server didn't recognize its type (There's no point in crashing the rest of the migration due to a link) if (ex.Message.Contains("Unrecognized Resource link")) { Log.LogError(ex, "[{ExceptionType}] Failed to save link {SourceLinkType} on {TargetId}", ex.GetType().Name, sourceLink.GetType().Name, target.Id); // Remove the link from the target so it doesn't cause problems downstream target.ToWorkItem().Links.Remove(el); } else { //pass along the exception since we don't know what went wrong throw; } } } } else { Log.LogInformation("Link {SourceLinkType} on {TargetId} already exists", sourceLink.GetType().Name, target.Id); } }
/** * from https://gist.github.com/pietergheysens/792ed505f09557e77ddfc1b83531e4fb */ public override void FixEmbededImages(WorkItemData wi, string oldTfsurl, string newTfsurl, string sourcePersonalAccessToken = "") { Debug.WriteLine($"Searching for urls: {oldTfsurl} and {GetUrlWithOppositeSchema(oldTfsurl)}"); bool wiUpdated = false; bool hasCandidates = false; var oldTfsurlOppositeSchema = GetUrlWithOppositeSchema(oldTfsurl); string regExSearchForImageUrl = "(?<=<img.*src=\")[^\"]*"; foreach (Field field in wi.ToWorkItem().Fields) { if (field.FieldDefinition.FieldType == FieldType.Html) { MatchCollection matches = Regex.Matches((string)field.Value, regExSearchForImageUrl); string regExSearchFileName = "(?<=FileName=)[^=]*"; foreach (Match match in matches) { if (match.Value.ToLower().Contains(oldTfsurl.ToLower()) || match.Value.ToLower().Contains(oldTfsurlOppositeSchema.ToLower())) { //save image locally and upload as attachment Match newFileNameMatch = Regex.Match(match.Value, regExSearchFileName, RegexOptions.IgnoreCase); if (newFileNameMatch.Success) { Trace.WriteLine($"field '{field.Name}' has match: {System.Net.WebUtility.HtmlDecode(match.Value)}"); string fullImageFilePath = Path.GetTempPath() + newFileNameMatch.Value; using (var httpClient = new HttpClient(_httpClientHandler, false)) { if (!string.IsNullOrEmpty(sourcePersonalAccessToken)) { httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", "", sourcePersonalAccessToken)))); } var result = DownloadFile(httpClient, match.Value, fullImageFilePath); if (!result.IsSuccessStatusCode) { if (_ignore404Errors && result.StatusCode == HttpStatusCode.NotFound) { Trace.WriteLine($"Image {match.Value} could not be found in WorkItem {wi.ToWorkItem().Id}, Field {field.Name}"); continue; } else { result.EnsureSuccessStatusCode(); } } } if (GetImageFormat(File.ReadAllBytes(fullImageFilePath)) == ImageFormat.unknown) { throw new Exception($"Downloaded image [{fullImageFilePath}] from Work Item [{wi.ToWorkItem().Id}] Field: [{field.Name}] could not be identified as an image. Authentication issue?"); } int attachmentIndex = wi.ToWorkItem().Attachments.Add(new Attachment(fullImageFilePath)); wi.SaveToAzureDevOps(); var newImageLink = wi.ToWorkItem().Attachments[attachmentIndex].Uri.ToString(); field.Value = field.Value.ToString().Replace(match.Value, newImageLink); wi.ToWorkItem().Attachments.RemoveAt(attachmentIndex); wi.SaveToAzureDevOps(); wiUpdated = true; File.Delete(fullImageFilePath); } } } } } }
private void CreateRelatedLink(WorkItemData wiSourceL, RelatedLink item, WorkItemData wiTargetL) { RelatedLink rl = (RelatedLink)item; WorkItemData wiSourceR = null; WorkItemData wiTargetR = null; try { wiSourceR = Engine.Source.WorkItems.GetWorkItem(rl.RelatedWorkItemId.ToString()); } catch (Exception ex) { Log.LogError(ex, " [FIND-FAIL] Adding Link of type {0} where wiSourceL={1}, wiTargetL={2} ", rl.LinkTypeEnd.ImmutableName, wiSourceL.Id, wiTargetL.Id); return; } try { wiTargetR = GetRightHandSideTargetWi(wiSourceL, wiSourceR, wiTargetL); } catch (Exception ex) { Log.LogError(ex, " [FIND-FAIL] Adding Link of type {0} where wiSourceL={1}, wiTargetL={2} ", rl.LinkTypeEnd.ImmutableName, wiSourceL.Id, wiTargetL.Id); return; } if (wiTargetR != null) { bool IsExisting = false; try { var exist = ( from Link l in wiTargetL.ToWorkItem().Links where l is RelatedLink && ((RelatedLink)l).RelatedWorkItemId.ToString() == wiTargetR.Id && ((RelatedLink)l).LinkTypeEnd.ImmutableName == item.LinkTypeEnd.ImmutableName select(RelatedLink) l).SingleOrDefault(); IsExisting = (exist != null); } catch (Exception ex) { Log.LogError(ex, " [SKIP] Unable to migrate links where wiSourceL={0}, wiSourceR={1}, wiTargetL={2}", ((wiSourceL != null) ? wiSourceL.Id.ToString() : "NotFound"), ((wiSourceR != null) ? wiSourceR.Id.ToString() : "NotFound"), ((wiTargetL != null) ? wiTargetL.Id.ToString() : "NotFound")); return; } if (!IsExisting && !wiTargetR.ToWorkItem().IsAccessDenied) { if (wiSourceR.Id != wiTargetR.Id) { Log.LogInformation(" [CREATE-START] Adding Link of type {0} where wiSourceL={1}, wiSourceR={2}, wiTargetL={3}, wiTargetR={4} ", rl.LinkTypeEnd.ImmutableName, wiSourceL.Id, wiSourceR.Id, wiTargetL.Id, wiTargetR.Id); var client = (TfsWorkItemMigrationClient)Engine.Target.WorkItems; if (!client.Store.WorkItemLinkTypes.LinkTypeEnds.Contains(rl.LinkTypeEnd.ImmutableName)) { Log.LogError($" [SKIP] Unable to migrate Link because type {rl.LinkTypeEnd.ImmutableName} does not exist in the target project."); return; } WorkItemLinkTypeEnd linkTypeEnd = client.Store.WorkItemLinkTypes.LinkTypeEnds[rl.LinkTypeEnd.ImmutableName]; RelatedLink newRl = new RelatedLink(linkTypeEnd, int.Parse(wiTargetR.Id)); if (linkTypeEnd.ImmutableName == "System.LinkTypes.Hierarchy-Forward") { var potentialParentConflictLink = ( // TF201036: You cannot add a Child link between work items xxx and xxx because a work item can have only one Parent link. from Link l in wiTargetR.ToWorkItem().Links where l is RelatedLink && ((RelatedLink)l).LinkTypeEnd.ImmutableName == "System.LinkTypes.Hierarchy-Reverse" select(RelatedLink) l).SingleOrDefault(); if (potentialParentConflictLink != null) { wiTargetR.ToWorkItem().Links.Remove(potentialParentConflictLink); } linkTypeEnd = ((TfsWorkItemMigrationClient)Engine.Target.WorkItems).Store.WorkItemLinkTypes.LinkTypeEnds["System.LinkTypes.Hierarchy-Reverse"]; RelatedLink newLl = new RelatedLink(linkTypeEnd, int.Parse(wiTargetL.Id)); wiTargetR.ToWorkItem().Links.Add(newLl); wiTargetR.ToWorkItem().Fields["System.ChangedBy"].Value = "Migration"; wiTargetR.SaveToAzureDevOps(); } else { if (linkTypeEnd.ImmutableName == "System.LinkTypes.Hierarchy-Reverse") { var potentialParentConflictLink = ( // TF201065: You can not add a Parent link to this work item because a work item can have only one link of this type. from Link l in wiTargetL.ToWorkItem().Links where l is RelatedLink && ((RelatedLink)l).LinkTypeEnd.ImmutableName == "System.LinkTypes.Hierarchy-Reverse" select(RelatedLink) l).SingleOrDefault(); if (potentialParentConflictLink != null) { wiTargetL.ToWorkItem().Links.Remove(potentialParentConflictLink); } } wiTargetL.ToWorkItem().Links.Add(newRl); if (_save) { wiTargetL.ToWorkItem().Fields["System.ChangedBy"].Value = "Migration"; wiTargetL.SaveToAzureDevOps(); } } Log.LogInformation( " [CREATE-SUCCESS] Adding Link of type {0} where wiSourceL={1}, wiSourceR={2}, wiTargetL={3}, wiTargetR={4} ", rl.LinkTypeEnd.ImmutableName, wiSourceL.Id, wiSourceR.Id, wiTargetL.Id, wiTargetR.Id); } else { Log.LogInformation( " [SKIP] Unable to migrate link where Link of type {0} where wiSourceL={1}, wiSourceR={2}, wiTargetL={3}, wiTargetR={4} as target WI has not been migrated", rl.LinkTypeEnd.ImmutableName, wiSourceL.Id, wiSourceR.Id, wiTargetL.Id, wiTargetR.Id); } } else { if (IsExisting) { Log.LogInformation(" [SKIP] Already Exists a Link of type {0} where wiSourceL={1}, wiSourceR={2}, wiTargetL={3}, wiTargetR={4} ", rl.LinkTypeEnd.ImmutableName, wiSourceL.Id, wiSourceR.Id, wiTargetL.Id, wiTargetR.Id); } if (wiTargetR.ToWorkItem().IsAccessDenied) { Log.LogInformation(" [AccessDenied] The Target work item is inaccessable to create a Link of type {0} where wiSourceL={1}, wiSourceR={2}, wiTargetL={3}, wiTargetR={4} ", rl.LinkTypeEnd.ImmutableName, wiSourceL.Id, wiSourceR.Id, wiTargetL.Id, wiTargetR.Id); } } } else { Log.LogInformation(" [SKIP] Cant find wiTargetR where wiSourceL={0}, wiSourceR={1}, wiTargetL={2}", wiSourceL.Id, wiSourceR.Id, wiTargetL.Id); } }
public int FixExternalLinks(WorkItemData targetWorkItem, IWorkItemMigrationClient targetStore, WorkItemData sourceWorkItem, bool save = true) { List <ExternalLink> newEL = new List <ExternalLink>(); List <ExternalLink> removeEL = new List <ExternalLink>(); int count = 0; foreach (Link l in targetWorkItem.ToWorkItem().Links) { if (l is ExternalLink && gitWits.Contains(l.ArtifactLinkType.Name)) { ExternalLink el = (ExternalLink)l; GitRepositoryInfo sourceRepoInfo = GitRepositoryInfo.Create(el, sourceRepos, migrationEngine, sourceWorkItem?.ProjectName); // if sourceRepo is null ignore this link and keep processing further links if (sourceRepoInfo == null) { continue; } // if repo was not found in source project, try to find it by repoId in the whole project collection if (sourceRepoInfo.GitRepo == null) { var anyProjectSourceRepoInfo = GitRepositoryInfo.Create(el, allSourceRepos, migrationEngine, sourceWorkItem?.ProjectName); // if repo is found in a different project and the repo Name is listed in repo mappings, use it if (anyProjectSourceRepoInfo.GitRepo != null && migrationEngine.GitRepoMaps.Items.ContainsKey(anyProjectSourceRepoInfo.GitRepo.Name)) { sourceRepoInfo = anyProjectSourceRepoInfo; } else { Trace.WriteLine($"FAIL could not find source git repo - repo referenced: {anyProjectSourceRepoInfo?.GitRepo?.ProjectReference?.Name}/{anyProjectSourceRepoInfo?.GitRepo?.Name}"); } } if (sourceRepoInfo.GitRepo != null) { string targetRepoName = GetTargetRepoName(migrationEngine.GitRepoMaps.Items, sourceRepoInfo); string sourceProjectName = sourceRepoInfo?.GitRepo?.ProjectReference?.Name ?? migrationEngine.Target.Config.Project; string targetProjectName = migrationEngine.Target.Config.Project; GitRepositoryInfo targetRepoInfo = GitRepositoryInfo.Create(targetRepoName, sourceRepoInfo, targetRepos); // if repo was not found in the target project, try to find it in the whole target project collection if (targetRepoInfo.GitRepo == null) { if (migrationEngine.GitRepoMaps.Items.Values.Contains(targetRepoName)) { var anyTargetRepoInCollectionInfo = GitRepositoryInfo.Create(targetRepoName, sourceRepoInfo, allTargetRepos); if (anyTargetRepoInCollectionInfo.GitRepo != null) { targetRepoInfo = anyTargetRepoInCollectionInfo; } } } // Fix commit links if target repo has been found if (targetRepoInfo.GitRepo != null) { Trace.WriteLine($"Fixing {sourceRepoInfo.GitRepo.RemoteUrl} to {targetRepoInfo.GitRepo.RemoteUrl}?"); // Create External Link object ExternalLink newLink = null; switch (l.ArtifactLinkType.Name) { case "Branch": newLink = new ExternalLink(((WorkItemMigrationClient)targetStore).Store.RegisteredLinkTypes[ArtifactLinkIds.Branch], $"vstfs:///git/ref/{targetRepoInfo.GitRepo.ProjectReference.Id}%2f{targetRepoInfo.GitRepo.Id}%2f{sourceRepoInfo.CommitID}"); break; case "Fixed in Changeset": // TFVC case "Fixed in Commit": newLink = new ExternalLink(((WorkItemMigrationClient)targetStore).Store.RegisteredLinkTypes[ArtifactLinkIds.Commit], $"vstfs:///git/commit/{targetRepoInfo.GitRepo.ProjectReference.Id}%2f{targetRepoInfo.GitRepo.Id}%2f{sourceRepoInfo.CommitID}"); break; case "Pull Request": //newLink = new ExternalLink(targetStore.Store.RegisteredLinkTypes[ArtifactLinkIds.PullRequest], // $"vstfs:///Git/PullRequestId/{targetRepoInfo.GitRepo.ProjectReference.Id}%2f{targetRepoInfo.GitRepo.Id}%2f{sourceRepoInfo.CommitID}"); removeEL.Add(el); break; default: Trace.WriteLine(String.Format("Skipping unsupported link type {0}", l.ArtifactLinkType.Name)); break; } if (newLink != null) { var elinks = from Link lq in targetWorkItem.ToWorkItem().Links where gitWits.Contains(lq.ArtifactLinkType.Name) select(ExternalLink) lq; var found = (from Link lq in elinks where (((ExternalLink)lq).LinkedArtifactUri.ToLower() == newLink.LinkedArtifactUri.ToLower()) select lq).SingleOrDefault(); if (found == null) { newEL.Add(newLink); } removeEL.Add(el); } } else { Trace.WriteLine($"FAIL: cannot map {sourceRepoInfo.GitRepo.RemoteUrl} to ???"); } } } } // add and remove foreach (ExternalLink eln in newEL) { try { Trace.WriteLine("Adding " + eln.LinkedArtifactUri); targetWorkItem.ToWorkItem().Links.Add(eln); } catch (Exception) { // eat exception as sometimes TFS thinks this is an attachment } } foreach (ExternalLink elr in removeEL) { if (targetWorkItem.ToWorkItem().Links.Contains(elr)) { try { Trace.WriteLine("Removing " + elr.LinkedArtifactUri); targetWorkItem.ToWorkItem().Links.Remove(elr); count++; } catch (Exception) { // eat exception as sometimes TFS thinks this is an attachment } } } if (targetWorkItem.ToWorkItem().IsDirty&& save) { Trace.WriteLine($"Saving {targetWorkItem.Id}"); targetWorkItem.ToWorkItem().Fields["System.ChangedBy"].Value = "Migration"; targetWorkItem.SaveToAzureDevOps(); } return(count); }
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 WorkItemData ReplayRevisions(List <RevisionItem> revisionsToMigrate, WorkItemData sourceWorkItem, 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, Rev = revWi.Rev, RevisedDate = revWi.ChangedDate, 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); 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); }
public override int Enrich(WorkItemData sourceWorkItem, WorkItemData targetWorkItem) { if (sourceWorkItem is null) { throw new ArgumentNullException(nameof(sourceWorkItem)); } if (targetWorkItem is null) { throw new ArgumentNullException(nameof(targetWorkItem)); } Log.LogInformation("GitRepositoryEnricher: Enriching {Id} To fix Git Repo Links", targetWorkItem.Id); List <ExternalLink> newEL = new List <ExternalLink>(); List <ExternalLink> removeEL = new List <ExternalLink>(); int count = 0; foreach (Link l in targetWorkItem.ToWorkItem().Links) { if (l is ExternalLink && gitWits.Contains(l.ArtifactLinkType.Name)) { ExternalLink el = (ExternalLink)l; TfsGitRepositoryInfo sourceRepoInfo = TfsGitRepositoryInfo.Create(el, sourceRepos, Engine, sourceWorkItem?.ProjectName); // if sourceRepo is null ignore this link and keep processing further links if (sourceRepoInfo == null) { continue; } // if repo was not found in source project, try to find it by repoId in the whole project collection if (sourceRepoInfo.GitRepo == null) { var anyProjectSourceRepoInfo = TfsGitRepositoryInfo.Create(el, allSourceRepos, Engine, sourceWorkItem?.ProjectName); // if repo is found in a different project and the repo Name is listed in repo mappings, use it if (anyProjectSourceRepoInfo.GitRepo != null && Engine.GitRepoMaps.Items.ContainsKey(anyProjectSourceRepoInfo.GitRepo.Name)) { sourceRepoInfo = anyProjectSourceRepoInfo; } else { Log.LogWarning("GitRepositoryEnricher: Could not find source git repo - repo referenced: {SourceRepoName}/{TargetRepoName}", anyProjectSourceRepoInfo?.GitRepo?.ProjectReference?.Name, anyProjectSourceRepoInfo?.GitRepo?.Name); } } if (sourceRepoInfo.GitRepo != null) { string targetRepoName = GetTargetRepoName(Engine.GitRepoMaps.Items, sourceRepoInfo); string sourceProjectName = sourceRepoInfo?.GitRepo?.ProjectReference?.Name ?? Engine.Target.Config.AsTeamProjectConfig().Project; string targetProjectName = Engine.Target.Config.AsTeamProjectConfig().Project; TfsGitRepositoryInfo targetRepoInfo = TfsGitRepositoryInfo.Create(targetRepoName, sourceRepoInfo, targetRepos); // if repo was not found in the target project, try to find it in the whole target project collection if (targetRepoInfo.GitRepo == null) { if (Engine.GitRepoMaps.Items.Values.Contains(targetRepoName)) { var anyTargetRepoInCollectionInfo = TfsGitRepositoryInfo.Create(targetRepoName, sourceRepoInfo, allTargetRepos); if (anyTargetRepoInCollectionInfo.GitRepo != null) { targetRepoInfo = anyTargetRepoInCollectionInfo; } } } // Fix commit links if target repo has been found if (targetRepoInfo.GitRepo != null) { Log.LogDebug("GitRepositoryEnricher: Fixing {SourceRepoUrl} to {TargetRepoUrl}?", sourceRepoInfo.GitRepo.RemoteUrl, targetRepoInfo.GitRepo.RemoteUrl); // Create External Link object ExternalLink newLink = null; switch (l.ArtifactLinkType.Name) { case "Branch": newLink = new ExternalLink(((TfsWorkItemMigrationClient)Engine.Target.WorkItems).Store.RegisteredLinkTypes[ArtifactLinkIds.Branch], $"vstfs:///git/ref/{targetRepoInfo.GitRepo.ProjectReference.Id}%2f{targetRepoInfo.GitRepo.Id}%2f{sourceRepoInfo.CommitID}"); break; case "Fixed in Changeset": // TFVC case "Fixed in Commit": newLink = new ExternalLink(((TfsWorkItemMigrationClient)Engine.Target.WorkItems).Store.RegisteredLinkTypes[ArtifactLinkIds.Commit], $"vstfs:///git/commit/{targetRepoInfo.GitRepo.ProjectReference.Id}%2f{targetRepoInfo.GitRepo.Id}%2f{sourceRepoInfo.CommitID}"); break; case "Pull Request": //newLink = new ExternalLink(targetStore.Store.RegisteredLinkTypes[ArtifactLinkIds.PullRequest], // $"vstfs:///Git/PullRequestId/{targetRepoInfo.GitRepo.ProjectReference.Id}%2f{targetRepoInfo.GitRepo.Id}%2f{sourceRepoInfo.CommitID}"); removeEL.Add(el); break; default: Log.LogWarning("Skipping unsupported link type {0}", l.ArtifactLinkType.Name); break; } if (newLink != null) { var elinks = from Link lq in targetWorkItem.ToWorkItem().Links where gitWits.Contains(lq.ArtifactLinkType.Name) select(ExternalLink) lq; var found = (from Link lq in elinks where (((ExternalLink)lq).LinkedArtifactUri.ToLower() == newLink.LinkedArtifactUri.ToLower()) select lq).SingleOrDefault(); if (found == null) { newEL.Add(newLink); } removeEL.Add(el); } } else { Log.LogWarning("GitRepositoryEnricher: Target Repo not found. Cannot map {SourceRepoUrl} to ???", sourceRepoInfo.GitRepo.RemoteUrl); } } } } // add and remove foreach (ExternalLink eln in newEL) { try { Log.LogDebug("GitRepositoryEnricher: Adding {LinkedArtifactUri} ", eln.LinkedArtifactUri); targetWorkItem.ToWorkItem().Links.Add(eln); } catch (Exception) { // eat exception as sometimes TFS thinks this is an attachment } } foreach (ExternalLink elr in removeEL) { if (targetWorkItem.ToWorkItem().Links.Contains(elr)) { try { Log.LogDebug("GitRepositoryEnricher: Removing {LinkedArtifactUri} ", elr.LinkedArtifactUri); targetWorkItem.ToWorkItem().Links.Remove(elr); count++; } catch (Exception) { // eat exception as sometimes TFS thinks this is an attachment } } } if (targetWorkItem.ToWorkItem().IsDirty&& _save) { Log.LogDebug("GitRepositoryEnricher: Saving {TargetWorkItemId}", targetWorkItem.Id); targetWorkItem.ToWorkItem().Fields["System.ChangedBy"].Value = "Migration"; targetWorkItem.SaveToAzureDevOps(); } return(count); }
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.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(); 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); }