From a2e624ad4c96bfe6494d7b782f7006a478c57b11 Mon Sep 17 00:00:00 2001 From: Hank McCord Date: Wed, 20 Aug 2025 16:47:30 -0400 Subject: [PATCH] Refactor URL parsing to support SSH remotes --- src/GitHubActionsVS.csproj | 1 + src/Helpers/GitUri.cs | 106 ++++++++++++++++++++ src/Helpers/RepoInfo.cs | 47 ++++----- src/ToolWindows/GHActionsToolWindow.xaml.cs | 4 +- 4 files changed, 131 insertions(+), 27 deletions(-) create mode 100644 src/Helpers/GitUri.cs diff --git a/src/GitHubActionsVS.csproj b/src/GitHubActionsVS.csproj index c734762..839ac6f 100644 --- a/src/GitHubActionsVS.csproj +++ b/src/GitHubActionsVS.csproj @@ -54,6 +54,7 @@ + diff --git a/src/Helpers/GitUri.cs b/src/Helpers/GitUri.cs new file mode 100644 index 0000000..db90736 --- /dev/null +++ b/src/Helpers/GitUri.cs @@ -0,0 +1,106 @@ +using System.Text.RegularExpressions; + +namespace GitHubActionsVS.Helpers; + +internal class GitUri +{ + public string OriginalUri { get; } + public string Scheme { get; } + public string Host { get; } + public int? Port { get; } + public string Path { get; } + + private GitUri(string originalUri, string scheme, string host, int? port, string path) + { + OriginalUri = originalUri; + Scheme = scheme; + Host = host; + Port = port; + Path = path; + } + + public static GitUri Parse(string originalUri) + { + GitUri gitUri = null; + originalUri = originalUri?.Trim(); + if (!string.IsNullOrEmpty(originalUri)) + { + Uri uri; + if (Uri.TryCreate(originalUri, UriKind.Absolute, out uri) && !string.IsNullOrEmpty(uri.Host)) + { + gitUri = new GitUri(originalUri, uri.Scheme, uri.Host, GetUriPort(uri), uri.AbsolutePath); + } + else if (!originalUri.Contains("://") && Uri.TryCreate("https://" + originalUri, UriKind.Absolute, out uri) && !string.IsNullOrEmpty(uri.Host)) + { + gitUri = new GitUri(originalUri, null, uri.Host, GetUriPort(uri), uri.AbsolutePath); + } + else + { + // ssh style git URI: git@github.com:owner/repo.git + Match match = new Regex("^(?:(?:.+)@)?(?'host'[^\\:\\/]+)\\:(?:(?'port'\\d+/)?)?(?'path'.+)$").Match(originalUri); + if (match.Success) + { + string host = match.Groups["host"].Value; + int? port = null; + string portValue = match.Groups["port"].Value; + if (!string.IsNullOrEmpty(portValue)) + { + if (int.TryParse(portValue.TrimEnd(['/']), out int parsedPort)) + { + port = new int?(parsedPort); + } + } + string path = "/" + match.Groups["path"].Value; + gitUri = new GitUri(originalUri, null, host, port, path); + } + } + } + if (gitUri == null) + { + throw new UriFormatException("The provided URI is not a valid HTTP or SSH URI."); + } + return gitUri; + } + + private static int? GetUriPort(Uri uri) + { + if (uri.Port != -1 && !uri.IsDefaultPort) + { + return uri.Port; + } + return null; + } + + public string GetRepositoryName() + { + if (string.IsNullOrEmpty(Path)) + { + return null; + } + string[] segments = Path.Split('/'); + if (segments.Length > 0) + { + string lastSegment = segments[segments.Length - 1]; + if (lastSegment.EndsWith(".git")) + { + return lastSegment.Substring(0, lastSegment.Length - 4); + } + return lastSegment; + } + return null; + } + + public string GetRepositoryOwner() + { + if (string.IsNullOrEmpty(Path)) + { + return null; + } + string[] segments = Path.Split('/'); + if (segments.Length > 1) + { + return segments[segments.Length - 2]; + } + return null; + } +} diff --git a/src/Helpers/RepoInfo.cs b/src/Helpers/RepoInfo.cs index 978146a..17dc89a 100644 --- a/src/Helpers/RepoInfo.cs +++ b/src/Helpers/RepoInfo.cs @@ -10,39 +10,36 @@ internal class RepoInfo public string RepoUrl { get; set; } public string CurrentBranch { get; set; } - - internal void FindGitFolder(string path, out string foundPath) + internal bool TryFindGitFolder(string path, out string foundPath) { foundPath = null; - // Check if the current directory contains a .git folder - if (Directory.Exists(Path.Combine(path, ".git"))) + string currentPath = path; + + while (!string.IsNullOrEmpty(currentPath)) { - foundPath = path; - var repo = new LibGit2Sharp.Repository(foundPath); - var remote = repo.Network.Remotes.FirstOrDefault(); - if (remote is not null) + // Check if the current directory contains a .git folder + if (Directory.Exists(Path.Combine(currentPath, ".git"))) { - var url = remote.Url; - if (url.Contains("github.com")) + foundPath = currentPath; + var repo = new LibGit2Sharp.Repository(foundPath); + var remote = repo.Network.Remotes.FirstOrDefault(); + if (remote is not null) { - IsGitHub = true; - var parts = url.Split('/'); - RepoOwner = parts[parts.Length - 2]; - RepoName = parts[parts.Length - 1].Replace(".git", ""); - RepoUrl = url.Replace(".git", ""); + var remoteUri = GitUri.Parse(remote.Url); + RepoOwner = remoteUri.GetRepositoryOwner(); + RepoName = remoteUri.GetRepositoryName(); + RepoUrl = $"https://{remoteUri.Host}{(remoteUri.Port is not null ? $":{remoteUri.Port}" : "")}/{RepoOwner}/{RepoName}"; CurrentBranch = repo.Head.FriendlyName; + IsGitHub = remoteUri.Host == "github.com"; } + + return true; } - return; - } - else - { - string parentPath = Directory.GetParent(path)?.FullName; - if (!string.IsNullOrEmpty(parentPath)) - { - FindGitFolder(parentPath, out foundPath); // Recursively search the parent directory - } + + // Move to parent directory + currentPath = Directory.GetParent(currentPath)?.FullName; } - return; + + return false; } } diff --git a/src/ToolWindows/GHActionsToolWindow.xaml.cs b/src/ToolWindows/GHActionsToolWindow.xaml.cs index 9a58404..94f6a80 100644 --- a/src/ToolWindows/GHActionsToolWindow.xaml.cs +++ b/src/ToolWindows/GHActionsToolWindow.xaml.cs @@ -112,9 +112,9 @@ public async Task GetRepoInfoAsync() } var projectPath = solution?.FullPath; - _repoInfo.FindGitFolder(projectPath, out string gitPath); + var found = _repoInfo.TryFindGitFolder(projectPath, out string gitPath); - if (string.IsNullOrWhiteSpace(gitPath)) + if (!found) { await _pane.WriteLineAsync($"[{DateTime.UtcNow.ToString("o")}] No git repo found"); Debug.WriteLine("No git repo found");