diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/BuildServers/BaseBuildServer.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/BuildServers/BaseBuildServer.cs index a62445fd..24ea8829 100644 --- a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/BuildServers/BaseBuildServer.cs +++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/BuildServers/BaseBuildServer.cs @@ -1,8 +1,6 @@ using Cake.Core.IO; -using Cake.Git; using Cake.Issues; using System; -using System.Linq; namespace Cake.Frosting.Issues.Recipe { @@ -19,8 +17,7 @@ public virtual Uri DetermineRepositoryRemoteUrl( context.NotNull(nameof(context)); repositoryRootDirectory.NotNull(nameof(repositoryRootDirectory)); - var currentBranch = context.GitBranchCurrent(repositoryRootDirectory); - return new Uri(currentBranch.Remotes.Single(x => x.Name == "origin").Url); + return context.State.RepositoryInfo.GetRepositoryRemoteUrl(context, repositoryRootDirectory); } /// @@ -31,7 +28,7 @@ public virtual string DetermineCommitId( context.NotNull(nameof(context)); repositoryRootDirectory.NotNull(nameof(repositoryRootDirectory)); - return context.GitLogTip(repositoryRootDirectory).Sha; + return context.State.RepositoryInfo.GetCommitId(context, repositoryRootDirectory); } /// diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/Context/IssuesContext.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/Context/IssuesContext.cs index eb44bdc9..11510b4c 100644 --- a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/Context/IssuesContext.cs +++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/Context/IssuesContext.cs @@ -21,11 +21,14 @@ public class IssuesContext : FrostingContext /// Creates a new instance of the class. /// /// The Cake context. - public IssuesContext(ICakeContext context) + /// Defines how information about the Git repository should be determined. + public IssuesContext( + ICakeContext context, + RepositoryInfoProviderType repositoryInfoProviderType) : base(context) { this.Parameters = new IssuesParameters(); - this.State = new IssuesState(this); + this.State = new IssuesState(this, repositoryInfoProviderType); } } } 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 new file mode 100644 index 00000000..ad91a8e1 --- /dev/null +++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/Context/RepositoryInfoProviderType.cs @@ -0,0 +1,20 @@ +namespace Cake.Frosting.Issues.Recipe +{ + /// + /// Supported ways to read repository information. + /// + public enum RepositoryInfoProviderType + { + /// + /// Read repository information using Cake.Git addin. + /// Requires system to be compatible with Cake.Git addin. + /// + CakeGit, + + /// + /// Read repository information using Git CLI. + /// Requires Git CLI to be available in path. + /// + Cli + } +} \ 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 7f8ef378..6d28be2e 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 @@ -3,7 +3,6 @@ using Cake.Common.Diagnostics; using Cake.Common.IO; using Cake.Core.IO; -using Cake.Git; using Cake.Issues; using System; using System.Collections.Generic; @@ -47,6 +46,11 @@ public class IssuesState /// public FilePath SummaryIssuesReport { get; set; } + /// + /// Gets the provider to read information about the Git repository. + /// + public IRepositoryInfoProvider RepositoryInfo { get; } + /// /// Gets the build server under which the build is running. /// Returns null if running locally or on an unsupported build server. @@ -68,7 +72,10 @@ public class IssuesState /// Creates a new instance of the class. /// /// The Cake context. - public IssuesState(IssuesContext context) + /// Defines how information about the Git repository should be determined. + public IssuesState( + IssuesContext context, + RepositoryInfoProviderType repositoryInfoProviderType) { if (context == null) { @@ -78,7 +85,9 @@ public IssuesState(IssuesContext context) this.BuildRootDirectory = context.MakeAbsolute(context.Directory("./")); context.Information("Build script root directory: {0}", this.BuildRootDirectory); - this.RepositoryRootDirectory = context.GitFindRootFromPath(this.BuildRootDirectory); + this.RepositoryInfo = DetermineRepositoryInfoProvider(context, repositoryInfoProviderType); + + this.RepositoryRootDirectory = this.RepositoryInfo.GetRepositoryRootDirectory(context, this.BuildRootDirectory); context.Information("Repository root directory: {0}", this.RepositoryRootDirectory); this.BuildServer = DetermineBuildServer(context); @@ -127,6 +136,34 @@ public void AddIssues(IEnumerable issues) this.issues.AddRange(issues); } + /// + /// Determines the repository info provider to use. + /// + /// The Cake context. + /// Defines how information about the Git repository should be determined. + /// The repository info provider which should be used. + private static IRepositoryInfoProvider DetermineRepositoryInfoProvider( + IssuesContext context, + RepositoryInfoProviderType repositoryInfoProviderType) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + switch (repositoryInfoProviderType) + { + case RepositoryInfoProviderType.CakeGit: + context.Information("Using Cake.Git for providing repository information"); + return new CliRepositoryInfoProvider(); + case RepositoryInfoProviderType.Cli: + context.Information("Using Git CLI for providing repository information"); + return new CliRepositoryInfoProvider(); + default: + throw new NotImplementedException("Unsupported repository info provider"); + } + } + /// /// Determines the build server on which the build is running. /// diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/RepositoryInfo/CakeGitRepositoryInfoProvider.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/RepositoryInfo/CakeGitRepositoryInfoProvider.cs new file mode 100644 index 00000000..41210390 --- /dev/null +++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/RepositoryInfo/CakeGitRepositoryInfoProvider.cs @@ -0,0 +1,43 @@ +using Cake.Core; +using Cake.Core.IO; +using Cake.Git; +using Cake.Issues; +using System; +using System.Linq; + +namespace Cake.Frosting.Issues.Recipe +{ + /// + /// Provider to retrieve repository information using Cake.Git addin. + /// + public class CakeGitRepositoryInfoProvider : IRepositoryInfoProvider + { + /// + public DirectoryPath GetRepositoryRootDirectory(ICakeContext context, DirectoryPath buildRootDirectory) + { + context.NotNull(nameof(context)); + buildRootDirectory.NotNull(nameof(buildRootDirectory)); + + return context.GitFindRootFromPath(buildRootDirectory); + } + + /// + public Uri GetRepositoryRemoteUrl(ICakeContext context, DirectoryPath repositoryRootDirectory) + { + context.NotNull(nameof(context)); + repositoryRootDirectory.NotNull(nameof(repositoryRootDirectory)); + + var currentBranch = context.GitBranchCurrent(repositoryRootDirectory); + return new Uri(currentBranch.Remotes.Single(x => x.Name == "origin").Url); + } + + /// + public string GetCommitId(ICakeContext context, DirectoryPath repositoryRootDirectory) + { + context.NotNull(nameof(context)); + repositoryRootDirectory.NotNull(nameof(repositoryRootDirectory)); + + return context.GitLogTip(repositoryRootDirectory).Sha; + } + } +} \ No newline at end of file diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/RepositoryInfo/CliRepositoryInfoProvider.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/RepositoryInfo/CliRepositoryInfoProvider.cs new file mode 100644 index 00000000..a022cbc4 --- /dev/null +++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/RepositoryInfo/CliRepositoryInfoProvider.cs @@ -0,0 +1,75 @@ +using Cake.Common; +using Cake.Core; +using Cake.Core.IO; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Cake.Frosting.Issues.Recipe +{ + /// + /// Provider to retrieve repository information using Git CLI. + /// + public class CliRepositoryInfoProvider : IRepositoryInfoProvider + { + /// + public DirectoryPath GetRepositoryRootDirectory(ICakeContext context, DirectoryPath buildRootDirectory) + { + var result = + this.GitCommand(context, buildRootDirectory, "rev-parse", "--show-toplevel"); + return new DirectoryPath(result.Single()); + } + + /// + public Uri GetRepositoryRemoteUrl(ICakeContext context, DirectoryPath repositoryRootDirectory) + { + var result = + this.GitCommand(context, repositoryRootDirectory, "config", "--get", "remote.origin.url"); + return new Uri(result.Single()); + } + + /// + public string GetCommitId(ICakeContext context, DirectoryPath repositoryRootDirectory) + { + return + this.GitCommand(context, repositoryRootDirectory, "rev-parse", "HEAD") + .Single(); + } + + private IEnumerable GitCommand( + ICakeContext context, + DirectoryPath repositoryRootFolder, + params string[] arguments) + { + if (!arguments.Any()) + { + throw new ArgumentOutOfRangeException(nameof(arguments)); + } + + var gitArguments = string.Join(" ", arguments); + + var exitCode = context.StartProcess( + "git", + new ProcessSettings + { + Arguments = gitArguments, + WorkingDirectory = repositoryRootFolder.FullPath, + RedirectStandardOutput = true, + RedirectStandardError = true + }, + out var redirectedStandardOutput, + out var redirectedErrorOutput + ); + + if (exitCode != 0) + { + throw new Exception( + $"Git command failed with arguments {gitArguments}. Exit code: {exitCode}. Error output: {string.Join(Environment.NewLine, redirectedErrorOutput)}" + ); + } + + return redirectedStandardOutput; + + } + } +} \ No newline at end of file diff --git a/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/RepositoryInfo/IRepositoryInfoProvider.cs b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/RepositoryInfo/IRepositoryInfoProvider.cs new file mode 100644 index 00000000..f761a0c6 --- /dev/null +++ b/Cake.Frosting.Issues.Recipe/Cake.Frosting.Issues.Recipe/RepositoryInfo/IRepositoryInfoProvider.cs @@ -0,0 +1,36 @@ +using Cake.Core; +using Cake.Core.IO; +using System; + +namespace Cake.Frosting.Issues.Recipe +{ + /// + /// Description of a provider to retrieve repository information. + /// + public interface IRepositoryInfoProvider + { + /// + /// Returns the root directory of the current repository. + /// + /// The Cake context. + /// Root directory of the build script. + /// The root directory of the current repository. + DirectoryPath GetRepositoryRootDirectory(ICakeContext context, DirectoryPath buildRootDirectory); + + /// + /// Returns the URL of the remote repository. + /// + /// The Cake context. + /// Root directory of the repository. + /// The URL of the remote repository. + Uri GetRepositoryRemoteUrl(ICakeContext context, DirectoryPath repositoryRootDirectory); + + /// + /// Returns the SHA hash of the current commit. + /// + /// The Cake context. + /// Root directory of the repository. + /// The SHA hash of the current commit + string GetCommitId(ICakeContext context, DirectoryPath repositoryRootDirectory); + } +} \ No newline at end of file diff --git a/Cake.Issues.Recipe/Content/build.cake b/Cake.Issues.Recipe/Content/build.cake index b57a2b27..00385c12 100644 --- a/Cake.Issues.Recipe/Content/build.cake +++ b/Cake.Issues.Recipe/Content/build.cake @@ -13,6 +13,11 @@ /// var IssuesBuildTasks = new IssuesBuildTaskDefinitions(); +/// +/// Defines how information about the Git repository should be determined. +/// +var RepositoryInfoProvider = RepositoryInfoProviderType.CakeGit; + /////////////////////////////////////////////////////////////////////////////// // SETUP / TEARDOWN /////////////////////////////////////////////////////////////////////////////// @@ -20,7 +25,7 @@ var IssuesBuildTasks = new IssuesBuildTaskDefinitions(); Setup(setupContext => { Information("Initializing Cake.Issues.Recipe (Version {0})...", BuildMetaDataCakeIssuesRecipe.Version); - return new IssuesData(setupContext); + return new IssuesData(setupContext, RepositoryInfoProvider); }); /////////////////////////////////////////////////////////////////////////////// diff --git a/Cake.Issues.Recipe/Content/data/IssuesData.cake b/Cake.Issues.Recipe/Content/data/IssuesData.cake index 9b83a979..49282507 100644 --- a/Cake.Issues.Recipe/Content/data/IssuesData.cake +++ b/Cake.Issues.Recipe/Content/data/IssuesData.cake @@ -40,6 +40,11 @@ public class IssuesData /// public IIssuesBuildServer BuildServer { get; } + /// + /// Gets the provider to read information about the Git repository. + /// + public IRepositoryInfoProvider RepositoryInfo { get; } + /// /// Gets the pull request system used for the code. /// Returns null if not running a pull request build or on an unsupported build server. @@ -61,13 +66,16 @@ public class IssuesData /// Creates a new instance of the class. /// /// The Cake context. - public IssuesData(ICakeContext context) + /// Defines how information about the Git repository should be determined. + public IssuesData(ICakeContext context, RepositoryInfoProviderType repositoryInfoProviderType) { context.NotNull(nameof(context)); this.BuildRootDirectory = context.MakeAbsolute(context.Directory("./")); context.Information("Build script root directory: {0}", this.BuildRootDirectory); + this.RepositoryInfo = DetermineRepositoryInfoProvider(context, repositoryInfoProviderType); + this.RepositoryRootDirectory = context.GitFindRootFromPath(this.BuildRootDirectory); context.Information("Repository root directory: {0}", this.RepositoryRootDirectory); @@ -111,6 +119,34 @@ public class IssuesData this.issues.AddRange(issues); } + /// + /// Determines the repository info provider to use. + /// + /// The Cake context. + /// Defines how information about the Git repository should be determined. + /// The repository info provider which should be used. + private static IRepositoryInfoProvider DetermineRepositoryInfoProvider( + ICakeContext context, + RepositoryInfoProviderType repositoryInfoProviderType) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + switch (repositoryInfoProviderType) + { + case RepositoryInfoProviderType.CakeGit: + context.Information("Using Cake.Git for providing repository information"); + return new CliRepositoryInfoProvider(); + case RepositoryInfoProviderType.Cli: + context.Information("Using Git CLI for providing repository information"); + return new CliRepositoryInfoProvider(); + default: + throw new NotImplementedException("Unsupported repository info provider"); + } + } + /// /// Determines the build server on which the build is running. /// diff --git a/Cake.Issues.Recipe/Content/data/RepositoryInfoProviderType.cake b/Cake.Issues.Recipe/Content/data/RepositoryInfoProviderType.cake new file mode 100644 index 00000000..ee0be2d9 --- /dev/null +++ b/Cake.Issues.Recipe/Content/data/RepositoryInfoProviderType.cake @@ -0,0 +1,16 @@ +/// +/// Supported ways to read repository information. +/// +public enum RepositoryInfoProviderType +{ + /// + /// Read repository information using Cake.Git addin. + /// Requires system to be compatible with Cake.Git addin. + /// + CakeGit, + /// + /// Read repository information using Git CLI. + /// Requires Git CLI to be available in path. + /// + Cli +} \ No newline at end of file diff --git a/Cake.Issues.Recipe/Content/data/data.cake b/Cake.Issues.Recipe/Content/data/data.cake index 3aa1a361..6adf7d5a 100644 --- a/Cake.Issues.Recipe/Content/data/data.cake +++ b/Cake.Issues.Recipe/Content/data/data.cake @@ -1 +1,2 @@ +#load repositoryinfoprovider/repositoryinfoprovider.cake #load IssuesData.cake \ No newline at end of file diff --git a/Cake.Issues.Recipe/Content/data/repositoryinfoprovider/CakeGitRepositoryInfoProvider.cake b/Cake.Issues.Recipe/Content/data/repositoryinfoprovider/CakeGitRepositoryInfoProvider.cake new file mode 100644 index 00000000..0b8a41a8 --- /dev/null +++ b/Cake.Issues.Recipe/Content/data/repositoryinfoprovider/CakeGitRepositoryInfoProvider.cake @@ -0,0 +1,30 @@ +/// +/// Provider to retrieve repository information using Cake.Git addin. +/// +public class CakeGitRepositoryInfoProvider : IRepositoryInfoProvider +{ + /// + public DirectoryPath GetRepositoryRootDirectory(ICakeContext context, DirectoryPath buildRootDirectory) + { + context.NotNull(nameof(context)); + buildRootDirectory.NotNull(nameof(buildRootDirectory)); + return context.GitFindRootFromPath(buildRootDirectory); + } + + /// + public Uri GetRepositoryRemoteUrl(ICakeContext context, DirectoryPath repositoryRootDirectory) + { + context.NotNull(nameof(context)); + repositoryRootDirectory.NotNull(nameof(repositoryRootDirectory)); + var currentBranch = context.GitBranchCurrent(repositoryRootDirectory); + return new Uri(currentBranch.Remotes.Single(x => x.Name == "origin").Url); + } + + /// + public string GetCommitId(ICakeContext context, DirectoryPath repositoryRootDirectory) + { + context.NotNull(nameof(context)); + repositoryRootDirectory.NotNull(nameof(repositoryRootDirectory)); + return context.GitLogTip(repositoryRootDirectory).Sha; + } +} \ No newline at end of file diff --git a/Cake.Issues.Recipe/Content/data/repositoryinfoprovider/CliRepositoryInfoProvider.cake b/Cake.Issues.Recipe/Content/data/repositoryinfoprovider/CliRepositoryInfoProvider.cake new file mode 100644 index 00000000..60357695 --- /dev/null +++ b/Cake.Issues.Recipe/Content/data/repositoryinfoprovider/CliRepositoryInfoProvider.cake @@ -0,0 +1,60 @@ +/// +/// Provider to retrieve repository information using Git CLI. +/// +public class CliRepositoryInfoProvider : IRepositoryInfoProvider +{ + /// + public DirectoryPath GetRepositoryRootDirectory(ICakeContext context, DirectoryPath buildRootDirectory) + { + var result = + this.GitCommand(context, buildRootDirectory, "rev-parse", "--show-toplevel"); + return new DirectoryPath(result.Single()); + } + + /// + public Uri GetRepositoryRemoteUrl(ICakeContext context, DirectoryPath repositoryRootDirectory) + { + var result = + this.GitCommand(context, repositoryRootDirectory, "config", "--get", "remote.origin.url"); + return new Uri(result.Single()); + } + + /// + public string GetCommitId(ICakeContext context, DirectoryPath repositoryRootDirectory) + { + return + this.GitCommand(context, repositoryRootDirectory, "rev-parse", "HEAD") + .Single(); + } + + private IEnumerable GitCommand( + ICakeContext context, + DirectoryPath repositoryRootFolder, + params string[] arguments) + { + if (!arguments.Any()) + { + throw new ArgumentOutOfRangeException(nameof(arguments)); + } + var gitArguments = string.Join(" ", arguments); + var exitCode = context.StartProcess( + "git", + new ProcessSettings + { + Arguments = gitArguments, + WorkingDirectory = repositoryRootFolder.FullPath, + RedirectStandardOutput = true, + RedirectStandardError = true + }, + out var redirectedStandardOutput, + out var redirectedErrorOutput + ); + if (exitCode != 0) + { + throw new Exception( + $"Git command failed with arguments {gitArguments}. Exit code: {exitCode}. Error output: {string.Join(Environment.NewLine, redirectedErrorOutput)}" + ); + } + return redirectedStandardOutput; + } +} \ No newline at end of file diff --git a/Cake.Issues.Recipe/Content/data/repositoryinfoprovider/IRepositoryInfoProvider.cake b/Cake.Issues.Recipe/Content/data/repositoryinfoprovider/IRepositoryInfoProvider.cake new file mode 100644 index 00000000..b39b9c10 --- /dev/null +++ b/Cake.Issues.Recipe/Content/data/repositoryinfoprovider/IRepositoryInfoProvider.cake @@ -0,0 +1,29 @@ +/// +/// Description of a provider to retrieve repository information. +/// +public interface IRepositoryInfoProvider +{ + /// + /// Returns the root directory of the current repository. + /// + /// The Cake context. + /// Root directory of the build script. + /// The root directory of the current repository. + DirectoryPath GetRepositoryRootDirectory(ICakeContext context, DirectoryPath buildRootDirectory); + + /// + /// Returns the URL of the remote repository. + /// + /// The Cake context. + /// Root directory of the repository. + /// The URL of the remote repository. + Uri GetRepositoryRemoteUrl(ICakeContext context, DirectoryPath repositoryRootDirectory); + + /// + /// Returns the SHA hash of the current commit. + /// + /// The Cake context. + /// Root directory of the repository. + /// The SHA hash of the current commit + string GetCommitId(ICakeContext context, DirectoryPath repositoryRootDirectory); +} \ 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 new file mode 100644 index 00000000..416f61a8 --- /dev/null +++ b/Cake.Issues.Recipe/Content/data/repositoryinfoprovider/repositoryinfoprovider.cake @@ -0,0 +1,3 @@ +#load IRepositoryInfoProvider.cake +#load CakeGitRepositoryInfoProvider.cake +#load CliRepositoryInfoProvider.cake \ No newline at end of file diff --git a/tests/frosting/net5.0/build/Program.cs b/tests/frosting/net5.0/build/Program.cs index cb7ecb4a..81c86bbc 100644 --- a/tests/frosting/net5.0/build/Program.cs +++ b/tests/frosting/net5.0/build/Program.cs @@ -40,7 +40,7 @@ public class BuildContext : IssuesContext public FilePath MarkdownlintCliLogFilePath { get; } public BuildContext(ICakeContext context) - : base(context) + : base(context, RepositoryInfoProviderType.Cli) { this.BuildArtifactsDirectory = new DirectoryPath("BuildArtifacts"); diff --git a/tests/script-runner/build.cake b/tests/script-runner/build.cake index fcdf811d..9c68f57b 100644 --- a/tests/script-runner/build.cake +++ b/tests/script-runner/build.cake @@ -12,6 +12,8 @@ var target = Argument("target", "Default"); +RepositoryInfoProvider = Argument("repositoryInfoProvider", RepositoryInfoProviderType.Cli); + ////////////////////////////////////////////////// // SETUP / TEARDOWN //////////////////////////////////////////////////