Skip to content

Commit 4b0fa79

Browse files
authored
Unified the path parameter (#12067)
* Normalized the path parameter * Enabled cache for repo root * Removed cache
1 parent 1c79a81 commit 4b0fa79

2 files changed

Lines changed: 166 additions & 34 deletions

File tree

tools/azsdk-cli/Azure.Sdk.Tools.Cli.Tests/Helpers/GitHelperTests.cs

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,90 @@ public void GetRepoRemoteUri_WithNonGitDirectory_ThrowsException()
7979

8080
try
8181
{
82-
Assert.Throws<RepositoryNotFoundException>(() => gitHelper.GetRepoRemoteUri(tempDir));
82+
Assert.Throws<InvalidOperationException>(() => gitHelper.GetRepoRemoteUri(tempDir));
83+
}
84+
finally
85+
{
86+
if (Directory.Exists(tempDir))
87+
{
88+
Directory.Delete(tempDir, true);
89+
}
90+
}
91+
}
92+
93+
[Test]
94+
public async Task GetRepoFullNameAsync_WithSubdirectoryPath_ReturnsCorrectFullName()
95+
{
96+
var testRepoPath = CreateTestRepoWithRemote("git@github.com:Azure/azure-rest-api-specs.git");
97+
var subDir = Path.Combine(testRepoPath, "subdirectory");
98+
Directory.CreateDirectory(subDir);
99+
mockGitHubService.Setup(x => x.GetGitHubParentRepoUrlAsync("Azure", "azure-rest-api-specs"))
100+
.ReturnsAsync(string.Empty); // Not a fork
101+
102+
try
103+
{
104+
var result = await gitHelper.GetRepoFullNameAsync(subDir);
105+
106+
Assert.That(result, Is.EqualTo("Azure/azure-rest-api-specs"));
107+
}
108+
finally
109+
{
110+
CleanupTestRepo(testRepoPath);
111+
}
112+
}
113+
114+
[Test]
115+
public async Task GetRepoFullNameAsync_WithForkRepoButDontFindUpstream_ReturnsDirectFullName()
116+
{
117+
var testRepoPath = CreateTestRepoWithRemote("https://github.com/UserFork/azure-rest-api-specs.git");
118+
119+
try
120+
{
121+
var result = await gitHelper.GetRepoFullNameAsync(testRepoPath, findUpstreamParent: false);
122+
123+
Assert.That(result, Is.EqualTo("UserFork/azure-rest-api-specs"));
124+
}
125+
finally
126+
{
127+
CleanupTestRepo(testRepoPath);
128+
}
129+
}
130+
131+
[Test]
132+
public async Task GetRepoFullNameAsync_WithEmptyPath_ThrowsArgumentException()
133+
{
134+
// Test empty string
135+
try
136+
{
137+
await gitHelper.GetRepoFullNameAsync("");
138+
Assert.Fail("Expected ArgumentException was not thrown");
139+
}
140+
catch (ArgumentException ex)
141+
{
142+
Assert.That(ex.ParamName, Is.EqualTo("pathInRepo"));
143+
}
144+
145+
// Test null
146+
try
147+
{
148+
await gitHelper.GetRepoFullNameAsync(null!);
149+
Assert.Fail("Expected ArgumentException was not thrown");
150+
}
151+
catch (ArgumentException ex)
152+
{
153+
Assert.That(ex.ParamName, Is.EqualTo("pathInRepo"));
154+
}
155+
}
156+
157+
[Test]
158+
public void GetRepoFullNameAsync_WithNonGitDirectory_ThrowsException()
159+
{
160+
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
161+
Directory.CreateDirectory(tempDir);
162+
163+
try
164+
{
165+
Assert.ThrowsAsync<InvalidOperationException>(async () => await gitHelper.GetRepoFullNameAsync(tempDir));
83166
}
84167
finally
85168
{

tools/azsdk-cli/Azure.Sdk.Tools.Cli/Helpers/GitHelper.cs

Lines changed: 82 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,30 @@ namespace Azure.Sdk.Tools.Cli.Helpers
99
public interface IGitHelper
1010
{
1111
// Get the owner
12-
public Task<string> GetRepoOwnerNameAsync(string path, bool findUpstreamParent = true);
13-
public Task<string> GetRepoFullNameAsync(string path, bool findUpstreamParent = true);
14-
public Uri GetRepoRemoteUri(string path);
15-
public string GetBranchName(string path);
16-
public string GetMergeBaseCommitSha(string path, string targetBranch);
17-
public string DiscoverRepoRoot(string path);
18-
public string GetRepoName(string path);
12+
public Task<string> GetRepoOwnerNameAsync(string pathInRepo, bool findUpstreamParent = true);
13+
public Task<string> GetRepoFullNameAsync(string pathInRepo, bool findUpstreamParent = true);
14+
public Uri GetRepoRemoteUri(string pathInRepo);
15+
public string GetBranchName(string pathInRepo);
16+
public string GetMergeBaseCommitSha(string pathInRepo, string targetBranch);
17+
public string DiscoverRepoRoot(string pathInRepo);
18+
public string GetRepoName(string pathInRepo);
1919
}
2020

2121
public class GitHelper(IGitHubService gitHubService, ILogger<GitHelper> logger) : IGitHelper
2222
{
2323
private readonly ILogger<GitHelper> logger = logger;
2424
private readonly IGitHubService gitHubService = gitHubService;
2525

26-
public string GetMergeBaseCommitSha(string path, string targetBranchName)
26+
/// <summary>
27+
/// Gets the SHA of the merge base (common ancestor) between the current branch and the target branch.
28+
/// </summary>
29+
/// <param name="pathInRepo">Any path within the git repository (file or directory)</param>
30+
/// <param name="targetBranchName">The name of the target branch to find the merge base with</param>
31+
/// <returns>The SHA of the merge base commit, or empty string if not found</returns>
32+
public string GetMergeBaseCommitSha(string pathInRepo, string targetBranchName)
2733
{
28-
using (var repo = new Repository(path))
34+
var repoRoot = DiscoverRepoRoot(pathInRepo);
35+
using (var repo = new Repository(repoRoot))
2936
{
3037
// Get the current branch
3138
Branch currentBranch = repo.Head;
@@ -38,16 +45,29 @@ public string GetMergeBaseCommitSha(string path, string targetBranchName)
3845
}
3946
}
4047

41-
public string GetBranchName(string repoPath)
48+
/// <summary>
49+
/// Gets the friendly name of the current branch in the repository.
50+
/// </summary>
51+
/// <param name="pathInRepo">Any path within the git repository (file or directory)</param>
52+
/// <returns>The friendly name of the current branch</returns>
53+
public string GetBranchName(string pathInRepo)
4254
{
43-
using var repo = new Repository(repoPath);
55+
var repoRoot = DiscoverRepoRoot(pathInRepo);
56+
using var repo = new Repository(repoRoot);
4457
var branchName = repo.Head.FriendlyName;
4558
return branchName;
4659
}
4760

48-
public Uri GetRepoRemoteUri(string path)
61+
/// <summary>
62+
/// Gets the remote origin URI of the repository in HTTPS format.
63+
/// </summary>
64+
/// <param name="pathInRepo">Any path within the git repository (file or directory)</param>
65+
/// <returns>The HTTPS URI of the remote origin</returns>
66+
/// <exception cref="InvalidOperationException">Thrown when unable to determine remote URL</exception>
67+
public Uri GetRepoRemoteUri(string pathInRepo)
4968
{
50-
using var repo = new Repository(path);
69+
var repoRoot = DiscoverRepoRoot(pathInRepo);
70+
using var repo = new Repository(repoRoot);
5171
var remote = repo.Network?.Remotes["origin"];
5272
if (remote != null)
5373
{
@@ -87,9 +107,16 @@ private static string ConvertSshToHttpsUrl(string gitUrl)
87107
return gitUrl;
88108
}
89109

90-
public async Task<string> GetRepoOwnerNameAsync(string path, bool findUpstreamParent = true)
110+
/// <summary>
111+
/// Gets the owner name of the repository, optionally finding the upstream parent if the repo is a fork.
112+
/// </summary>
113+
/// <param name="pathInRepo">Any path within the git repository (file or directory)</param>
114+
/// <param name="findUpstreamParent">Whether to find the upstream parent repo if this is a fork (default: true)</param>
115+
/// <returns>The owner name of the repository or its upstream parent</returns>
116+
/// <exception cref="InvalidOperationException">Thrown when unable to determine repository owner</exception>
117+
public async Task<string> GetRepoOwnerNameAsync(string pathInRepo, bool findUpstreamParent = true)
91118
{
92-
var uri = GetRepoRemoteUri(path);
119+
var uri = GetRepoRemoteUri(pathInRepo);
93120
var segments = uri.Segments;
94121
string repoOwner = string.Empty;
95122
string repoName = string.Empty;
@@ -122,52 +149,74 @@ public async Task<string> GetRepoOwnerNameAsync(string path, bool findUpstreamPa
122149
throw new InvalidOperationException("Unable to determine repository owner.");
123150
}
124151

125-
// Get the full name of repo in the format of "{owner/name}", e.g. "Azure/azure-rest-api-specs"
126-
public async Task<string> GetRepoFullNameAsync(string path, bool findUpstreamParent = true)
152+
/// <summary>
153+
/// Gets the full name of the repository in the format "{owner}/{name}", e.g. "Azure/azure-rest-api-specs".
154+
/// </summary>
155+
/// <param name="pathInRepo">Any path within the git repository (file or directory)</param>
156+
/// <param name="findUpstreamParent">Whether to find the upstream parent repo if this is a fork (default: true)</param>
157+
/// <returns>The full name of the repository in "owner/name" format</returns>
158+
/// <exception cref="ArgumentException">Thrown when pathInRepo is null or empty</exception>
159+
public async Task<string> GetRepoFullNameAsync(string pathInRepo, bool findUpstreamParent = true)
127160
{
128-
if (!string.IsNullOrEmpty(path))
161+
if (!string.IsNullOrEmpty(pathInRepo))
129162
{
130-
var repoOwner = await GetRepoOwnerNameAsync(path, findUpstreamParent);
131-
var repoName = GetRepoName(path);
163+
var repoOwner = await GetRepoOwnerNameAsync(pathInRepo, findUpstreamParent);
164+
var repoName = GetRepoName(pathInRepo);
132165
return $"{repoOwner}/{repoName}";
133166
}
134167

135-
throw new ArgumentException("Invalid repository path.", nameof(path));
168+
throw new ArgumentException("Invalid repository path.", nameof(pathInRepo));
136169
}
137170

138-
public string DiscoverRepoRoot(string path)
171+
/// <summary>
172+
/// Discovers and returns the root directory path of the git repository containing the specified path.
173+
/// </summary>
174+
/// <param name="pathInRepo">Any path within the git repository (file or directory)</param>
175+
/// <returns>The absolute path to the repository root directory</returns>
176+
/// <exception cref="InvalidOperationException">Thrown when no git repository is found at or above the specified path</exception>
177+
public string DiscoverRepoRoot(string pathInRepo)
139178
{
140-
var repoPath = Repository.Discover(path);
179+
// Discover the repo root for this path
180+
var repoPath = Repository.Discover(pathInRepo);
141181
if (string.IsNullOrEmpty(repoPath))
142182
{
143-
throw new InvalidOperationException($"No git repository found at or above the path: {path}");
183+
throw new InvalidOperationException($"No git repository found at or above the path: {pathInRepo}");
144184
}
145185

146186
// Repository.Discover returns the path to .git directory
147187
// The repository root is the parent directory of .git
148188
var gitDir = new DirectoryInfo(repoPath);
149-
return gitDir.Parent?.FullName ?? throw new InvalidOperationException("Unable to determine repository root");
189+
if (gitDir.Parent == null || string.IsNullOrEmpty(gitDir.Parent.FullName))
190+
{
191+
throw new InvalidOperationException("Unable to determine repository root");
192+
}
193+
194+
return gitDir.Parent.FullName;
150195
}
151196

152-
// Get the repository name from the local path
153-
public string GetRepoName(string path)
197+
/// <summary>
198+
/// Gets the repository name from the remote origin URL.
199+
/// </summary>
200+
/// <param name="pathInRepo">Any path within the git repository (file or directory)</param>
201+
/// <returns>The name of the repository (without the owner)</returns>
202+
/// <exception cref="ArgumentException">Thrown when pathInRepo is null or empty</exception>
203+
/// <exception cref="InvalidOperationException">Thrown when unable to determine repository name from remote URL</exception>
204+
public string GetRepoName(string pathInRepo)
154205
{
155-
if (string.IsNullOrEmpty(path))
206+
if (string.IsNullOrEmpty(pathInRepo))
156207
{
157-
throw new ArgumentException("Invalid repository path.", nameof(path));
208+
throw new ArgumentException("Invalid repository path.", nameof(pathInRepo));
158209
}
159210

160-
var repoRoot = DiscoverRepoRoot(path);
161-
var uri = GetRepoRemoteUri(repoRoot);
211+
var uri = GetRepoRemoteUri(pathInRepo);
162212
var segments = uri.Segments;
163213

164214
if (segments.Length < 2)
165215
{
166-
throw new InvalidOperationException($"Unable to parse repository owner and name from remote URL: {uri}");
216+
throw new InvalidOperationException($"Unable to parse repository name from remote URL: {uri}");
167217
}
168218

169219
string repoName = segments[^1].TrimEnd(".git".ToCharArray());
170-
171220
return repoName;
172221
}
173222
}

0 commit comments

Comments
 (0)