diff --git a/docs/input/docs/usage/cli/arguments.md b/docs/input/docs/usage/cli/arguments.md index 01d773c752..8b1b8ea5ea 100644 --- a/docs/input/docs/usage/cli/arguments.md +++ b/docs/input/docs/usage/cli/arguments.md @@ -30,7 +30,7 @@ GitVersion [path] /targetpath Same as 'path', but not positional /output Determines the output to the console. Can be either 'json', - 'file' or 'buildserver', will default to 'json'. + 'file', 'buildserver' or 'dotenv', will default to 'json'. /outputfile Path to output file. It is used in combination with /output 'file'. /showvariable Used in conjunction with /output json, will output just a diff --git a/docs/input/docs/usage/cli/output.md b/docs/input/docs/usage/cli/output.md index 52a03daba1..e8cdaf051d 100644 --- a/docs/input/docs/usage/cli/output.md +++ b/docs/input/docs/usage/cli/output.md @@ -14,3 +14,24 @@ out the variables to whatever build server it is running in. You can then use those variables in your build scripts or run different tools to create versioned NuGet packages or whatever you would like to do. See [build servers](/docs/reference/build-servers) for more information about this. + +You can even store the [variables](/docs/reference/variables) in a Dotenv file +and load it to have the variables available in your environment. +For that you have to run `GitVersion.exe /output dotenv` and store the output +into e.g. a `gitversion.env` file. These files can also be passed around in CI environments +like [GitHub](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables#passing-values-between-steps-and-jobs-in-a-workflow) +or [GitLab](https://docs.gitlab.com/ee/ci/variables/#pass-an-environment-variable-to-another-job). +Below are some examples of using the Dotenv format in the Unix command line: +```bash +# Output version variables in Dotenv format +gitversion /output dotenv + +# Show only a subset of the version variables in Dotenv format +gitversion /output dotenv | grep -i "prerelease" + +# Show only a subset of the version variables that match the regex in Dotenv format +gitversion /output dotenv | grep -iE "major|sha=|_prerelease" + +# Write version variables in Dotenv format into a file +gitversion /output dotenv > gitversion.env +``` diff --git a/src/GitVersion.App.Tests/ArgumentParserTests.cs b/src/GitVersion.App.Tests/ArgumentParserTests.cs index 9120204927..abdcd6c2a5 100644 --- a/src/GitVersion.App.Tests/ArgumentParserTests.cs +++ b/src/GitVersion.App.Tests/ArgumentParserTests.cs @@ -103,7 +103,7 @@ public void UnknownOutputShouldThrow() { var exception = Assert.Throws(() => this.argumentParser.ParseArguments("targetDirectoryPath -output invalid_value")); exception.ShouldNotBeNull(); - exception.Message.ShouldBe("Value 'invalid_value' cannot be parsed as output type, please use 'json', 'file' or 'buildserver'"); + exception.Message.ShouldBe("Value 'invalid_value' cannot be parsed as output type, please use 'json', 'file', 'buildserver' or 'dotenv'"); } [Test] diff --git a/src/GitVersion.App/ArgumentParser.cs b/src/GitVersion.App/ArgumentParser.cs index 7686b77231..bccbc96d36 100644 --- a/src/GitVersion.App/ArgumentParser.cs +++ b/src/GitVersion.App/ArgumentParser.cs @@ -432,7 +432,7 @@ private static void ParseOutput(Arguments arguments, IEnumerable? values { if (!Enum.TryParse(v, true, out OutputType outputType)) { - throw new WarningException($"Value '{v}' cannot be parsed as output type, please use 'json', 'file' or 'buildserver'"); + throw new WarningException($"Value '{v}' cannot be parsed as output type, please use 'json', 'file', 'buildserver' or 'dotenv'"); } arguments.Output.Add(outputType); diff --git a/src/GitVersion.Core/Options/OutputType.cs b/src/GitVersion.Core/Options/OutputType.cs index bb72800fc7..3ad1b04d94 100644 --- a/src/GitVersion.Core/Options/OutputType.cs +++ b/src/GitVersion.Core/Options/OutputType.cs @@ -4,5 +4,6 @@ public enum OutputType { BuildServer, Json, - File + File, + DotEnv } diff --git a/src/GitVersion.Core/PublicAPI.Shipped.txt b/src/GitVersion.Core/PublicAPI.Shipped.txt index 646e2c5952..8feb99e5cc 100644 --- a/src/GitVersion.Core/PublicAPI.Shipped.txt +++ b/src/GitVersion.Core/PublicAPI.Shipped.txt @@ -417,6 +417,7 @@ GitVersion.OutputType GitVersion.OutputType.BuildServer = 0 -> GitVersion.OutputType GitVersion.OutputType.File = 2 -> GitVersion.OutputType GitVersion.OutputType.Json = 1 -> GitVersion.OutputType +GitVersion.OutputType.DotEnv = 3 -> GitVersion.OutputType GitVersion.OutputVariables.GitVersionVariables GitVersion.OutputVariables.GitVersionVariables.AssemblySemFileVer.get -> string? GitVersion.OutputVariables.GitVersionVariables.AssemblySemFileVer.init -> void diff --git a/src/GitVersion.Output.Tests/Output/FormatArgumentTests.cs b/src/GitVersion.Output.Tests/Output/FormatArgumentTests.cs index d116855c6f..3bcec4b95c 100644 --- a/src/GitVersion.Output.Tests/Output/FormatArgumentTests.cs +++ b/src/GitVersion.Output.Tests/Output/FormatArgumentTests.cs @@ -68,6 +68,126 @@ public void ShouldOutputFormatWithEnvironmentVariablesTests(string format, strin output.ShouldBeEquivalentTo(expectedValue); } + [TestCase("Major", "'1'")] + [TestCase("MajorMinorPatch", "'1.1.0'")] + [TestCase("SemVer", "'1.1.0-foo.1'")] + [TestCase("PreReleaseTagWithDash", "'-foo.1'")] + [TestCase("AssemblySemFileVer", "'1.1.0.0'")] + [TestCase("BranchName", "'feature/foo'")] + [TestCase("FullSemVer", "'1.1.0-foo.1+1'")] + public void ShouldOutputDotEnvEntries(string variableName, 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 }, Output = { OutputType.DotEnv } }); + 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()); + var output = consoleBuilder.ToString(); + output.ShouldContain($"GitVersion_{variableName}={expectedValue}{SysEnv.NewLine}"); + } + + [TestCase] + public void ShouldOutputAllCalculatedVariablesAsDotEnvEntries() + { + 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 }, Output = { OutputType.DotEnv } }); + 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()); + var output = consoleBuilder.ToString(); + var totalOutputLines = output.Split(SysEnv.NewLine).Length - 1; // ignore last item that also ends with the newline string + Assert.That(totalOutputLines, Is.EqualTo(versionVariables.Count())); + } + + [TestCase("Major", "'0'")] + [TestCase("MajorMinorPatch", "'0.0.1'")] + [TestCase("SemVer", "'0.0.1-1'")] + [TestCase("BuildMetaData", "''")] + [TestCase("AssemblySemVer", "'0.0.1.0'")] + [TestCase("PreReleaseTagWithDash", "'-1'")] + [TestCase("BranchName", "'main'")] + [TestCase("PreReleaseLabel", "''")] + [TestCase("PreReleaseLabelWithDash", "''")] + public void ShouldOutputAllDotEnvEntriesEvenForMinimalRepositories(string variableName, string expectedValue) + { + var fixture = CreateMinimalTestRepository(); + + 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 }, Output = { OutputType.DotEnv } }); + 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()); + var output = consoleBuilder.ToString(); + output.ShouldContain($"GitVersion_{variableName}={expectedValue}{SysEnv.NewLine}"); + } + + [TestCase] + public void ShouldOutputAllCalculatedVariablesAsDotEnvEntriesEvenForMinimalRepositories() + { + var fixture = CreateMinimalTestRepository(); + + 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 }, Output = { OutputType.DotEnv } }); + 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()); + var output = consoleBuilder.ToString(); + var totalOutputLines = output.Split(SysEnv.NewLine).Length - 1; // ignore last item that also ends with the newline string + Assert.That(totalOutputLines, Is.EqualTo(versionVariables.Count())); + } + private static EmptyRepositoryFixture CreateTestRepository() { var fixture = new EmptyRepositoryFixture(); @@ -80,4 +200,11 @@ private static EmptyRepositoryFixture CreateTestRepository() _ = fixture.Repository.MakeACommit(); return fixture; } + + private static EmptyRepositoryFixture CreateMinimalTestRepository() + { + var fixture = new EmptyRepositoryFixture(); + _ = fixture.Repository.MakeACommit(); + return fixture; + } } diff --git a/src/GitVersion.Output/OutputGenerator/OutputGenerator.cs b/src/GitVersion.Output/OutputGenerator/OutputGenerator.cs index 1da7fe356e..8e53f306f8 100644 --- a/src/GitVersion.Output/OutputGenerator/OutputGenerator.cs +++ b/src/GitVersion.Output/OutputGenerator/OutputGenerator.cs @@ -28,11 +28,34 @@ internal sealed class OutputGenerator( public void Execute(GitVersionVariables variables, OutputContext context) { var gitVersionOptions = this.options.Value; + if (gitVersionOptions.Output.Contains(OutputType.BuildServer)) { this.buildAgent.WriteIntegration(this.console.WriteLine, variables, context.UpdateBuildNumber ?? true); } + if (gitVersionOptions.Output.Contains(OutputType.DotEnv)) + { + List dotEnvEntries = []; + foreach (var (key, value) in variables.OrderBy(x => x.Key)) + { + string prefixedKey = "GitVersion_" + key; + string environmentValue = ""; + if (!value.IsNullOrEmpty()) + { + environmentValue = value; + } + dotEnvEntries.Add($"{prefixedKey}='{environmentValue}'"); + } + + foreach (var dotEnvEntry in dotEnvEntries) + { + this.console.WriteLine(dotEnvEntry); + } + + return; + } + var json = this.serializer.ToJson(variables); if (gitVersionOptions.Output.Contains(OutputType.File)) {