diff --git a/src/WorkItemMigrator/JiraExport/JiraCommandLine.cs b/src/WorkItemMigrator/JiraExport/JiraCommandLine.cs index d5d64f83..e098258c 100644 --- a/src/WorkItemMigrator/JiraExport/JiraCommandLine.cs +++ b/src/WorkItemMigrator/JiraExport/JiraCommandLine.cs @@ -91,7 +91,7 @@ private bool ExecuteMigration(CommandOption user, CommandOption password, Comman AttachmentsDir = Path.Combine(migrationWorkspace, config.AttachmentsFolder), JQL = config.Query, UsingJiraCloud = config.UsingJiraCloud, - IncludeCommits = config.IncludeCommits, + IncludeDevelopmentLinks = config.IncludeDevelopmentLinks, RepositoryMap = config.RepositoryMap }; diff --git a/src/WorkItemMigrator/JiraExport/JiraCommit.cs b/src/WorkItemMigrator/JiraExport/JiraCommit.cs deleted file mode 100644 index e259c053..00000000 --- a/src/WorkItemMigrator/JiraExport/JiraCommit.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace JiraExport -{ - public class JiraCommit - { - public string Repository { get; set; } - public string Id { get; set; } - public DateTime AuthorTimestamp { get; set; } - } -} \ No newline at end of file diff --git a/src/WorkItemMigrator/JiraExport/JiraDevelopmentLink.cs b/src/WorkItemMigrator/JiraExport/JiraDevelopmentLink.cs new file mode 100644 index 00000000..510ddc0d --- /dev/null +++ b/src/WorkItemMigrator/JiraExport/JiraDevelopmentLink.cs @@ -0,0 +1,26 @@ +using System; + +namespace JiraExport +{ + public class JiraDevelopmentLink + { + public enum DevelopmentLinkType + { + Commit, + Branch + } + + public string Repository { get; private set; } + public string Id { get; private set; } + public DateTime AuthorTimestamp { get; private set; } + public DevelopmentLinkType Type { get; private set; } + + public JiraDevelopmentLink(string repository, string id, DateTime authorTimestamp, DevelopmentLinkType type) + { + Repository = repository; + Id = id; + AuthorTimestamp = authorTimestamp; + Type = type; + } + } +} \ No newline at end of file diff --git a/src/WorkItemMigrator/JiraExport/JiraItem.cs b/src/WorkItemMigrator/JiraExport/JiraItem.cs index 10d2b188..3120f461 100644 --- a/src/WorkItemMigrator/JiraExport/JiraItem.cs +++ b/src/WorkItemMigrator/JiraExport/JiraItem.cs @@ -105,15 +105,16 @@ private static List BuildRevisions(JiraItem jiraItem, IJiraProvide listOfRevisions.AddRange(commentRevisions); var settings = jiraProvider.GetSettings(); - if (settings.IncludeCommits) + if (settings.IncludeDevelopmentLinks) { if (settings.RepositoryMap == null) { - Logger.Log(LogLevel.Warning, $"IncludeCommits was 'true' in the config, but no RepositoryMap was specified in the config. " + + Logger.Log(LogLevel.Warning, $"IncludeDevelopmentLinks was 'true' in the config, but no RepositoryMap was specified in the config. " + $"Please add a RepositoryMap in order to migrate git artifact links. Git artifacts will be skipped for now..."); } else { + // Get development links: commits var commitRepositories = jiraProvider.GetCommitRepositories(jiraItem.Id); foreach (var respository in commitRepositories) { @@ -122,21 +123,36 @@ private static List BuildRevisions(JiraItem jiraItem, IJiraProvide { var commitCreatedOn = commit.ExValue("$.authorTimestamp"); var commitAuthor = GetAuthor(commit as JObject); - var jiraCommit = commit.ToObject(); var repositoryName = respository.SelectToken("$.name").Value(); if (string.IsNullOrEmpty(repositoryName)) { continue; } - var hasRespositoryTarget = settings.RepositoryMap.Repositories.Exists(r => r.Source == repositoryName && !string.IsNullOrEmpty(r.Target)); + var hasRespositoryTarget = settings.RepositoryMap.Repositories.Exists( + r => r.Source == repositoryName && !string.IsNullOrEmpty(r.Target)); if (!hasRespositoryTarget) { continue; } - jiraCommit.Repository = repositoryName; - var commitRevision = new JiraRevision(jiraItem) { Time = commitCreatedOn, Author = commitAuthor, Fields = new Dictionary(), Commit = new RevisionAction() { ChangeType = RevisionChangeType.Added, Value = jiraCommit } }; + var jiraDevelopmentLink = new JiraDevelopmentLink( + repositoryName, + commit.SelectToken("id").ToString(), + commitCreatedOn, + JiraDevelopmentLink.DevelopmentLinkType.Commit + ); + var commitRevision = new JiraRevision(jiraItem) + { + Time = commitCreatedOn, + Author = commitAuthor, + Fields = new Dictionary(), + DevelopmentLink = new RevisionAction() + { + ChangeType = RevisionChangeType.Added, + Value = jiraDevelopmentLink + } + }; listOfRevisions.Add(commitRevision); } } diff --git a/src/WorkItemMigrator/JiraExport/JiraMapper.cs b/src/WorkItemMigrator/JiraExport/JiraMapper.cs index b15343b1..68438670 100644 --- a/src/WorkItemMigrator/JiraExport/JiraMapper.cs +++ b/src/WorkItemMigrator/JiraExport/JiraMapper.cs @@ -189,18 +189,18 @@ internal Dictionary> InitializeFieldMappings( return mappingPerWiType; } - internal WiCommit MapCommit(JiraRevision jiraRevision) + internal WiDevelopmentLink MapDevelopmentLink(JiraRevision jiraRevision) { if (jiraRevision == null) throw new ArgumentNullException(nameof(jiraRevision)); - if (jiraRevision.Commit == null) + if (jiraRevision.DevelopmentLink == null) { return null; } - var jiraCommit = jiraRevision.Commit.Value; - var respositoryTarget = jiraCommit.Repository; + var jiraDevelopmentLink = jiraRevision.DevelopmentLink.Value; + var respositoryTarget = jiraDevelopmentLink.Repository; var respositoryOverride = _config .RepositoryMap @@ -213,13 +213,14 @@ internal WiCommit MapCommit(JiraRevision jiraRevision) respositoryTarget = respositoryOverride; } - var commit = new WiCommit() + var developmentLink = new WiDevelopmentLink() { - Id = jiraCommit.Id, + Id = jiraDevelopmentLink.Id, Repository = respositoryTarget, + Type = jiraDevelopmentLink.Type.ToString() }; - return commit; + return developmentLink; } internal List MapLinks(JiraRevision r) @@ -348,7 +349,7 @@ internal WiRevision MapRevision(JiraRevision r) List attachments = MapAttachments(r); List fields = MapFields(r); List links = MapLinks(r); - var commit = MapCommit(r); + var developmentLink = MapDevelopmentLink(r); return new WiRevision() { @@ -360,7 +361,7 @@ internal WiRevision MapRevision(JiraRevision r) Fields = fields, Links = links, AttachmentReferences = attachments.Any(), - Commit = commit + DevelopmentLink = developmentLink }; } diff --git a/src/WorkItemMigrator/JiraExport/JiraRevision.cs b/src/WorkItemMigrator/JiraExport/JiraRevision.cs index e95201c2..685eb2f1 100644 --- a/src/WorkItemMigrator/JiraExport/JiraRevision.cs +++ b/src/WorkItemMigrator/JiraExport/JiraRevision.cs @@ -32,7 +32,7 @@ public class JiraRevision : ISourceRevision, IComparable public List> LinkActions { get; set; } public List> AttachmentActions { get; set; } - public RevisionAction Commit { get; set; } + public RevisionAction DevelopmentLink { get; set; } public JiraItem ParentItem { get; private set; } public int Index { get; set; } diff --git a/src/WorkItemMigrator/JiraExport/JiraSettings.cs b/src/WorkItemMigrator/JiraExport/JiraSettings.cs index 2c010d73..01a28696 100644 --- a/src/WorkItemMigrator/JiraExport/JiraSettings.cs +++ b/src/WorkItemMigrator/JiraExport/JiraSettings.cs @@ -18,7 +18,7 @@ public class JiraSettings public string AttachmentsDir { get; set; } public string JQL { get; set; } public bool UsingJiraCloud { get; set; } - public bool IncludeCommits { get; set; } + public bool IncludeDevelopmentLinks { get; set; } public RepositoryMap RepositoryMap { get; set; } public JiraSettings(string userID, string pass, string token, string url, string project) diff --git a/src/WorkItemMigrator/JiraExport/jira-export.csproj b/src/WorkItemMigrator/JiraExport/jira-export.csproj index 2e8579ea..d935dbdd 100644 --- a/src/WorkItemMigrator/JiraExport/jira-export.csproj +++ b/src/WorkItemMigrator/JiraExport/jira-export.csproj @@ -117,7 +117,7 @@ - + diff --git a/src/WorkItemMigrator/Migration.Common/Config/ConfigJson.cs b/src/WorkItemMigrator/Migration.Common/Config/ConfigJson.cs index 471ea58a..a5ad353d 100644 --- a/src/WorkItemMigrator/Migration.Common/Config/ConfigJson.cs +++ b/src/WorkItemMigrator/Migration.Common/Config/ConfigJson.cs @@ -75,8 +75,8 @@ public class ConfigJson [JsonProperty(PropertyName = "sleep-time-between-revision-import-milliseconds")] public int SleepTimeBetweenRevisionImportMilliseconds { get; set; } = 0; - [JsonProperty(PropertyName = "include-commits")] - public bool IncludeCommits { get; set; } = false; + [JsonProperty(PropertyName = "include-development-links")] + public bool IncludeDevelopmentLinks { get; set; } = false; [JsonProperty(PropertyName = "include-jira-css-styles")] public bool IncludeJiraCssStyles { get; set; } = false; diff --git a/src/WorkItemMigrator/Migration.WIContract/WiCommit.cs b/src/WorkItemMigrator/Migration.WIContract/WiCommit.cs index 5c5612cf..fcb1d6ca 100644 --- a/src/WorkItemMigrator/Migration.WIContract/WiCommit.cs +++ b/src/WorkItemMigrator/Migration.WIContract/WiCommit.cs @@ -1,9 +1,10 @@ namespace Migration.WIContract { - public class WiCommit + public class WiDevelopmentLink { public string Id { get; set; } public string Repository { get; set; } + public string Type { get; set; } public override string ToString() { diff --git a/src/WorkItemMigrator/Migration.WIContract/WiRevision.cs b/src/WorkItemMigrator/Migration.WIContract/WiRevision.cs index 01ac5e71..8788c8c9 100644 --- a/src/WorkItemMigrator/Migration.WIContract/WiRevision.cs +++ b/src/WorkItemMigrator/Migration.WIContract/WiRevision.cs @@ -28,7 +28,7 @@ public WiRevision() public List Fields { get; set; } public List Links { get; set; } public List Attachments { get; set; } - public WiCommit Commit { get; set; } + public WiDevelopmentLink DevelopmentLink { get; set; } [DefaultValue(false)] public bool AttachmentReferences { get; set; } = false; diff --git a/src/WorkItemMigrator/WorkItemImport/Agent.cs b/src/WorkItemMigrator/WorkItemImport/Agent.cs index 2b761f4a..450f01fa 100644 --- a/src/WorkItemMigrator/WorkItemImport/Agent.cs +++ b/src/WorkItemMigrator/WorkItemImport/Agent.cs @@ -158,10 +158,10 @@ public bool ImportRevision(WiRevision rev, WorkItem wi, Settings settings) } } - // rev with a commit won't have meaningful information, skip saving fields - if (rev.Commit != null) + // rev with a development link won't have meaningful information, skip saving fields + if (rev.DevelopmentLink != null) { - if (settings.IncludeCommits) + if (settings.IncludeDevelopmentLinks) { _witClientUtils.SaveWorkItemArtifacts(rev, wi, settings); } diff --git a/src/WorkItemMigrator/WorkItemImport/ImportCommandLine.cs b/src/WorkItemMigrator/WorkItemImport/ImportCommandLine.cs index f5833cb6..c79647eb 100644 --- a/src/WorkItemMigrator/WorkItemImport/ImportCommandLine.cs +++ b/src/WorkItemMigrator/WorkItemImport/ImportCommandLine.cs @@ -90,7 +90,7 @@ private bool ExecuteMigration(CommandOption token, CommandOption url, CommandOpt IgnoreFailedLinks = config.IgnoreFailedLinks, ProcessTemplate = config.ProcessTemplate, IncludeLinkComments = config.IncludeLinkComments, - IncludeCommits = config.IncludeCommits, + IncludeDevelopmentLinks = config.IncludeDevelopmentLinks, FieldMap = config.FieldMap, SuppressNotifications = config.SuppressNotifications }; diff --git a/src/WorkItemMigrator/WorkItemImport/Settings.cs b/src/WorkItemMigrator/WorkItemImport/Settings.cs index 0c47a33e..bb1c27ea 100644 --- a/src/WorkItemMigrator/WorkItemImport/Settings.cs +++ b/src/WorkItemMigrator/WorkItemImport/Settings.cs @@ -19,7 +19,7 @@ public Settings(string account, string project, string pat) public bool IgnoreFailedLinks { get; internal set; } public string ProcessTemplate { get; internal set; } public bool IncludeLinkComments { get; internal set; } - public bool IncludeCommits { get; internal set; } + public bool IncludeDevelopmentLinks { get; internal set; } public FieldMap FieldMap { get; internal set; } public bool SuppressNotifications { get; internal set; } } diff --git a/src/WorkItemMigrator/WorkItemImport/WitClient/JsonPatchDocUtils.cs b/src/WorkItemMigrator/WorkItemImport/WitClient/JsonPatchDocUtils.cs index 04e845b4..df81fc27 100644 --- a/src/WorkItemMigrator/WorkItemImport/WitClient/JsonPatchDocUtils.cs +++ b/src/WorkItemMigrator/WorkItemImport/WitClient/JsonPatchDocUtils.cs @@ -1,6 +1,7 @@ using Microsoft.VisualStudio.Services.WebApi.Patch; using Microsoft.VisualStudio.Services.WebApi.Patch.Json; using System; +using System.Web; namespace WorkItemImport.WitClient { @@ -33,11 +34,17 @@ public static JsonPatchOperation CreateJsonFieldPatchOp(Operation op, string key }; } - public static JsonPatchOperation CreateJsonArtifactLinkPatchOp(Operation op, string projectId, string repositoryId, string commitId) + public static JsonPatchOperation CreateJsonArtifactLinkPatchOp( + Operation op, + string projectId, + string repositoryId, + string developmentLinkId, + string type + ) { - if (string.IsNullOrEmpty(commitId)) + if (string.IsNullOrEmpty(developmentLinkId)) { - throw new ArgumentException(nameof(commitId)); + throw new ArgumentException(nameof(developmentLinkId)); } if (string.IsNullOrEmpty(projectId)) @@ -50,6 +57,16 @@ public static JsonPatchOperation CreateJsonArtifactLinkPatchOp(Operation op, str throw new ArgumentException(nameof(repositoryId)); } + string url; + if (type == "Commit") + { + url = $"vstfs:///Git/Commit/{projectId}%2F{repositoryId}%2F{developmentLinkId}"; + } + else + { + throw new ArgumentException(nameof(type)); + } + return new JsonPatchOperation() { Operation = op, @@ -57,7 +74,7 @@ public static JsonPatchOperation CreateJsonArtifactLinkPatchOp(Operation op, str Value = new PatchOperationValue { Rel = "ArtifactLink", - Url = $"vstfs:///Git/Commit/{projectId}%2F{repositoryId}%2F{commitId}", + Url = url, Attributes = new Attributes { Name = "Fixed in Commit" diff --git a/src/WorkItemMigrator/WorkItemImport/WitClient/WitClientUtils.cs b/src/WorkItemMigrator/WorkItemImport/WitClient/WitClientUtils.cs index fab01863..d25dae19 100644 --- a/src/WorkItemMigrator/WorkItemImport/WitClient/WitClientUtils.cs +++ b/src/WorkItemMigrator/WorkItemImport/WitClient/WitClientUtils.cs @@ -687,17 +687,23 @@ public void SaveWorkItemArtifacts(WiRevision rev, WorkItem wi, Settings settings throw new ArgumentException(nameof(wi)); } - if (rev.Commit == null) + if (rev.DevelopmentLink == null) { return; } Guid projectId = _witClientWrapper.GetProject(settings.Project).Id; - Guid repositoryId = _witClientWrapper.GetRepository(settings.Project, rev.Commit.Repository).Id; + Guid repositoryId = _witClientWrapper.GetRepository(settings.Project, rev.DevelopmentLink.Repository).Id; var patchDocument = new JsonPatchDocument { - JsonPatchDocUtils.CreateJsonArtifactLinkPatchOp(Operation.Add, projectId.ToString(), repositoryId.ToString(), rev.Commit.Id), + JsonPatchDocUtils.CreateJsonArtifactLinkPatchOp( + Operation.Add, + projectId.ToString(), + repositoryId.ToString(), + rev.DevelopmentLink.Id, + rev.DevelopmentLink.Type + ), JsonPatchDocUtils.CreateJsonFieldPatchOp(Operation.Add, WiFieldReference.ChangedDate, rev.Time), JsonPatchDocUtils.CreateJsonFieldPatchOp(Operation.Add, WiFieldReference.ChangedBy, rev.Author) }; diff --git a/src/WorkItemMigrator/tests/Migration.Wi-Import.Tests/WitClient/JsonPatchDocUtilsTests.cs b/src/WorkItemMigrator/tests/Migration.Wi-Import.Tests/WitClient/JsonPatchDocUtilsTests.cs index cbcd0fc0..bca9f565 100644 --- a/src/WorkItemMigrator/tests/Migration.Wi-Import.Tests/WitClient/JsonPatchDocUtilsTests.cs +++ b/src/WorkItemMigrator/tests/Migration.Wi-Import.Tests/WitClient/JsonPatchDocUtilsTests.cs @@ -36,7 +36,7 @@ public void When_calling_create_json_field_patch_op_with_empty_args_Then_an_exce public void When_calling_create_json_artifact_link_field_patch_op_with_empty_args_Then_an_exception_is_thrown() { Assert.That( - () => JsonPatchDocUtils.CreateJsonArtifactLinkPatchOp(Operation.Add, null, null, null), + () => JsonPatchDocUtils.CreateJsonArtifactLinkPatchOp(Operation.Add, null, null, null, null), Throws.InstanceOf()); } @@ -61,7 +61,7 @@ public void When_calling_create_json_artifact_link_field_patch_op_Then_a_correct string projectId = Guid.NewGuid().ToString(); string repositoryId = Guid.NewGuid().ToString(); string commitId = Guid.NewGuid().ToString(); - JsonPatchOperation jsonPatchOp = JsonPatchDocUtils.CreateJsonArtifactLinkPatchOp(Operation.Add, projectId, repositoryId, commitId); + JsonPatchOperation jsonPatchOp = JsonPatchDocUtils.CreateJsonArtifactLinkPatchOp(Operation.Add, projectId, repositoryId, commitId, "Commit"); PatchOperationValue artifactLink = jsonPatchOp.Value as PatchOperationValue; Assert.Multiple(() => diff --git a/src/WorkItemMigrator/tests/Migration.Wi-Import.Tests/WitClient/WitClientUtilsTests.cs b/src/WorkItemMigrator/tests/Migration.Wi-Import.Tests/WitClient/WitClientUtilsTests.cs index 2e55d540..4139da1c 100644 --- a/src/WorkItemMigrator/tests/Migration.Wi-Import.Tests/WitClient/WitClientUtilsTests.cs +++ b/src/WorkItemMigrator/tests/Migration.Wi-Import.Tests/WitClient/WitClientUtilsTests.cs @@ -1176,10 +1176,11 @@ public void When_calling_save_workitem_artifacts_with_populated_workitem_Then_wo WiRevision revision = new WiRevision { - Commit = new WiCommit + DevelopmentLink = new WiDevelopmentLink { Repository = "repository", - Id = "1234567890" + Id = "1234567890", + Type = "Commit" } }; @@ -1200,7 +1201,7 @@ public void When_calling_save_workitem_artifacts_with_populated_workitem_Then_wo { Assert.That(updatedWI.Relations.First().Rel, Is.EqualTo("ArtifactLink")); Assert.That(updatedWI.Relations.First().Url, Is.EqualTo($"vstfs:///Git/Commit/" + - $"{witClientWrapper.projectId}%2F{witClientWrapper.repositoryId}%2F{revision.Commit.Id}")); + $"{witClientWrapper.projectId}%2F{witClientWrapper.repositoryId}%2F{revision.DevelopmentLink.Id}")); }); }