diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.csproj b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.csproj index 3b498e1e..274777d8 100644 --- a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.csproj +++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe.csproj @@ -58,6 +58,7 @@ For recipe compatible with Cake Script Runners see Cake.Issues.Recipe. + diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/Context/RepositoryInfoProviderType.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/Context/RepositoryInfoProviderType.cs index 12947cdc..a7f12d59 100644 --- a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/Context/RepositoryInfoProviderType.cs +++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/Context/RepositoryInfoProviderType.cs @@ -15,5 +15,11 @@ public enum RepositoryInfoProviderType /// Read repository information using Git CLI. /// Requires Git CLI to be available in path. /// - Cli + Cli, + + /// + /// Read repository information using GitReader library. + /// Pure .NET implementation without external dependencies. + /// + GitReader } \ No newline at end of file diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/Context/State/IssuesState.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/Context/State/IssuesState.cs index 1ada4bc4..bb97005f 100644 --- a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/Context/State/IssuesState.cs +++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/Context/State/IssuesState.cs @@ -160,6 +160,9 @@ private static IRepositoryInfoProvider DetermineRepositoryInfoProvider( case RepositoryInfoProviderType.Cli: context.Information("Using Git CLI for providing repository information"); return new CliRepositoryInfoProvider(); + case RepositoryInfoProviderType.GitReader: + context.Information("Using GitReader for providing repository information"); + return new GitReaderRepositoryInfoProvider(); default: throw new NotImplementedException("Unsupported repository info provider"); } diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/RepositoryInfo/GitReaderRepositoryInfoProvider.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/RepositoryInfo/GitReaderRepositoryInfoProvider.cs new file mode 100644 index 00000000..7968fa25 --- /dev/null +++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/RepositoryInfo/GitReaderRepositoryInfoProvider.cs @@ -0,0 +1,106 @@ +namespace Cake.Frosting.Issues.Recipe; + +using Cake.Core; +using Cake.Core.IO; +using GitReader; +using GitReader.Structures; +using System.IO; + +/// +/// Provider to retrieve repository information using GitReader library. +/// +internal sealed class GitReaderRepositoryInfoProvider : IRepositoryInfoProvider +{ + /// + public DirectoryPath GetRepositoryRootDirectory(ICakeContext context, DirectoryPath buildRootDirectory) + { + context.NotNull(); + buildRootDirectory.NotNull(); + + // Search for .git directory starting from build root and going up + var currentDir = new DirectoryInfo(buildRootDirectory.FullPath); + while (currentDir != null) + { + var gitDir = System.IO.Path.Combine(currentDir.FullName, ".git"); + if (Directory.Exists(gitDir) || File.Exists(gitDir)) + { + return new DirectoryPath(currentDir.FullName); + } + currentDir = currentDir.Parent; + } + + throw new DirectoryNotFoundException($"Could not find Git repository root starting from {buildRootDirectory.FullPath}"); + } + + /// + public Uri GetRepositoryRemoteUrl(ICakeContext context, DirectoryPath repositoryRootDirectory) + { + context.NotNull(); + repositoryRootDirectory.NotNull(); + + try + { + // Read the origin remote URL from .git/config + var configPath = System.IO.Path.Combine(repositoryRootDirectory.FullPath, ".git", "config"); + if (File.Exists(configPath)) + { + var configLines = File.ReadAllLines(configPath); + bool inOriginRemote = false; + + foreach (var line in configLines) + { + var trimmedLine = line.Trim(); + + if (trimmedLine == "[remote \"origin\"]") + { + inOriginRemote = true; + continue; + } + + if (trimmedLine.StartsWith("[") && inOriginRemote) + { + // Exited the origin remote section + break; + } + + if (inOriginRemote && trimmedLine.StartsWith("url = ")) + { + var url = trimmedLine.Substring(6); // Remove "url = " + return new Uri(url); + } + } + } + + throw new InvalidOperationException("Could not find origin remote URL in repository configuration"); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Failed to read repository remote URL from {repositoryRootDirectory.FullPath}", ex); + } + } + + /// + public string GetCommitId(ICakeContext context, DirectoryPath repositoryRootDirectory) + { + context.NotNull(); + repositoryRootDirectory.NotNull(); + + try + { + // Use async method with GetAwaiter().GetResult() pattern as used in other parts of Cake + using var repository = Repository.Factory.OpenStructureAsync(repositoryRootDirectory.FullPath).GetAwaiter().GetResult(); + + if (repository.Head is Branch head) + { + var commit = head.GetHeadCommitAsync().GetAwaiter().GetResult(); + return commit.Hash.ToString(); + } + + throw new InvalidOperationException("Could not find HEAD commit in repository"); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Failed to read commit ID from {repositoryRootDirectory.FullPath}", ex); + } + } +} \ No newline at end of file diff --git a/Cake.Issues.Recipe/Content/addins.cake b/Cake.Issues.Recipe/Content/addins.cake index 5f6b2227..c7236663 100644 --- a/Cake.Issues.Recipe/Content/addins.cake +++ b/Cake.Issues.Recipe/Content/addins.cake @@ -21,3 +21,4 @@ #addin nuget:?package=Cake.AzureDevOps&version=5.0.2 #addin nuget:?package=Cake.GitHub&version=1.0.0 #addin nuget:?package=Octokit&version=13.0.1 +#addin nuget:?package=GitReader&version=1.4.0 diff --git a/Cake.Issues.Recipe/Content/data/IssuesData.cake b/Cake.Issues.Recipe/Content/data/IssuesData.cake index 4a51565c..a03e01bc 100644 --- a/Cake.Issues.Recipe/Content/data/IssuesData.cake +++ b/Cake.Issues.Recipe/Content/data/IssuesData.cake @@ -202,6 +202,9 @@ public class IssuesData case RepositoryInfoProviderType.Cli: context.Information("Using Git CLI for providing repository information"); return new CliRepositoryInfoProvider(); + case RepositoryInfoProviderType.GitReader: + context.Information("Using GitReader for providing repository information"); + return new GitReaderRepositoryInfoProvider(); default: throw new NotImplementedException("Unsupported repository info provider"); } diff --git a/Cake.Issues.Recipe/Content/data/RepositoryInfoProviderType.cake b/Cake.Issues.Recipe/Content/data/RepositoryInfoProviderType.cake index ee0be2d9..0f199d8f 100644 --- a/Cake.Issues.Recipe/Content/data/RepositoryInfoProviderType.cake +++ b/Cake.Issues.Recipe/Content/data/RepositoryInfoProviderType.cake @@ -12,5 +12,10 @@ public enum RepositoryInfoProviderType /// Read repository information using Git CLI. /// Requires Git CLI to be available in path. /// - Cli + Cli, + /// + /// Read repository information using GitReader library. + /// Pure .NET implementation without external dependencies. + /// + GitReader } \ No newline at end of file diff --git a/Cake.Issues.Recipe/Content/data/repositoryinfoprovider/GitReaderRepositoryInfoProvider.cake b/Cake.Issues.Recipe/Content/data/repositoryinfoprovider/GitReaderRepositoryInfoProvider.cake new file mode 100644 index 00000000..b298b391 --- /dev/null +++ b/Cake.Issues.Recipe/Content/data/repositoryinfoprovider/GitReaderRepositoryInfoProvider.cake @@ -0,0 +1,98 @@ +/// +/// Provider to retrieve repository information using GitReader library. +/// +public class GitReaderRepositoryInfoProvider : IRepositoryInfoProvider +{ + /// + public DirectoryPath GetRepositoryRootDirectory(ICakeContext context, DirectoryPath buildRootDirectory) + { + context.NotNull(); + buildRootDirectory.NotNull(); + + // Search for .git directory starting from build root and going up + var currentDir = new DirectoryInfo(buildRootDirectory.FullPath); + while (currentDir != null) + { + var gitDir = System.IO.Path.Combine(currentDir.FullName, ".git"); + if (Directory.Exists(gitDir) || File.Exists(gitDir)) + { + return new DirectoryPath(currentDir.FullName); + } + currentDir = currentDir.Parent; + } + + throw new DirectoryNotFoundException($"Could not find Git repository root starting from {buildRootDirectory.FullPath}"); + } + + /// + public Uri GetRepositoryRemoteUrl(ICakeContext context, DirectoryPath repositoryRootDirectory) + { + context.NotNull(); + repositoryRootDirectory.NotNull(); + + try + { + // Read the origin remote URL from .git/config + var configPath = System.IO.Path.Combine(repositoryRootDirectory.FullPath, ".git", "config"); + if (File.Exists(configPath)) + { + var configLines = File.ReadAllLines(configPath); + bool inOriginRemote = false; + + foreach (var line in configLines) + { + var trimmedLine = line.Trim(); + + if (trimmedLine == "[remote \"origin\"]") + { + inOriginRemote = true; + continue; + } + + if (trimmedLine.StartsWith("[") && inOriginRemote) + { + // Exited the origin remote section + break; + } + + if (inOriginRemote && trimmedLine.StartsWith("url = ")) + { + var url = trimmedLine.Substring(6); // Remove "url = " + return new Uri(url); + } + } + } + + throw new InvalidOperationException("Could not find origin remote URL in repository configuration"); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Failed to read repository remote URL from {repositoryRootDirectory.FullPath}", ex); + } + } + + /// + public string GetCommitId(ICakeContext context, DirectoryPath repositoryRootDirectory) + { + context.NotNull(); + repositoryRootDirectory.NotNull(); + + try + { + // Use async method with GetAwaiter().GetResult() pattern as used in other parts of Cake + using var repository = GitReader.Repository.Factory.OpenStructureAsync(repositoryRootDirectory.FullPath).GetAwaiter().GetResult(); + + if (repository.Head is GitReader.Structures.Branch head) + { + var commit = head.GetHeadCommitAsync().GetAwaiter().GetResult(); + return commit.Hash.ToString(); + } + + throw new InvalidOperationException("Could not find HEAD commit in repository"); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Failed to read commit ID from {repositoryRootDirectory.FullPath}", ex); + } + } +} \ No newline at end of file diff --git a/Cake.Issues.Recipe/Content/data/repositoryinfoprovider/repositoryinfoprovider.cake b/Cake.Issues.Recipe/Content/data/repositoryinfoprovider/repositoryinfoprovider.cake index 416f61a8..1d42fff7 100644 --- a/Cake.Issues.Recipe/Content/data/repositoryinfoprovider/repositoryinfoprovider.cake +++ b/Cake.Issues.Recipe/Content/data/repositoryinfoprovider/repositoryinfoprovider.cake @@ -1,3 +1,4 @@ #load IRepositoryInfoProvider.cake #load CakeGitRepositoryInfoProvider.cake -#load CliRepositoryInfoProvider.cake \ No newline at end of file +#load CliRepositoryInfoProvider.cake +#load GitReaderRepositoryInfoProvider.cake \ No newline at end of file