Skip to content

Commit 898dc0f

Browse files
github-actions[bot]EalesCopilot
authored
[release/10.0.4xx] Preserve .git suffix in repository names when generating SourceLink URLs (#1658)
* Preserve .git suffix in repository names when generating SourceLink URLs Do not strip the `.git` suffix from repository names in GetSourceLinkUrlGitTask. Some repositories legitimately include `.git` as part of their name, and removing the suffix produces incorrect SourceLink URLs and 404 responses. This change keeps `.git` when it is part of the repository name. Additionally update the GitWeb provider to avoid appending `.git` when the suffix is already present, preventing URLs like `repo.git.git`. Add regression tests for repository names ending with `.git`. * Update src/SourceLink.AzureRepos.Git.UnitTests/GetSourceLinkUrlTests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/SourceLink.AzureRepos.Git.UnitTests/GetSourceLinkUrlTests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * SourceLinkUrl is nullable, but the test asserts it is not null or empty before returning it. The null-forgiving operator is used here to satisfy the nullable analysis. * Revert change in GetSourceLinkUrlGitTask The previous change modified the shared Git SourceLink task to preserve the `.git` suffix in repository names. This introduced regressions in other providers (e.g. Bitbucket and Common tests). The fix will be moved to the Azure Repos provider where the exact repository name is required by the Azure DevOps API, instead of changing the shared behavior for all providers. * Refactor Git URL handling in GetSourceLinkUrlGitTask * Restore GetSourceLinkUrlTests and add tests for Azure Repos repositories ending with .git Ensure that repository names ending with ".git" are not treated the same as repositories without the suffix and that the suffix is preserved in the generated SourceLink URL. * Remove duplicated Azure Repos .git regression tests Fix build failure caused by accidentally duplicated test methods introduced while adding regression tests for repository names ending with `.git`. * Enhance GetSourceLinkUrlTests with error assertion Restore error comment – removal was unintentional. * Add comment explaining why gitUri.GetPath() is used instead of relativeUrl for AzDO --------- Co-authored-by: Artur Zegarek <artureales@gmail.com> Co-authored-by: Eales <46241595+Eales@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 317814b commit 898dc0f

4 files changed

Lines changed: 62 additions & 5 deletions

File tree

src/Common/GitProvider/GetSourceLinkUrlGitTask.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Licensed to the .NET Foundation under one or more agreements.
1+
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the License.txt file in the project root for more information.
44

@@ -222,7 +222,7 @@ static bool IsValidContentUri(Uri uri)
222222
}
223223
}
224224

225-
private static bool TryGetMatchingContentUri(UrlMapping[] mappings, Uri repoUri, [NotNullWhen(true)]out Uri? contentUri, out ITaskItem? hostItem)
225+
private static bool TryGetMatchingContentUri(UrlMapping[] mappings, Uri repoUri, [NotNullWhen(true)] out Uri? contentUri, out ITaskItem? hostItem)
226226
{
227227
UrlMapping? FindMatch(bool exactHost)
228228
{

src/SourceLink.AzureRepos.Git.UnitTests/AzureDevOpsUrlParserHostedTests.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ public void TryParseHostedHttp_Error(string host, string relativeUrl)
5555
[InlineData("contoso.com", "/account/project/_git/repo", "account/project", "repo")]
5656
[InlineData("contoso.com", "/account/project/_git/_full/repo", "account/project", "repo")]
5757
[InlineData("contoso.com", "/account/project/_git/_optimized/repo", "account/project", "repo")]
58+
[InlineData("dev.azure.com", "/org/project/_git/repo.git", "org/project", "repo.git")]
59+
[InlineData("dev.azure.com", "/org/project/_git/repo.git/", "org/project", "repo.git")]
60+
[InlineData("account.visualstudio.com", "/DefaultCollection/project/_git/repo.git", "project", "repo.git")]
61+
5862
public void TryParseHostedHttp_Success(string host, string relativeUrl, string repositoryPath, string repositoryName)
5963
{
6064
Assert.True(AzureDevOpsUrlParser.TryParseHostedHttp(host, relativeUrl, out var actualRepositoryPath, out var actualRepositoryName));

src/SourceLink.AzureRepos.Git.UnitTests/GetSourceLinkUrlTests.cs

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public void BadUrl(string domainAndAccount, string host)
7171
};
7272

7373
var result = task.Execute();
74-
74+
7575
// ERROR : The value of SourceRoot.RepositoryUrl with identity '/src/' is invalid: 'http://account.visualstudio.com/_git'""
7676
AssertEx.AssertEqualToleratingWhitespaceDifferences(
7777
"ERROR : " + string.Format(CommonResources.ValueOfWithIdentityIsInvalid, "SourceRoot.RepositoryUrl", "/src/", $"http://{domainAndAccount}/_git"), engine.Log);
@@ -155,7 +155,6 @@ public void Project_Team_NonVisualStudioHost()
155155

156156
var result = task.Execute();
157157

158-
// ERROR : The value of SourceRoot.RepositoryUrl with identity '/src/' is invalid: 'http://contoso.com/account/project/team/_git/repo'""
159158
AssertEx.AssertEqualToleratingWhitespaceDifferences(
160159
"ERROR : " + string.Format(CommonResources.ValueOfWithIdentityIsInvalid, "SourceRoot.RepositoryUrl", "/src/", "http://contoso.com/account/project/team/_git/repo"), engine.Log);
161160

@@ -285,5 +284,56 @@ public void VisualStudioHost_ImplicitHost_DeafultCollection_Project_Team(string
285284
AssertEx.AreEqual($"https://{domain}/project/_apis/git/repositories/repo/items?api-version=1.0&versionType=commit&version=0123456789abcdefABCDEF000000000000000000&path=/*", task.SourceLinkUrl);
286285
Assert.True(result);
287286
}
287+
288+
[Fact]
289+
public void DevAzureCom_RepositoryName_WithDotGit_IsNotIgnored()
290+
{
291+
var urlWithoutDotGit = ExecuteDevAzureCom("https://dev.azure.com/org/project/_git/repo");
292+
var urlWithDotGit = ExecuteDevAzureCom("https://dev.azure.com/org/project/_git/repo.git");
293+
294+
Assert.True(
295+
!string.Equals(urlWithoutDotGit, urlWithDotGit, StringComparison.Ordinal),
296+
$"Repository name is ignored: URLs are identical.\n" +
297+
$"Input without .git: https://dev.azure.com/org/project/_git/repo\n" +
298+
$"Input with .git: https://dev.azure.com/org/project/_git/repo.git\n" +
299+
$"Output: {urlWithoutDotGit}");
300+
}
301+
302+
[Fact]
303+
public void DevAzureCom_RepositoryName_WithDotGit_IsPreservedInOutput()
304+
{
305+
var url = ExecuteDevAzureCom("https://dev.azure.com/org/project/_git/repo.git");
306+
307+
// Ensure the '.git' suffix is preserved in the repository segment of the URL, not just anywhere.
308+
Assert.Contains(
309+
"project/_apis/git/repositories/repo.git/items",
310+
url);
311+
}
312+
313+
private static string ExecuteDevAzureCom(string repositoryUrl)
314+
{
315+
var engine = new MockEngine();
316+
317+
var task = new GetSourceLinkUrl()
318+
{
319+
BuildEngine = engine,
320+
SourceRoot = new MockItem("/src/",
321+
KVP("RepositoryUrl", repositoryUrl),
322+
KVP("SourceControl", "git"),
323+
KVP("RevisionId", "0123456789abcdefABCDEF000000000000000000")),
324+
Hosts = new[]
325+
{
326+
new MockItem("dev.azure.com")
327+
}
328+
};
329+
330+
var result = task.Execute();
331+
332+
AssertEx.AssertEqualToleratingWhitespaceDifferences("", engine.Log);
333+
Assert.True(result);
334+
Assert.False(string.IsNullOrEmpty(task.SourceLinkUrl));
335+
336+
return task.SourceLinkUrl!;
337+
}
288338
}
289339
}

src/SourceLink.AzureRepos.Git/GetSourceLinkUrl.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ protected override Uri GetDefaultContentUriFromRepositoryUri(Uri repositoryUri)
3232

3333
protected override string? BuildSourceLinkUrl(Uri contentUri, Uri gitUri, string relativeUrl, string revisionId, ITaskItem? hostItem)
3434
{
35-
if (!AzureDevOpsUrlParser.TryParseHostedHttp(gitUri.GetHost(), relativeUrl, out var projectPath, out var repositoryName))
35+
// Azure DevOps does not support optional ".git" suffix in repository URLs and adding it may result in 404.
36+
// Unlike other providers (GitHub, GitLab, etc.), relativeUrl may include the ".git" suffix,
37+
// so use gitUri.GetPath() here instead.
38+
if (!AzureDevOpsUrlParser.TryParseHostedHttp(gitUri.GetHost(), gitUri.GetPath(), out var projectPath, out var repositoryName))
3639
{
3740
Log.LogError(CommonResources.ValueOfWithIdentityIsInvalid, Names.SourceRoot.RepositoryUrlFullName, SourceRoot!.ItemSpec, gitUri);
3841
return null;

0 commit comments

Comments
 (0)