// if you add Migration Engine in here you will have to fix the infinate loop public TfsMigrationClient(ITestPlanMigrationClient testPlanClient, IWorkItemMigrationClient workItemClient, IServiceProvider services, ITelemetryLogger telemetry) { _testPlanClient = testPlanClient; _workItemClient = workItemClient; _Services = services; _Telemetry = telemetry; }
private void MigrateSharedSteps(WorkItemData wiSourceL, WorkItemData wiTargetL, IWorkItemMigrationClient sourceStore, IWorkItemMigrationClient targetStore, bool save) { 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 => sourceStore.GetWorkItem(x.RelatedWorkItemId.ToString())); foreach (WorkItemData sourceSharedStep in sourceSharedSteps) { WorkItemData matchingTargetSharedStep = targetStore.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.ToWorkItem().Fields["System.ChangedBy"].Value = "Migration"; wiTargetL.ToWorkItem().Save(); } }
public void MigrateLinks(WorkItemData sourceWorkItemLinkStart, IWorkItemMigrationClient sourceWorkItemStore, WorkItemData targetWorkItemLinkStart, IWorkItemMigrationClient targetWorkItemStore, bool save = true, bool filterWorkItemsThatAlreadyExistInTarget = true, string sourceReflectedWIIdField = null) { if (ShouldCopyLinks(sourceWorkItemLinkStart, targetWorkItemLinkStart, filterWorkItemsThatAlreadyExistInTarget)) { Trace.Indent(); foreach (Link item in sourceWorkItemLinkStart.ToWorkItem().Links) { try { Log.Information("Migrating link for {sourceWorkItemLinkStartId} of type {ItemGetTypeName}", sourceWorkItemLinkStart.Id, item.GetType().Name); if (IsHyperlink(item)) { CreateHyperlink((Hyperlink)item, targetWorkItemLinkStart, save); } else if (IsRelatedLink(item)) { RelatedLink rl = (RelatedLink)item; CreateRelatedLink(sourceWorkItemLinkStart, rl, targetWorkItemLinkStart, sourceWorkItemStore, targetWorkItemStore, save, sourceReflectedWIIdField); } else if (IsExternalLink(item)) { var el = (ExternalLink)item; if (!IsBuildLink(el)) { CreateExternalLink(el, targetWorkItemLinkStart, save); } } else { UnknownLinkTypeException ex = new UnknownLinkTypeException(string.Format(" [UnknownLinkType] Unable to {0}", item.GetType().Name)); Log.Error(ex, "LinkMigrationContext"); throw ex; } } catch (WorkItemLinkValidationException ex) { sourceWorkItemLinkStart.ToWorkItem().Reset(); targetWorkItemLinkStart.ToWorkItem().Reset(); Log.Error(ex, "[WorkItemLinkValidationException] Adding link for wiSourceL={sourceWorkItemLinkStartId}", sourceWorkItemLinkStart.Id); } catch (FormatException ex) { sourceWorkItemLinkStart.ToWorkItem().Reset(); targetWorkItemLinkStart.ToWorkItem().Reset(); Log.Error(ex, "[CREATE-FAIL] Adding Link for wiSourceL={sourceWorkItemLinkStartId}", sourceWorkItemLinkStart.Id); } } } if (sourceWorkItemLinkStart.Type == "Test Case") { MigrateSharedSteps(sourceWorkItemLinkStart, targetWorkItemLinkStart, sourceWorkItemStore, targetWorkItemStore, save); } }
private void ProcessWorkItemLinks(IWorkItemMigrationClient sourceStore, IWorkItemMigrationClient targetStore, WorkItemData sourceWorkItem, WorkItemData targetWorkItem) { if (targetWorkItem != null && _config.LinkMigration && sourceWorkItem.ToWorkItem().Links.Count > 0) { TraceWriteLine(LogEventLevel.Information, "Links {SourceWorkItemLinkCount} | LinkMigrator:{LinkMigration}", new Dictionary <string, object>() { { "SourceWorkItemLinkCount", sourceWorkItem.ToWorkItem().Links.Count }, { "LinkMigration", _config.LinkMigration } }); workItemLinkEnricher.MigrateLinks(sourceWorkItem, sourceStore, targetWorkItem, targetStore, _config.LinkMigrationSaveEachAsAdded, _config.FilterWorkItemsThatAlreadyExistInTarget, Engine.Source.Config.ReflectedWorkItemIDFieldName); AddMetric("RelatedLinkCount", processWorkItemMetrics, targetWorkItem.ToWorkItem().Links.Count); int fixedLinkCount = gitRepositoryEnricher.FixExternalLinks(targetWorkItem, targetStore, sourceWorkItem, _config.LinkMigrationSaveEachAsAdded); AddMetric("FixedGitLinkCount", processWorkItemMetrics, fixedLinkCount); } }
private void ProcessWorkItemLinks(IWorkItemMigrationClient sourceStore, IWorkItemMigrationClient targetStore, MigrationTools._EngineV1.DataContracts.WorkItemData sourceWorkItem, MigrationTools._EngineV1.DataContracts.WorkItemData targetWorkItem) { if (targetWorkItem != null && _config.LinkMigration && sourceWorkItem.ToWorkItem().Links.Count > 0) { TraceWriteLine(LogEventLevel.Information, "Links {SourceWorkItemLinkCount} | LinkMigrator:{LinkMigration}", new Dictionary <string, object>() { { "SourceWorkItemLinkCount", sourceWorkItem.ToWorkItem().Links.Count }, { "LinkMigration", _config.LinkMigration } }); workItemLinkEnricher.Enrich(sourceWorkItem, targetWorkItem); AddMetric("RelatedLinkCount", processWorkItemMetrics, targetWorkItem.ToWorkItem().Links.Count); int fixedLinkCount = gitRepositoryEnricher.Enrich(sourceWorkItem, targetWorkItem); AddMetric("FixedGitLinkCount", processWorkItemMetrics, fixedLinkCount); } }
public MigrationClientMock(IWorkItemMigrationClient workItemMigrationClient) { this.workItemMigrationClient = workItemMigrationClient; }
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 void ProcessWorkItem(IWorkItemMigrationClient sourceStore, IWorkItemMigrationClient targetStore, ProjectData destProject, WorkItemData sourceWorkItem, int retryLimit = 5, int retrys = 0) { var witstopwatch = Stopwatch.StartNew(); var starttime = DateTime.Now; processWorkItemMetrics = new Dictionary <string, double>(); processWorkItemParamiters = new Dictionary <string, string>(); AddParameter("SourceURL", processWorkItemParamiters, sourceStore.Config.Collection.ToString()); AddParameter("SourceWorkItem", processWorkItemParamiters, sourceWorkItem.Id.ToString()); AddParameter("TargetURL", processWorkItemParamiters, targetStore.Config.Collection.ToString()); AddParameter("TargetProject", processWorkItemParamiters, destProject.Name); AddParameter("RetryLimit", processWorkItemParamiters, retryLimit.ToString()); AddParameter("RetryNumber", processWorkItemParamiters, retrys.ToString()); try { if (sourceWorkItem.Type != "Test Plan" || sourceWorkItem.Type != "Test Suite") { var targetWorkItem = targetStore.FindReflectedWorkItem(sourceWorkItem, false); /////////////////////////////////////////////// TraceWriteLine(LogEventLevel.Information, "Work Item has {sourceWorkItemRev} revisions and revision migration is set to {ReplayRevisions}", new Dictionary <string, object>() { { "sourceWorkItemRev", sourceWorkItem.Rev }, { "ReplayRevisions", _config.ReplayRevisions } } ); List <RevisionItem> revisionsToMigrate = RevisionsToMigrate(sourceWorkItem, targetWorkItem); if (targetWorkItem == null) { targetWorkItem = ReplayRevisions(revisionsToMigrate, sourceWorkItem, null, destProject, sourceStore, _current, targetStore); AddMetric("Revisions", processWorkItemMetrics, revisionsToMigrate.Count); } else { if (revisionsToMigrate.Count == 0) { ProcessWorkItemAttachments(sourceWorkItem, targetWorkItem, false); ProcessWorkItemLinks(sourceStore, targetStore, sourceWorkItem, targetWorkItem); TraceWriteLine(LogEventLevel.Information, "Skipping as work item exists and no revisions to sync detected"); processWorkItemMetrics.Add("Revisions", 0); } else { TraceWriteLine(LogEventLevel.Information, "Syncing as there are {revisionsToMigrateCount} revisons detected", new Dictionary <string, object>() { { "revisionsToMigrateCount", revisionsToMigrate.Count } }); targetWorkItem = ReplayRevisions(revisionsToMigrate, sourceWorkItem, targetWorkItem, destProject, sourceStore, _current, targetStore); AddMetric("Revisions", processWorkItemMetrics, revisionsToMigrate.Count); AddMetric("SyncRev", processWorkItemMetrics, revisionsToMigrate.Count); } } AddParameter("TargetWorkItem", processWorkItemParamiters, targetWorkItem.ToWorkItem().Revisions.Count.ToString()); /////////////////////////////////////////////// ProcessHTMLFieldAttachements(targetWorkItem); /////////////////////////////////////////////// /////////////////////////////////////////////////////// if (targetWorkItem != null && targetWorkItem.ToWorkItem().IsDirty) { this.SaveWorkItem(targetWorkItem); } if (targetWorkItem != null) { targetWorkItem.ToWorkItem().Close(); } if (sourceWorkItem != null) { sourceWorkItem.ToWorkItem().Close(); } } else { TraceWriteLine(LogEventLevel.Warning, "SKIP: Unable to migrate {sourceWorkItemTypeName}/{sourceWorkItemId}. Use the TestPlansAndSuitesMigrationContext after you have migrated all Test Cases. ", new Dictionary <string, object>() { { "sourceWorkItemTypeName", sourceWorkItem.Type }, { "sourceWorkItemId", sourceWorkItem.Id } }); } } catch (WebException ex) { Log.Error(ex, "Some kind of internet pipe blockage"); if (retrys < retryLimit) { TraceWriteLine(LogEventLevel.Warning, "WebException: Will retry in {retrys}s ", new Dictionary <string, object>() { { "retrys", retrys } }); System.Threading.Thread.Sleep(new TimeSpan(0, 0, retrys)); retrys++; TraceWriteLine(LogEventLevel.Warning, "RETRY {Retrys}/{RetryLimit} ", new Dictionary <string, object>() { { "Retrys", retrys }, { "RetryLimit", retryLimit } }); ProcessWorkItem(sourceStore, targetStore, destProject, sourceWorkItem, retryLimit, retrys); } else { TraceWriteLine(LogEventLevel.Error, "ERROR: Failed to create work item. Retry Limit reached "); } } catch (Exception ex) { Log.Error(ex, ex.ToString()); Telemetry.TrackRequest("ProcessWorkItem", starttime, witstopwatch.Elapsed, "502", false); throw ex; } witstopwatch.Stop(); _elapsedms += witstopwatch.ElapsedMilliseconds; processWorkItemMetrics.Add("ElapsedTimeMS", _elapsedms); var average = new TimeSpan(0, 0, 0, 0, (int)(_elapsedms / _current)); var remaining = new TimeSpan(0, 0, 0, 0, (int)(average.TotalMilliseconds * _count)); TraceWriteLine(LogEventLevel.Information, "Average time of {average:%s}.{average:%fff} per work item and {remaining:%h} hours {remaining:%m} minutes {remaining:%s}.{remaining:%fff} seconds estimated to completion", new Dictionary <string, object>() { { "average", average }, { "remaining", remaining } }); Trace.Flush(); Telemetry.TrackEvent("WorkItemMigrated", processWorkItemParamiters, processWorkItemMetrics); Telemetry.TrackRequest("ProcessWorkItem", starttime, witstopwatch.Elapsed, "200", true); _current++; _count--; }
//List<WorkItemData> GetWorkItems(string query) //{ // throw new System.NotImplementedException(); //} public List <WorkItemData> FilterWorkItemsThatAlreadyExist(List <WorkItemData> sourceWorkItems, IWorkItemMigrationClient target) { throw new System.NotImplementedException(); }
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 GetRightHandSideTargetWi(WorkItemData wiSourceL, WorkItemData wiSourceR, WorkItemData wiTargetL, IWorkItemMigrationClient targetStore, IWorkItemMigrationClient sourceStore, string sourceReflectedWIIdField) { WorkItemData wiTargetR; if (!(wiTargetL == null) && wiSourceR.ToWorkItem().Project.Name == wiTargetL.ToWorkItem().Project.Name && wiSourceR.ToWorkItem().Project.Store.TeamProjectCollection.Uri.ToString().Replace("/", "") == wiTargetL.ToWorkItem().Project.Store.TeamProjectCollection.Uri.ToString().Replace("/", "")) { // Moving to same team project as SourceR wiTargetR = wiSourceR; } else { // Moving to Other Team Project from Source wiTargetR = targetStore.FindReflectedWorkItem(wiSourceR, true, sourceReflectedWIIdField); if (wiTargetR == null) // Assume source only (other team project) { wiTargetR = wiSourceR; if (wiTargetR.ToWorkItem().Project.Store.TeamProjectCollection.Uri.ToString().Replace("/", "") != wiSourceR.ToWorkItem().Project.Store.TeamProjectCollection.Uri.ToString().Replace("/", "")) { wiTargetR = null; // Totally bogus break! as not same team collection } } } return(wiTargetR); }
private void CreateRelatedLink(WorkItemData wiSourceL, RelatedLink item, WorkItemData wiTargetL, IWorkItemMigrationClient sourceStore, IWorkItemMigrationClient targetStore, bool save, string sourceReflectedWIIdField) { RelatedLink rl = (RelatedLink)item; WorkItemData wiSourceR = null; WorkItemData wiTargetR = null; try { wiSourceR = sourceStore.GetWorkItem(rl.RelatedWorkItemId.ToString()); } catch (Exception ex) { Log.Error(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, targetStore, sourceStore, sourceReflectedWIIdField); } catch (Exception ex) { Log.Error(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.Error(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.Information(" [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); WorkItemLinkTypeEnd linkTypeEnd = ((WorkItemMigrationClient)targetStore).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 = ((WorkItemMigrationClient)targetStore).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.ToWorkItem().Save(); } 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.ToWorkItem().Save(); } } Log.Information( " [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.Information( " [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.Information(" [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.Information(" [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.Information(" [SKIP] Cant find wiTargetR where wiSourceL={0}, wiSourceR={1}, wiTargetL={2}", wiSourceL.Id, wiSourceR.Id, wiTargetL.Id); } }