diff --git a/README.md b/README.md index 88b726ed..e1d1bc14 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ For projects hosted by [GitLab](https://gitlab.com) reference [Microsoft.SourceL ### Bitbucket.org -For projects hosted on [Bitbucket.org](https://bitbucket.org) in git repositories reference [Microsoft.SourceLink.Bitbucket.Git](https://www.nuget.org/packages/Microsoft.SourceLink.Bitbucket.Git) package: +For projects hosted on [Bitbucket.org](https://bitbucket.org) in git repositories reference [Microsoft.SourceLink.Bitbucket.Git](https://www.nuget.org/packages/Microsoft.SourceLink.Bitbucket.Git) package: ```xml @@ -100,6 +100,19 @@ For projects hosted on [Bitbucket.org](https://bitbucket.org) in git repositorie ``` +For self-hosted Bitbucket projects reference [Microsoft.SourceLink.Bitbucket.Git](https://www.nuget.org/packages/Microsoft.SourceLink.Bitbucket.Git) package and add Bitbucket host configuration. +Additional configuration is available when SourceLinkBitbucketGitHost is added to csproj: + +- EnterpriseEdition - flag whether it is Enterprise Edition or Cloud Edition, by default it is true. +- Version="4.7" - for Enterprise Edition provides its version. URL format for accessing files is different for Bitbucket in version < 4.7, please add Bitbucket version if it is the case + +```xml + + + + +``` + ### Multiple providers, repositories with submodules If your repository contains submodules hosted by other git providers reference packages of all these providers. For example, projects in a repository hosted by Azure DevOps that links a GitHub repository via a submodule should reference both [Microsoft.SourceLink.Vsts.Git](https://www.nuget.org/packages/Microsoft.SourceLink.Vsts.Git) and [Microsoft.SourceLink.GitHub](https://www.nuget.org/packages/Microsoft.SourceLink.GitHub) packages. [Additional configuration](https://github.com/dotnet/sourcelink/blob/master/docs/README.md#configuring-projects-with-multiple-sourcelink-providers) might be needed if multiple Source Link packages are used in the project. diff --git a/src/SourceLink.Bitbucket.Git.UnitTests/GetSourceLinkUrlTests.cs b/src/SourceLink.Bitbucket.Git.UnitTests/GetSourceLinkUrlTests.cs index b0bc0eff..b0c6e170 100644 --- a/src/SourceLink.Bitbucket.Git.UnitTests/GetSourceLinkUrlTests.cs +++ b/src/SourceLink.Bitbucket.Git.UnitTests/GetSourceLinkUrlTests.cs @@ -8,6 +8,11 @@ namespace Microsoft.SourceLink.Bitbucket.Git.UnitTests { public class GetSourceLinkUrlTests { + private const string ExpectedUrlForCloudEdition = + "https://domain.com/x/y/a/b/raw/0123456789abcdefABCDEF000000000000000000/*"; + private const string ExpectedUrlForEnterpriseEditionOldVersion = "https://bitbucket.domain.com/projects/a/repos/b/browse/*?at=0123456789abcdefABCDEF000000000000000000&raw"; + private const string ExpectedUrlForEnterpriseEditionNewVersion = "https://bitbucket.domain.com/projects/a/repos/b/raw/*?at=0123456789abcdefABCDEF000000000000000000"; + [Fact] public void EmptyHosts() { @@ -32,24 +37,190 @@ public void EmptyHosts() [InlineData("", "/")] [InlineData("/", "")] [InlineData("/", "/")] - public void BuildSourceLinkUrl(string s1, string s2) + public void BuildSourceLinkUrl_BitbucketCloud(string s1, string s2) { + var isEnterpriseEditionSetting = KVP("EnterpriseEdition", "false"); var engine = new MockEngine(); - var task = new GetSourceLinkUrl() { BuildEngine = engine, SourceRoot = new MockItem("/src/", KVP("RepositoryUrl", "http://subdomain.mybitbucket.org:100/a/b" + s1), KVP("SourceControl", "git"), KVP("RevisionId", "0123456789abcdefABCDEF000000000000000000")), Hosts = new[] { - new MockItem("mybitbucket.org", KVP("ContentUrl", "https://domain.com/x/y" + s2)), + new MockItem("mybitbucket.org", KVP("ContentUrl", "https://domain.com/x/y" + s2), isEnterpriseEditionSetting), + } + }; + + bool result = task.Execute(); + AssertEx.AssertEqualToleratingWhitespaceDifferences("", engine.Log); + AssertEx.AreEqual(ExpectedUrlForCloudEdition, task.SourceLinkUrl); + Assert.True(result); + } + + [Fact] + public void BuildSourceLinkUrl_MetadataWithEnterpriseEditionButWithoutVersion_UseNewVersionAsDefauld() + { + var isEnterpriseEditionSetting = KVP("EnterpriseEdition", "true"); + var engine = new MockEngine(); + var task = new GetSourceLinkUrl() + { + BuildEngine = engine, + SourceRoot = new MockItem("/src/", KVP("RepositoryUrl", "http://bitbucket.domain.com:100/a/b"), KVP("SourceControl", "git"), KVP("RevisionId", "0123456789abcdefABCDEF000000000000000000")), + Hosts = new[] + { + new MockItem("domain.com", KVP("ContentUrl", "https://bitbucket.domain.com"), isEnterpriseEditionSetting), + } + }; + + bool result = task.Execute(); + AssertEx.AssertEqualToleratingWhitespaceDifferences("", engine.Log); + AssertEx.AreEqual(ExpectedUrlForEnterpriseEditionNewVersion, task.SourceLinkUrl); + Assert.True(result); + } + + [Theory] + [InlineData("", "", "4.4")] + [InlineData("", "/", "4.4")] + [InlineData("/", "", "4.4")] + [InlineData("/", "/", "4.4")] + [InlineData("", "", "4.6")] + [InlineData("", "/", "4.6")] + [InlineData("/", "", "4.6")] + [InlineData("/", "/", "4.6")] + public void BuildSourceLinkUrl_BitbucketEnterpriseOldVersionSsh(string s1, string s2, string bitbucketVersion) + { + var isEnterpriseEditionSetting = KVP("EnterpriseEdition", "true"); + var version = KVP("Version", bitbucketVersion); + var engine = new MockEngine(); + var task = new GetSourceLinkUrl() + { + BuildEngine = engine, + SourceRoot = new MockItem("/src/", KVP("RepositoryUrl", "http://bitbucket.domain.com:100/a/b" + s1), KVP("SourceControl", "git"), KVP("RevisionId", "0123456789abcdefABCDEF000000000000000000")), + Hosts = new[] + { + new MockItem("domain.com", KVP("ContentUrl", "https://bitbucket.domain.com" + s2), isEnterpriseEditionSetting, version), + } + }; + + bool result = task.Execute(); + AssertEx.AssertEqualToleratingWhitespaceDifferences("", engine.Log); + AssertEx.AreEqual(ExpectedUrlForEnterpriseEditionOldVersion, task.SourceLinkUrl); + Assert.True(result); + } + + [Theory] + [InlineData("", "", "4.4")] + [InlineData("", "/", "4.4")] + [InlineData("/", "", "4.4")] + [InlineData("/", "/", "4.4")] + [InlineData("", "", "4.6")] + [InlineData("", "/", "4.6")] + [InlineData("/", "", "4.6")] + [InlineData("/", "/", "4.6")] + public void BuildSourceLinkUrl_BitbucketEnterpriseOldVersionHttps(string s1, string s2, string bitbucketVersion) + { + var isEnterpriseEditionSetting = KVP("EnterpriseEdition", "true"); + var version = KVP("Version", bitbucketVersion); + var engine = new MockEngine(); + var task = new GetSourceLinkUrl() + { + BuildEngine = engine, + SourceRoot = new MockItem("/src/", KVP("RepositoryUrl", "http://bitbucket.domain.com:100/scm/a/b" + s1), KVP("SourceControl", "git"), KVP("RevisionId", "0123456789abcdefABCDEF000000000000000000")), + Hosts = new[] + { + new MockItem("domain.com", KVP("ContentUrl", "https://bitbucket.domain.com" + s2), isEnterpriseEditionSetting, version), } }; bool result = task.Execute(); AssertEx.AssertEqualToleratingWhitespaceDifferences("", engine.Log); - AssertEx.AreEqual("https://domain.com/x/y/a/b/raw/0123456789abcdefABCDEF000000000000000000/*", task.SourceLinkUrl); + AssertEx.AreEqual(ExpectedUrlForEnterpriseEditionOldVersion, task.SourceLinkUrl); Assert.True(result); } + + [Theory] + [InlineData("", "", "")] + [InlineData("", "", "4.7")] + [InlineData("", "/", "4.7")] + [InlineData("/", "", "4.7")] + [InlineData("/", "/", "4.7")] + [InlineData("", "", "5.6")] + [InlineData("", "/", "5.6")] + [InlineData("/", "", "5.6")] + [InlineData("/", "/", "5.6")] + public void BuildSourceLinkUrl_BitbucketEnterpriseNewVersionSsh(string s1, string s2, string bitbucketVersion) + { + var isEnterpriseEditionSetting = KVP("EnterpriseEdition", "true"); + var version = KVP("Version", bitbucketVersion); + var engine = new MockEngine(); + var task = new GetSourceLinkUrl() + { + BuildEngine = engine, + SourceRoot = new MockItem("/src/", KVP("RepositoryUrl", "http://bitbucket.domain.com:100/a/b" + s1), KVP("SourceControl", "git"), KVP("RevisionId", "0123456789abcdefABCDEF000000000000000000")), + Hosts = new[] + { + new MockItem("domain.com", KVP("ContentUrl", "https://bitbucket.domain.com" + s2), isEnterpriseEditionSetting, version), + } + }; + + bool result = task.Execute(); + AssertEx.AssertEqualToleratingWhitespaceDifferences("", engine.Log); + AssertEx.AreEqual(ExpectedUrlForEnterpriseEditionNewVersion, task.SourceLinkUrl); + Assert.True(result); + } + + [Theory] + [InlineData("", "", "")] + [InlineData("", "", "4.7")] + [InlineData("", "/", "4.7")] + [InlineData("/", "", "4.7")] + [InlineData("/", "/", "4.7")] + [InlineData("", "", "5.6")] + [InlineData("", "/", "5.6")] + [InlineData("/", "", "5.6")] + [InlineData("/", "/", "5.6")] + public void BuildSourceLinkUrl_BitbucketEnterpriseNewVersionHttps(string s1, string s2, string bitbucketVersion) + { + var isEnterpriseEditionSetting = KVP("EnterpriseEdition", "true"); + var version = KVP("Version", bitbucketVersion); + var engine = new MockEngine(); + var task = new GetSourceLinkUrl() + { + BuildEngine = engine, + SourceRoot = new MockItem("/src/", KVP("RepositoryUrl", "http://bitbucket.domain.com:100/scm/a/b" + s1), KVP("SourceControl", "git"), KVP("RevisionId", "0123456789abcdefABCDEF000000000000000000")), + Hosts = new[] + { + new MockItem("domain.com", KVP("ContentUrl", "https://bitbucket.domain.com" + s2), isEnterpriseEditionSetting, version), + } + }; + + bool result = task.Execute(); + AssertEx.AssertEqualToleratingWhitespaceDifferences("", engine.Log); + AssertEx.AreEqual(ExpectedUrlForEnterpriseEditionNewVersion, task.SourceLinkUrl); + Assert.True(result); + } + + [Fact] + public void BuildSourceLinkUrl_IncorrectVersionForEnterpriseEdition_ERROR() + { + var isEnterpriseEditionSetting = KVP("EnterpriseEdition", "true"); + var version = KVP("Version", "incorrect_version"); + var engine = new MockEngine(); + var task = new GetSourceLinkUrl() + { + BuildEngine = engine, + SourceRoot = new MockItem("/src/", KVP("RepositoryUrl", "http://bitbucket.domain.com:100/a/b"), KVP("SourceControl", "git"), KVP("RevisionId", "0123456789abcdefABCDEF000000000000000000")), + Hosts = new[] + { + new MockItem("domain.com", KVP("ContentUrl", "https://bitbucket.domain.com"), isEnterpriseEditionSetting, version), + } + }; + + bool result = task.Execute(); + + AssertEx.AssertEqualToleratingWhitespaceDifferences( + "ERROR : " + string.Format(CommonResources.ItemOfItemGroupMustSpecifyMetadata, "domain.com", "SourceLinkBitbucketGitHost", "Version"), engine.Log); + Assert.False(result); + } } } diff --git a/src/SourceLink.Bitbucket.Git/GetSourceLinkUrl.cs b/src/SourceLink.Bitbucket.Git/GetSourceLinkUrl.cs index 9a6e2031..7870674c 100644 --- a/src/SourceLink.Bitbucket.Git/GetSourceLinkUrl.cs +++ b/src/SourceLink.Bitbucket.Git/GetSourceLinkUrl.cs @@ -16,7 +16,78 @@ public sealed class GetSourceLinkUrl : GetSourceLinkUrlGitTask protected override string HostsItemGroupName => "SourceLinkBitbucketGitHost"; protected override string ProviderDisplayName => "Bitbucket.Git"; + private const string IsEnterpriseEditionMetadataName = "EnterpriseEdition"; + private const string VersionMetadataName = "Version"; + private const string VersionWithNewUrlFormat = "4.7"; + protected override string BuildSourceLinkUrl(Uri contentUri, Uri gitUri, string relativeUrl, string revisionId, ITaskItem hostItem) - => UriUtilities.Combine(UriUtilities.Combine(contentUri.ToString(), relativeUrl), "raw/" + revisionId + "/*"); + { + return + bool.TryParse(hostItem?.GetMetadata(IsEnterpriseEditionMetadataName), out var isEnterpriseEdition) && !isEnterpriseEdition + ? BuildSourceLinkUrlForCloudEdition(contentUri, relativeUrl, revisionId) + : BuildSourceLinkUrlForEnterpriseEdition(contentUri, relativeUrl, revisionId, hostItem); + } + + private string BuildSourceLinkUrlForEnterpriseEdition(Uri contentUri, string relativeUrl, string revisionId, + ITaskItem hostItem) + { + var bitbucketEnterpriseVersion = GetBitbucketEnterpriseVersion(hostItem); + + var splits = relativeUrl.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries); + var isSshRepoUri = !(splits.Length == 3 && splits[0] == "scm"); + var projectName = isSshRepoUri ? splits[0] : splits[1]; + var repositoryName = isSshRepoUri ? splits[1] : splits[2]; + + var relativeUrlForBitbucketEnterprise = + GetRelativeUrlForBitbucketEnterprise(projectName, repositoryName, revisionId, + bitbucketEnterpriseVersion); + + var result = UriUtilities.Combine(contentUri.ToString(), relativeUrlForBitbucketEnterprise); + + return result; + } + + private Version GetBitbucketEnterpriseVersion(ITaskItem hostItem) + { + var bitbucketEnterpriseVersionAsString = hostItem?.GetMetadata(VersionMetadataName); + Version bitbucketEnterpriseVersion; + if (!string.IsNullOrEmpty(bitbucketEnterpriseVersionAsString)) + { + if (!Version.TryParse(bitbucketEnterpriseVersionAsString, out bitbucketEnterpriseVersion)) + { + Log.LogError(CommonResources.ItemOfItemGroupMustSpecifyMetadata, hostItem.ItemSpec, + HostsItemGroupName, VersionMetadataName); + + return null; + } + } + else + { + bitbucketEnterpriseVersion = Version.Parse(VersionWithNewUrlFormat); + } + + return bitbucketEnterpriseVersion; + } + + private static string BuildSourceLinkUrlForCloudEdition(Uri contentUri, string relativeUrl, string revisionId) + { + return UriUtilities.Combine(UriUtilities.Combine(contentUri.ToString(), relativeUrl), + "raw/" + revisionId + "/*"); + } + + private static string GetRelativeUrlForBitbucketEnterprise(string projectName, string repositoryName, string commitId, Version bitbucketVersion) + { + string relativeUrl; + if (bitbucketVersion >= Version.Parse(VersionWithNewUrlFormat)) + { + relativeUrl = $"projects/{projectName}/repos/{repositoryName}/raw/*?at={commitId}"; + } + else + { + relativeUrl = $"projects/{projectName}/repos/{repositoryName}/browse/*?at={commitId}&raw"; + } + + return relativeUrl; + } } } diff --git a/src/SourceLink.Bitbucket.Git/build/Microsoft.SourceLink.Bitbucket.Git.props b/src/SourceLink.Bitbucket.Git/build/Microsoft.SourceLink.Bitbucket.Git.props index 08469bed..8710d6aa 100644 --- a/src/SourceLink.Bitbucket.Git/build/Microsoft.SourceLink.Bitbucket.Git.props +++ b/src/SourceLink.Bitbucket.Git/build/Microsoft.SourceLink.Bitbucket.Git.props @@ -1,6 +1,6 @@ - + diff --git a/src/SourceLink.Git.IntegrationTests/BitbucketGitTests.cs b/src/SourceLink.Git.IntegrationTests/BitbucketGitTests.cs index b1f85036..70000639 100644 --- a/src/SourceLink.Git.IntegrationTests/BitbucketGitTests.cs +++ b/src/SourceLink.Git.IntegrationTests/BitbucketGitTests.cs @@ -13,7 +13,7 @@ public BitBucketGitTests() } [ConditionalFact(typeof(DotNetSdkAvailable))] - public void FullValidation_Https() + public void FullValidation_CloudHttps() { // Test non-ascii characters and escapes in the URL. // Escaped URI reserved characters should remain escaped, non-reserved characters unescaped in the results. @@ -27,8 +27,7 @@ public void FullValidation_Https() customProps: @" true - -", +", customTargets: "", targets: new[] { @@ -67,7 +66,180 @@ public void FullValidation_Https() } [ConditionalFact(typeof(DotNetSdkAvailable))] - public void FullValidation_Ssh() + public void FullValidation_EnterpriseNewHttps() + { + // Test non-ascii characters and escapes in the URL. + // Escaped URI reserved characters should remain escaped, non-reserved characters unescaped in the results. + var repoUrl = "https://bitbucket.domain.com/scm/test-org/test-%72epo\u1234%24%2572%2F"; + var repoName = "test-repo\u1234%24%2572%2F"; + + var repo = GitUtilities.CreateGitRepositoryWithSingleCommit(ProjectDir.Path, new[] {ProjectFileName}, + repoUrl); + var commitSha = repo.Head.Tip.Sha; + + VerifyValues( + customProps: @" + + true + + + + +", + customTargets: "", + targets: new[] + { + "Build", "Pack" + }, + expressions: new[] + { + "@(SourceRoot)", + "@(SourceRoot->'%(SourceLinkUrl)')", + "$(SourceLink)", + "$(PrivateRepositoryUrl)", + "$(RepositoryUrl)" + }, + expectedResults: new[] + { + ProjectSourceRoot, + $"https://bitbucket.domain.com/projects/test-org/repos/{repoName}/raw/*?at={commitSha}", + s_relativeSourceLinkJsonPath, + $"https://bitbucket.domain.com/scm/test-org/{repoName}", + $"https://bitbucket.domain.com/scm/test-org/{repoName}" + }); + + AssertEx.AreEqual( + $@"{{""documents"":{{""{ProjectSourceRoot.Replace(@"\", @"\\")}*"":""https://bitbucket.domain.com/projects/test-org/repos/{repoName}/raw/*?at={commitSha}""}}}}", + File.ReadAllText(Path.Combine(ProjectDir.Path, s_relativeSourceLinkJsonPath))); + + TestUtilities.ValidateAssemblyInformationalVersion( + Path.Combine(ProjectDir.Path, s_relativeOutputFilePath), + "1.0.0+" + commitSha); + + TestUtilities.ValidateNuSpecRepository( + Path.Combine(ProjectDir.Path, s_relativePackagePath), + type: "git", + commit: commitSha, + url: $"https://bitbucket.domain.com/scm/test-org/{repoName}"); + } + + [ConditionalFact(typeof(DotNetSdkAvailable))] + public void FullValidation_EnterpriseNewHttpsWithDefaultFlags() + { + // Test non-ascii characters and escapes in the URL. + // Escaped URI reserved characters should remain escaped, non-reserved characters unescaped in the results. + var repoUrl = "https://bitbucket.domain.com/scm/test-org/test-%72epo\u1234%24%2572%2F"; + var repoName = "test-repo\u1234%24%2572%2F"; + + var repo = GitUtilities.CreateGitRepositoryWithSingleCommit(ProjectDir.Path, new[] { ProjectFileName }, + repoUrl); + var commitSha = repo.Head.Tip.Sha; + + VerifyValues( + customProps: @" + + true + + + + +", + customTargets: "", + targets: new[] + { + "Build", "Pack" + }, + expressions: new[] + { + "@(SourceRoot)", + "@(SourceRoot->'%(SourceLinkUrl)')", + "$(SourceLink)", + "$(PrivateRepositoryUrl)", + "$(RepositoryUrl)" + }, + expectedResults: new[] + { + ProjectSourceRoot, + $"https://bitbucket.domain.com/projects/test-org/repos/{repoName}/raw/*?at={commitSha}", + s_relativeSourceLinkJsonPath, + $"https://bitbucket.domain.com/scm/test-org/{repoName}", + $"https://bitbucket.domain.com/scm/test-org/{repoName}" + }); + + AssertEx.AreEqual( + $@"{{""documents"":{{""{ProjectSourceRoot.Replace(@"\", @"\\")}*"":""https://bitbucket.domain.com/projects/test-org/repos/{repoName}/raw/*?at={commitSha}""}}}}", + File.ReadAllText(Path.Combine(ProjectDir.Path, s_relativeSourceLinkJsonPath))); + + TestUtilities.ValidateAssemblyInformationalVersion( + Path.Combine(ProjectDir.Path, s_relativeOutputFilePath), + "1.0.0+" + commitSha); + + TestUtilities.ValidateNuSpecRepository( + Path.Combine(ProjectDir.Path, s_relativePackagePath), + type: "git", + commit: commitSha, + url: $"https://bitbucket.domain.com/scm/test-org/{repoName}"); + } + + [ConditionalFact(typeof(DotNetSdkAvailable))] + public void FullValidation_EnterpriseOldHttps() + { + // Test non-ascii characters and escapes in the URL. + // Escaped URI reserved characters should remain escaped, non-reserved characters unescaped in the results. + var repoUrl = "https://bitbucket.domain.com/scm/test-org/test-%72epo\u1234%24%2572%2F"; + var repoName = "test-repo\u1234%24%2572%2F"; + + var repo = GitUtilities.CreateGitRepositoryWithSingleCommit(ProjectDir.Path, new[] { ProjectFileName }, repoUrl); + var commitSha = repo.Head.Tip.Sha; + + VerifyValues( + customProps: @" + + true + + + + + ", + customTargets: "", + targets: new[] + { + "Build", "Pack" + }, + expressions: new[] + { + "@(SourceRoot)", + "@(SourceRoot->'%(SourceLinkUrl)')", + "$(SourceLink)", + "$(PrivateRepositoryUrl)", + "$(RepositoryUrl)" + }, + expectedResults: new[] + { + ProjectSourceRoot, + $"https://bitbucket.domain.com/projects/test-org/repos/{repoName}/browse/*?at={commitSha}&raw", + s_relativeSourceLinkJsonPath, + $"https://bitbucket.domain.com/scm/test-org/{repoName}", + $"https://bitbucket.domain.com/scm/test-org/{repoName}" + }); + + AssertEx.AreEqual( + $@"{{""documents"":{{""{ProjectSourceRoot.Replace(@"\", @"\\")}*"":""https://bitbucket.domain.com/projects/test-org/repos/{repoName}/browse/*?at={commitSha}&raw""}}}}", + File.ReadAllText(Path.Combine(ProjectDir.Path, s_relativeSourceLinkJsonPath))); + + TestUtilities.ValidateAssemblyInformationalVersion( + Path.Combine(ProjectDir.Path, s_relativeOutputFilePath), + "1.0.0+" + commitSha); + + TestUtilities.ValidateNuSpecRepository( + Path.Combine(ProjectDir.Path, s_relativePackagePath), + type: "git", + commit: commitSha, + url: $"https://bitbucket.domain.com/scm/test-org/{repoName}"); + } + + [ConditionalFact(typeof(DotNetSdkAvailable))] + public void FullValidation_CloudSsh() { // Test non-ascii characters and escapes in the URL. // Escaped URI reserved characters should remain escaped, non-reserved characters unescaped in the results. @@ -83,7 +255,7 @@ public void FullValidation_Ssh() true - + ", customTargets: "", @@ -122,5 +294,119 @@ public void FullValidation_Ssh() commit: commitSha, url: $"https://噸.com/test-org/{repoName}"); } + + [ConditionalFact(typeof(DotNetSdkAvailable))] + public void FullValidation_EnterpriseOldSsh() + { + // Test non-ascii characters and escapes in the URL. + // Escaped URI reserved characters should remain escaped, non-reserved characters unescaped in the results. + var repoUrl = "test-user@噸.com:test-org/test-%72epo\u1234%24%2572%2F"; + var repoName = "test-repo\u1234%24%2572%2F"; + + var repo = GitUtilities.CreateGitRepositoryWithSingleCommit(ProjectDir.Path, new[] { ProjectFileName }, repoUrl); + var commitSha = repo.Head.Tip.Sha; + + VerifyValues( + customProps: @" + + true + + + + +", + customTargets: "", + targets: new[] + { + "Build", "Pack" + }, + expressions: new[] + { + "@(SourceRoot)", + "@(SourceRoot->'%(SourceLinkUrl)')", + "$(SourceLink)", + "$(PrivateRepositoryUrl)", + "$(RepositoryUrl)" + }, + expectedResults: new[] + { + ProjectSourceRoot, + $"https://噸.com/projects/test-org/repos/{repoName}/browse/*?at={commitSha}&raw", + s_relativeSourceLinkJsonPath, + $"https://噸.com/test-org/{repoName}", + $"https://噸.com/test-org/{repoName}" + }); + + AssertEx.AreEqual( + $@"{{""documents"":{{""{ProjectSourceRoot.Replace(@"\", @"\\")}*"":""https://噸.com/projects/test-org/repos/{repoName}/browse/*?at={commitSha}&raw""}}}}", + File.ReadAllText(Path.Combine(ProjectDir.Path, s_relativeSourceLinkJsonPath))); + + TestUtilities.ValidateAssemblyInformationalVersion( + Path.Combine(ProjectDir.Path, s_relativeOutputFilePath), + "1.0.0+" + commitSha); + + TestUtilities.ValidateNuSpecRepository( + Path.Combine(ProjectDir.Path, s_relativePackagePath), + type: "git", + commit: commitSha, + url: $"https://噸.com/test-org/{repoName}"); + } + + [ConditionalFact(typeof(DotNetSdkAvailable))] + public void FullValidation_EnterpriseNewSsh() + { + // Test non-ascii characters and escapes in the URL. + // Escaped URI reserved characters should remain escaped, non-reserved characters unescaped in the results. + var repoUrl = "test-user@噸.com:test-org/test-%72epo\u1234%24%2572%2F"; + var repoName = "test-repo\u1234%24%2572%2F"; + + var repo = GitUtilities.CreateGitRepositoryWithSingleCommit(ProjectDir.Path, new[] { ProjectFileName }, repoUrl); + var commitSha = repo.Head.Tip.Sha; + + VerifyValues( + customProps: @" + + true + + + + +", + customTargets: "", + targets: new[] + { + "Build", "Pack" + }, + expressions: new[] + { + "@(SourceRoot)", + "@(SourceRoot->'%(SourceLinkUrl)')", + "$(SourceLink)", + "$(PrivateRepositoryUrl)", + "$(RepositoryUrl)" + }, + expectedResults: new[] + { + ProjectSourceRoot, + $"https://噸.com/projects/test-org/repos/{repoName}/raw/*?at={commitSha}", + s_relativeSourceLinkJsonPath, + $"https://噸.com/test-org/{repoName}", + $"https://噸.com/test-org/{repoName}" + }); + + AssertEx.AreEqual( + $@"{{""documents"":{{""{ProjectSourceRoot.Replace(@"\", @"\\")}*"":""https://噸.com/projects/test-org/repos/{repoName}/raw/*?at={commitSha}""}}}}", + File.ReadAllText(Path.Combine(ProjectDir.Path, s_relativeSourceLinkJsonPath))); + + TestUtilities.ValidateAssemblyInformationalVersion( + Path.Combine(ProjectDir.Path, s_relativeOutputFilePath), + "1.0.0+" + commitSha); + + TestUtilities.ValidateNuSpecRepository( + Path.Combine(ProjectDir.Path, s_relativePackagePath), + type: "git", + commit: commitSha, + url: $"https://噸.com/test-org/{repoName}"); + } } }