From 735e7b1d66c8eb6607e377cc0003ca2f3daa4f5e Mon Sep 17 00:00:00 2001 From: Alexander Hjelm Date: Tue, 3 Oct 2023 20:13:52 +0200 Subject: [PATCH 1/4] Fix error where Epic Child links would not get deleted properly --- .../JiraExport/RevisionUtils/LinkMapperUtils.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/WorkItemMigrator/JiraExport/RevisionUtils/LinkMapperUtils.cs b/src/WorkItemMigrator/JiraExport/RevisionUtils/LinkMapperUtils.cs index f99c9590..1ef9521a 100644 --- a/src/WorkItemMigrator/JiraExport/RevisionUtils/LinkMapperUtils.cs +++ b/src/WorkItemMigrator/JiraExport/RevisionUtils/LinkMapperUtils.cs @@ -28,8 +28,11 @@ public static void MapEpicChildLink(JiraRevision r, List links, string f var parentKeyStr = r.OriginId.Substring(r.OriginId.LastIndexOf("-", StringComparison.InvariantCultureIgnoreCase) + 1); var childKeyStr = value?.ToString().Substring((value?.ToString()).LastIndexOf("-", StringComparison.InvariantCultureIgnoreCase) + 1); - if (int.TryParse(parentKeyStr, out var parentKey) && int.TryParse(childKeyStr, out var childKey) && parentKey > childKey) - AddSingleLink(r, links, field, type, config); + if (int.TryParse(parentKeyStr, out var parentKey) && int.TryParse(childKeyStr, out var childKey) && parentKey > childKey + || childKeyStr == null) // For a removed EpicChildLink, the value will be null + { + AddRemoveSingleLink(r, links, field, type, config); + } } } From 60ce2b381196986e2e1509a176a5b9a3f27ea16d Mon Sep 17 00:00:00 2001 From: Alexander Hjelm Date: Tue, 3 Oct 2023 22:01:23 +0200 Subject: [PATCH 2/4] Correct revision time for revisions with only one removed link --- src/WorkItemMigrator/JiraExport/JiraMapper.cs | 23 ++++++++++++++++++- .../RevisionUtils/LinkMapperUtils.cs | 2 +- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/WorkItemMigrator/JiraExport/JiraMapper.cs b/src/WorkItemMigrator/JiraExport/JiraMapper.cs index 901d469f..f38b1556 100644 --- a/src/WorkItemMigrator/JiraExport/JiraMapper.cs +++ b/src/WorkItemMigrator/JiraExport/JiraMapper.cs @@ -338,11 +338,13 @@ internal WiRevision MapRevision(JiraRevision r) List links = MapLinks(r); var commit = MapCommit(r); + DateTime rTime = CorrectTime(r, attachments, fields, links); + return new WiRevision() { ParentOriginId = r.ParentItem.Key, Index = r.Index, - Time = r.Time, + Time = rTime, Author = MapUser(r.Author), Attachments = attachments, Fields = fields, @@ -361,6 +363,25 @@ protected override string MapUser(string sourceUser) return base.MapUser(email); } + protected DateTime CorrectTime(JiraRevision r, List attachments, List fields, List links) + { + var rTime = r.Time; + + // If the only change is to remove a link, remove 1 second to ensure that Link Removals happen before + // the corresponding Link Additions, since this can be a problem in the raw jira data + if ( + fields.Count == 0 + && attachments.Count == 0 + && links.Count == 1 + && links[0].Change == ReferenceChangeType.Removed + ) + { + rTime = rTime.AddSeconds(-1); + } + + return rTime; + } + private HashSet InitializeTypeMappings() { HashSet types = new HashSet(); diff --git a/src/WorkItemMigrator/JiraExport/RevisionUtils/LinkMapperUtils.cs b/src/WorkItemMigrator/JiraExport/RevisionUtils/LinkMapperUtils.cs index 1ef9521a..9bb4894a 100644 --- a/src/WorkItemMigrator/JiraExport/RevisionUtils/LinkMapperUtils.cs +++ b/src/WorkItemMigrator/JiraExport/RevisionUtils/LinkMapperUtils.cs @@ -89,7 +89,7 @@ public static void AddRemoveSingleLink(JiraRevision r, List links, strin Change = changeType, SourceOriginId = r.ParentItem.Key, TargetOriginId = linkedItemKey, - WiType = linkType, + WiType = linkType }; links.Add(link); From 8fbf471e34f6acba2684a2761caf7275fe906332 Mon Sep 17 00:00:00 2001 From: Alexander Hjelm Date: Wed, 4 Oct 2023 10:14:31 +0200 Subject: [PATCH 3/4] Update fix revision dates after import, once we have access to all revisions --- .../JiraExport/JiraCommandLine.cs | 67 ++++++++++++++++++- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/src/WorkItemMigrator/JiraExport/JiraCommandLine.cs b/src/WorkItemMigrator/JiraExport/JiraCommandLine.cs index a48d4a3a..45178b26 100644 --- a/src/WorkItemMigrator/JiraExport/JiraCommandLine.cs +++ b/src/WorkItemMigrator/JiraExport/JiraCommandLine.cs @@ -120,6 +120,8 @@ private void ExecuteMigration(CommandOption user, CommandOption password, Comman var issues = jiraProvider.EnumerateIssues(jiraSettings.JQL, skips, downloadOptions); + var createdWorkItems = new List(); + foreach (var issue in issues) { if (issue == null) @@ -128,11 +130,18 @@ private void ExecuteMigration(CommandOption user, CommandOption password, Comman WiItem wiItem = mapper.Map(issue); if (wiItem != null) { - localProvider.Save(wiItem); - exportedItemsCount++; - Logger.Log(LogLevel.Debug, $"Exported as type '{wiItem.Type}'."); + createdWorkItems.Add(wiItem); } } + + FixRevisionDates(createdWorkItems); + + foreach (var wiItem in createdWorkItems) + { + localProvider.Save(wiItem); + exportedItemsCount++; + Logger.Log(LogLevel.Debug, $"Exported as type '{wiItem.Type}'."); + } } catch (CommandParsingException e) { @@ -148,6 +157,58 @@ private void ExecuteMigration(CommandOption user, CommandOption password, Comman } } + private static void FixRevisionDates(List createdWorkItems) + { + var revisionsWithLinkChanges = new List(); + foreach (var wiItem in createdWorkItems) + { + revisionsWithLinkChanges.AddRange(wiItem.Revisions.Where(r => r.Links != null && r.Links.Count != 0)); + } + bool anyRevisionTimeUpdated = true; + while (anyRevisionTimeUpdated) + { + anyRevisionTimeUpdated = false; + foreach (var rev1 in revisionsWithLinkChanges) + { + var rev1LinkSourceWiIds = rev1.Links.Select(l => l.SourceOriginId).ToList(); + var revsWithOppositeLink = revisionsWithLinkChanges.Where( + r => r.Links.Any(l => rev1LinkSourceWiIds.Contains(l.TargetOriginId)) + ); + foreach (var rev2 in revsWithOppositeLink) + { + if (rev2 != rev1) + { + foreach (var link1 in rev1.Links) + { + foreach (var link2 in rev2.Links) + { + if (link1.SourceOriginId == link2.TargetOriginId || link1.TargetOriginId == link2.SourceOriginId) + { + if (link1.Change == ReferenceChangeType.Added && link2.Change == ReferenceChangeType.Removed + && rev1.Time < rev2.Time && Math.Abs((rev1.Time - rev2.Time).TotalSeconds) < 2 + && link1.WiType == "System.LinkTypes.Hierarchy-Forward" && link2.WiType == "System.LinkTypes.Hierarchy-Reverse") + { + // rev1 should be moved back by 1 second + rev2.Time = rev2.Time.AddSeconds(-1); + anyRevisionTimeUpdated = true; + } + else if (link1.Change == ReferenceChangeType.Removed && link2.Change == ReferenceChangeType.Added + && rev1.Time > rev2.Time && Math.Abs((rev1.Time - rev2.Time).TotalSeconds) < 2 + && link1.WiType == "System.LinkTypes.Hierarchy-Reverse" && link2.WiType == "System.LinkTypes.Hierarchy-Forward") + { + // rev2 should be moved back by 1 second + rev1.Time = rev1.Time.AddSeconds(-1); + anyRevisionTimeUpdated = true; + } + } + } + } + } + } + } + } + } + private static void InitSession(ConfigJson config, string continueOnCritical) { Logger.Init("jira-export", config.Workspace, config.LogLevel, continueOnCritical); From 3d5666b6a392d4cb845cf4b7528c96a891d0dd77 Mon Sep 17 00:00:00 2001 From: Alexander Hjelm Date: Wed, 4 Oct 2023 10:31:00 +0200 Subject: [PATCH 4/4] Revert changes in JiraMapper and LinkMapperUtils, incorrect solution --- src/WorkItemMigrator/JiraExport/JiraMapper.cs | 23 +------------------ .../RevisionUtils/LinkMapperUtils.cs | 7 ++---- 2 files changed, 3 insertions(+), 27 deletions(-) diff --git a/src/WorkItemMigrator/JiraExport/JiraMapper.cs b/src/WorkItemMigrator/JiraExport/JiraMapper.cs index f38b1556..901d469f 100644 --- a/src/WorkItemMigrator/JiraExport/JiraMapper.cs +++ b/src/WorkItemMigrator/JiraExport/JiraMapper.cs @@ -338,13 +338,11 @@ internal WiRevision MapRevision(JiraRevision r) List links = MapLinks(r); var commit = MapCommit(r); - DateTime rTime = CorrectTime(r, attachments, fields, links); - return new WiRevision() { ParentOriginId = r.ParentItem.Key, Index = r.Index, - Time = rTime, + Time = r.Time, Author = MapUser(r.Author), Attachments = attachments, Fields = fields, @@ -363,25 +361,6 @@ protected override string MapUser(string sourceUser) return base.MapUser(email); } - protected DateTime CorrectTime(JiraRevision r, List attachments, List fields, List links) - { - var rTime = r.Time; - - // If the only change is to remove a link, remove 1 second to ensure that Link Removals happen before - // the corresponding Link Additions, since this can be a problem in the raw jira data - if ( - fields.Count == 0 - && attachments.Count == 0 - && links.Count == 1 - && links[0].Change == ReferenceChangeType.Removed - ) - { - rTime = rTime.AddSeconds(-1); - } - - return rTime; - } - private HashSet InitializeTypeMappings() { HashSet types = new HashSet(); diff --git a/src/WorkItemMigrator/JiraExport/RevisionUtils/LinkMapperUtils.cs b/src/WorkItemMigrator/JiraExport/RevisionUtils/LinkMapperUtils.cs index 9bb4894a..7f743d34 100644 --- a/src/WorkItemMigrator/JiraExport/RevisionUtils/LinkMapperUtils.cs +++ b/src/WorkItemMigrator/JiraExport/RevisionUtils/LinkMapperUtils.cs @@ -28,11 +28,8 @@ public static void MapEpicChildLink(JiraRevision r, List links, string f var parentKeyStr = r.OriginId.Substring(r.OriginId.LastIndexOf("-", StringComparison.InvariantCultureIgnoreCase) + 1); var childKeyStr = value?.ToString().Substring((value?.ToString()).LastIndexOf("-", StringComparison.InvariantCultureIgnoreCase) + 1); - if (int.TryParse(parentKeyStr, out var parentKey) && int.TryParse(childKeyStr, out var childKey) && parentKey > childKey - || childKeyStr == null) // For a removed EpicChildLink, the value will be null - { - AddRemoveSingleLink(r, links, field, type, config); - } + if (int.TryParse(parentKeyStr, out var parentKey) && int.TryParse(childKeyStr, out var childKey) && parentKey > childKey) + AddSingleLink(r, links, field, type, config); } }