diff --git a/docs/input/docs/usage/cli/arguments.md b/docs/input/docs/usage/cli/arguments.md index 1fb995114e..93cdd7445e 100644 --- a/docs/input/docs/usage/cli/arguments.md +++ b/docs/input/docs/usage/cli/arguments.md @@ -37,6 +37,10 @@ GitVersion [path] /showvariable Used in conjuntion with /output json, will output just a particular variable. E.g. /output json /showvariable SemVer - will output `1.2.3+beta.4` + /format Used in conjuntion with /output json, will output a format + containing version variables. + E.g. /output json /format {SemVer} - will output `1.2.3+beta.4` + /output json /format {Major}.{Minor} - will output `1.2` /l Path to logfile. /config Path to config file (defaults to GitVersion.yml) /showconfig Outputs the effective GitVersion config (defaults + custom diff --git a/src/GitVersion.App.Tests/ArgumentParserTests.cs b/src/GitVersion.App.Tests/ArgumentParserTests.cs index 7b16cd7a83..7f31c81f21 100644 --- a/src/GitVersion.App.Tests/ArgumentParserTests.cs +++ b/src/GitVersion.App.Tests/ArgumentParserTests.cs @@ -726,4 +726,18 @@ public void ArbitraryArgumentsRemotePasswordDefinedSetsPassword() var arguments = this.argumentParser.ParseArguments("-nocache"); arguments.Authentication.Password.ShouldBe("value"); } + + [Test] + public void EnsureShowVariableIsSet() + { + var arguments = this.argumentParser.ParseArguments("-showvariable SemVer"); + arguments.ShowVariable.ShouldBe("SemVer"); + } + + [Test] + public void EnsureFormatIsSet() + { + var arguments = this.argumentParser.ParseArguments("-format {Major}.{Minor}.{Patch}"); + arguments.Format.ShouldBe("{Major}.{Minor}.{Patch}"); + } } diff --git a/src/GitVersion.App.Tests/GitVersion.App.Tests.csproj b/src/GitVersion.App.Tests/GitVersion.App.Tests.csproj index bab87578b7..f1a123a254 100644 --- a/src/GitVersion.App.Tests/GitVersion.App.Tests.csproj +++ b/src/GitVersion.App.Tests/GitVersion.App.Tests.csproj @@ -16,6 +16,9 @@ + + Helpers\TestConsoleAdapter.cs + diff --git a/src/GitVersion.App.Tests/HelpWriterTests.cs b/src/GitVersion.App.Tests/HelpWriterTests.cs index 858cbe1859..df75c5510a 100644 --- a/src/GitVersion.App.Tests/HelpWriterTests.cs +++ b/src/GitVersion.App.Tests/HelpWriterTests.cs @@ -32,7 +32,9 @@ public void AllArgsAreInHelp() { nameof(Arguments.Verbosity), "/verbosity" }, { nameof(Arguments.CommitId), "/c" }, { nameof(Arguments.ShowConfiguration), "/showconfig" }, - { nameof(Arguments.OverrideConfiguration), "/overrideconfig" } + { nameof(Arguments.OverrideConfiguration), "/overrideconfig" }, + { nameof(Arguments.ShowVariable), "/showvariable" }, + { nameof(Arguments.Format), "/format" } }; string helpText = string.Empty; diff --git a/src/GitVersion.App/ArgumentParser.cs b/src/GitVersion.App/ArgumentParser.cs index 530f18c288..60c0e79f1f 100644 --- a/src/GitVersion.App/ArgumentParser.cs +++ b/src/GitVersion.App/ArgumentParser.cs @@ -236,6 +236,12 @@ private static bool ParseSwitches(Arguments arguments, string? name, IReadOnlyLi return true; } + if (name.IsSwitch("format")) + { + ParseFormat(arguments, value); + return true; + } + if (name.IsSwitch("output")) { ParseOutput(arguments, values); @@ -371,6 +377,23 @@ private static void ParseShowVariable(Arguments arguments, string? value, string arguments.ShowVariable = versionVariable; } + private static void ParseFormat(Arguments arguments, string? value) + { + if (value.IsNullOrWhiteSpace()) + { + throw new WarningException("Format requires a valid format string. Available variables are: " + string.Join(", ", VersionVariables.AvailableVariables)); + } + + var foundVariable = VersionVariables.AvailableVariables.Any(variable => value.Contains(variable, StringComparison.CurrentCultureIgnoreCase)); + + if (!foundVariable) + { + throw new WarningException("Format requires a valid format string. Available variables are: " + string.Join(", ", VersionVariables.AvailableVariables)); + } + + arguments.Format = value; + } + private static void ParseEnsureAssemblyInfo(Arguments arguments, string? value) { arguments.EnsureAssemblyInfo = true; diff --git a/src/GitVersion.App/Arguments.cs b/src/GitVersion.App/Arguments.cs index 34b87eb791..d5c6c845e0 100644 --- a/src/GitVersion.App/Arguments.cs +++ b/src/GitVersion.App/Arguments.cs @@ -30,6 +30,7 @@ public class Arguments public string? LogFilePath; public string? ShowVariable; + public string? Format; public string? OutputFile; public ISet Output = new HashSet(); public Verbosity Verbosity = Verbosity.Normal; @@ -92,6 +93,7 @@ public GitVersionOptions ToOptions() LogFilePath = LogFilePath, ShowVariable = ShowVariable, + Format = Format, Verbosity = Verbosity, Output = Output, OutputFile = OutputFile diff --git a/src/GitVersion.App/PublicAPI.Unshipped.txt b/src/GitVersion.App/PublicAPI.Unshipped.txt index c77bdd66a0..db97d37558 100644 --- a/src/GitVersion.App/PublicAPI.Unshipped.txt +++ b/src/GitVersion.App/PublicAPI.Unshipped.txt @@ -11,6 +11,7 @@ GitVersion.Arguments.CommitId -> string? GitVersion.Arguments.ConfigurationFile -> string? GitVersion.Arguments.Diag -> bool GitVersion.Arguments.EnsureAssemblyInfo -> bool +GitVersion.Arguments.Format -> string? GitVersion.Arguments.Init -> bool GitVersion.Arguments.IsHelp -> bool GitVersion.Arguments.IsVersion -> bool diff --git a/src/GitVersion.BuildAgents.Tests/AssemblyParallelizable.cs b/src/GitVersion.BuildAgents.Tests/AssemblyParallelizable.cs new file mode 100644 index 0000000000..fdd365b7e2 --- /dev/null +++ b/src/GitVersion.BuildAgents.Tests/AssemblyParallelizable.cs @@ -0,0 +1 @@ +[assembly: Parallelizable(ParallelScope.Fixtures)] diff --git a/src/GitVersion.Core.Tests/GitVersion.Core.Tests.csproj b/src/GitVersion.Core.Tests/GitVersion.Core.Tests.csproj index 0946d95e37..430622a90d 100644 --- a/src/GitVersion.Core.Tests/GitVersion.Core.Tests.csproj +++ b/src/GitVersion.Core.Tests/GitVersion.Core.Tests.csproj @@ -24,5 +24,6 @@ + diff --git a/src/GitVersion.App.Tests/Helpers/TestConsoleAdapter.cs b/src/GitVersion.Core.Tests/Helpers/TestConsoleAdapter.cs similarity index 93% rename from src/GitVersion.App.Tests/Helpers/TestConsoleAdapter.cs rename to src/GitVersion.Core.Tests/Helpers/TestConsoleAdapter.cs index b5ce351058..557d321a78 100644 --- a/src/GitVersion.App.Tests/Helpers/TestConsoleAdapter.cs +++ b/src/GitVersion.Core.Tests/Helpers/TestConsoleAdapter.cs @@ -1,6 +1,6 @@ using GitVersion.Logging; -namespace GitVersion.App.Tests; +namespace GitVersion.Core.Tests.Helpers; public class TestConsoleAdapter : IConsole { diff --git a/src/GitVersion.Core/GitVersion.Core.csproj b/src/GitVersion.Core/GitVersion.Core.csproj index 2cbdcc09bc..3b4875ef34 100644 --- a/src/GitVersion.Core/GitVersion.Core.csproj +++ b/src/GitVersion.Core/GitVersion.Core.csproj @@ -22,6 +22,7 @@ + diff --git a/src/GitVersion.Core/Options/GitVersionOptions.cs b/src/GitVersion.Core/Options/GitVersionOptions.cs index e0a364ff67..42feb102a7 100644 --- a/src/GitVersion.Core/Options/GitVersionOptions.cs +++ b/src/GitVersion.Core/Options/GitVersionOptions.cs @@ -20,6 +20,7 @@ public class GitVersionOptions public string? LogFilePath; public string? ShowVariable; + public string? Format; public string? OutputFile; public ISet Output = new HashSet(); public Verbosity Verbosity = Verbosity.Normal; diff --git a/src/GitVersion.Core/PublicAPI.Unshipped.txt b/src/GitVersion.Core/PublicAPI.Unshipped.txt index 8ecc79065b..5b4c010acc 100644 --- a/src/GitVersion.Core/PublicAPI.Unshipped.txt +++ b/src/GitVersion.Core/PublicAPI.Unshipped.txt @@ -386,6 +386,7 @@ GitVersion.GitVersionOptions.AssemblySettingsInfo.get -> GitVersion.AssemblySett GitVersion.GitVersionOptions.AuthenticationInfo.get -> GitVersion.AuthenticationInfo! GitVersion.GitVersionOptions.ConfigurationInfo.get -> GitVersion.ConfigurationInfo! GitVersion.GitVersionOptions.Diag -> bool +GitVersion.GitVersionOptions.Format -> string? GitVersion.GitVersionOptions.GitVersionOptions() -> void GitVersion.GitVersionOptions.Init -> bool GitVersion.GitVersionOptions.IsHelp -> bool diff --git a/src/GitVersion.Output.Tests/AssemblyParallelizable.cs b/src/GitVersion.Output.Tests/AssemblyParallelizable.cs new file mode 100644 index 0000000000..fdd365b7e2 --- /dev/null +++ b/src/GitVersion.Output.Tests/AssemblyParallelizable.cs @@ -0,0 +1 @@ +[assembly: Parallelizable(ParallelScope.Fixtures)] diff --git a/src/GitVersion.Output.Tests/Output/FormatArgumentTests.cs b/src/GitVersion.Output.Tests/Output/FormatArgumentTests.cs new file mode 100644 index 0000000000..46a65eb8f2 --- /dev/null +++ b/src/GitVersion.Output.Tests/Output/FormatArgumentTests.cs @@ -0,0 +1,95 @@ +using GitVersion.Core.Tests.Helpers; +using GitVersion.Logging; +using GitVersion.Output.OutputGenerator; +using LibGit2Sharp; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace GitVersion.Core.Tests; + +[TestFixture] +public class FormatArgumentTests : TestBase +{ + [TestCase("{SemVer}", "1.1.0-foo.1")] + [TestCase("{Major}.{Minor}", "1.1")] + [TestCase("{Major}.{Minor}.{Patch}", "1.1.0")] + [TestCase("{Major}.{Minor}.{Patch}.{PreReleaseTag}", "1.1.0.foo.1")] + public void ShouldOutputFormatTests(string format, string expectedValue) + { + var fixture = CreateTestRepository(); + + var consoleBuilder = new StringBuilder(); + IConsole consoleAdapter = new TestConsoleAdapter(consoleBuilder); + + var sp = ConfigureServices(services => + { + var options = Options.Create(new GitVersionOptions + { + WorkingDirectory = fixture.RepositoryPath, + RepositoryInfo = { TargetBranch = fixture.Repository.Head.CanonicalName }, + Format = format, + Output = { OutputType.Json } + }); + var repository = fixture.Repository.ToGitRepository(); + + services.AddSingleton(options); + services.AddSingleton(repository); + services.AddSingleton(consoleAdapter); + }); + + var versionVariables = sp.GetRequiredService().CalculateVersionVariables(); + var outputGenerator = sp.GetRequiredService(); + + outputGenerator.Execute(versionVariables, new OutputContext()); + var output = consoleBuilder.ToString().Replace("\n", "").Replace("\r", ""); + output.ShouldBeEquivalentTo(expectedValue); + } + + [TestCase("{Major}.{Minor}.{env:CustomVar}", "1.1.foo")] + [TestCase("{Major}.{Minor}.{Patch}.{env:CustomVar}", "1.1.0.foo")] + public void ShouldOutputFormatWithEnvironmentVariablesTests(string format, string expectedValue) + { + var fixture = CreateTestRepository(); + var consoleBuilder = new StringBuilder(); + IConsole console = new TestConsoleAdapter(consoleBuilder); + IEnvironment environment = new TestEnvironment(); + environment.SetEnvironmentVariable("CustomVar", "foo"); + + var sp = ConfigureServices(services => + { + var options = Options.Create(new GitVersionOptions + { + WorkingDirectory = fixture.RepositoryPath, + RepositoryInfo = { TargetBranch = fixture.Repository.Head.CanonicalName }, + Format = format, + Output = { OutputType.Json } + }); + var repository = fixture.Repository.ToGitRepository(); + + services.AddSingleton(options); + services.AddSingleton(repository); + services.AddSingleton(console); + services.AddSingleton(environment); + }); + + var versionVariables = sp.GetRequiredService().CalculateVersionVariables(); + var outputGenerator = sp.GetRequiredService(); + + outputGenerator.Execute(versionVariables, new OutputContext()); + var output = consoleBuilder.ToString().Replace("\n", "").Replace("\r", ""); + output.ShouldBeEquivalentTo(expectedValue); + } + + private static EmptyRepositoryFixture CreateTestRepository() + { + var fixture = new EmptyRepositoryFixture(); + _ = fixture.Repository.MakeACommit(); + Commands.Checkout(fixture.Repository, fixture.Repository.CreateBranch("develop")); + var secondCommit = fixture.Repository.MakeACommit(); + _ = fixture.Repository.Tags.Add("1.0.0", secondCommit); + var featureBranch = fixture.Repository.CreateBranch("feature/foo"); + Commands.Checkout(fixture.Repository, featureBranch); + _ = fixture.Repository.MakeACommit(); + return fixture; + } +} diff --git a/src/GitVersion.Output/OutputGenerator/OutputGenerator.cs b/src/GitVersion.Output/OutputGenerator/OutputGenerator.cs index d324fd921e..9322ab1c5d 100644 --- a/src/GitVersion.Output/OutputGenerator/OutputGenerator.cs +++ b/src/GitVersion.Output/OutputGenerator/OutputGenerator.cs @@ -15,13 +15,15 @@ public sealed class OutputGenerator : IOutputGenerator { private readonly IConsole console; private readonly IFileSystem fileSystem; + private readonly IEnvironment environment; private readonly IOptions options; private readonly ICurrentBuildAgent buildAgent; - public OutputGenerator(ICurrentBuildAgent buildAgent, IConsole console, IFileSystem fileSystem, IOptions options) + public OutputGenerator(ICurrentBuildAgent buildAgent, IConsole console, IFileSystem fileSystem, IEnvironment environment, IOptions options) { this.console = console.NotNull(); this.fileSystem = fileSystem.NotNull(); + this.environment = environment; this.options = options.NotNull(); this.buildAgent = buildAgent.NotNull(); } @@ -41,6 +43,34 @@ public void Execute(VersionVariables variables, OutputContext context) if (!gitVersionOptions.Output.Contains(OutputType.Json)) return; + if (gitVersionOptions.ShowVariable is null && gitVersionOptions.Format is null) + { + this.console.WriteLine(variables.ToString()); + return; + } + + if (gitVersionOptions.ShowVariable is not null && gitVersionOptions.Format is not null) + { + throw new WarningException("Cannot specify both /showvariable and /format"); + } + if (gitVersionOptions.ShowVariable is not null) + { + if (!variables.TryGetValue(gitVersionOptions.ShowVariable, out var part)) + { + throw new WarningException($"'{gitVersionOptions.ShowVariable}' variable does not exist"); + } + + this.console.WriteLine(part); + return; + } + if (gitVersionOptions.Format is not null) + { + var format = gitVersionOptions.Format; + var formatted = format.FormatWith(variables, environment); + this.console.WriteLine(formatted); + return; + } + switch (gitVersionOptions.ShowVariable) { case null: diff --git a/src/GitVersion.Output/PublicAPI.Unshipped.txt b/src/GitVersion.Output/PublicAPI.Unshipped.txt index c568f0b0fb..7f53efbd7b 100644 --- a/src/GitVersion.Output/PublicAPI.Unshipped.txt +++ b/src/GitVersion.Output/PublicAPI.Unshipped.txt @@ -47,7 +47,7 @@ GitVersion.Output.OutputGenerator.OutputContext.WorkingDirectory.get -> string! GitVersion.Output.OutputGenerator.OutputGenerator GitVersion.Output.OutputGenerator.OutputGenerator.Dispose() -> void GitVersion.Output.OutputGenerator.OutputGenerator.Execute(GitVersion.OutputVariables.VersionVariables! variables, GitVersion.Output.OutputGenerator.OutputContext context) -> void -GitVersion.Output.OutputGenerator.OutputGenerator.OutputGenerator(GitVersion.Agents.ICurrentBuildAgent! buildAgent, GitVersion.Logging.IConsole! console, GitVersion.IFileSystem! fileSystem, Microsoft.Extensions.Options.IOptions! options) -> void +GitVersion.Output.OutputGenerator.OutputGenerator.OutputGenerator(GitVersion.Agents.ICurrentBuildAgent! buildAgent, GitVersion.Logging.IConsole! console, GitVersion.IFileSystem! fileSystem, GitVersion.IEnvironment! environment, Microsoft.Extensions.Options.IOptions! options) -> void GitVersion.Output.WixUpdater.IWixVersionFileUpdater GitVersion.Output.WixUpdater.WixVersionContext GitVersion.Output.WixUpdater.WixVersionContext.WixVersionContext() -> void