From bafb3a6a57dde10279f24df865416114bc84cde5 Mon Sep 17 00:00:00 2001 From: Bi0T1N Date: Sat, 8 Feb 2025 14:18:06 +0100 Subject: [PATCH 1/5] Support for dotenv output format closes #4174 --- docs/input/docs/usage/cli/arguments.md | 2 +- docs/input/docs/usage/cli/output.md | 7 + new-cli/command.md | 12 ++ .../ArgumentParserTests.cs | 2 +- src/GitVersion.App/ArgumentParser.cs | 2 +- src/GitVersion.Core/Options/OutputType.cs | 3 +- src/GitVersion.Core/PublicAPI.Shipped.txt | 1 + .../Output/FormatArgumentTests.cs | 127 ++++++++++++++++++ .../OutputGenerator/OutputGenerator.cs | 23 ++++ 9 files changed, 175 insertions(+), 4 deletions(-) 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..d26bf5a84d 100644 --- a/docs/input/docs/usage/cli/output.md +++ b/docs/input/docs/usage/cli/output.md @@ -14,3 +14,10 @@ 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). diff --git a/new-cli/command.md b/new-cli/command.md index c137381b07..7e6d2aec3b 100644 --- a/new-cli/command.md +++ b/new-cli/command.md @@ -53,6 +53,18 @@ cat gitversion.json | gitversion output buildserver # Read version variables from stdin and write to Jenkins. cat gitversion.json | gitversion output buildserver --buildserver Jenkins +# Output version variables in Dotenv format +gitversion /output dotenv + +# Show only a subset of the version variables in Dotenv format (Unix syntax) +gitversion /output dotenv | grep -i "prerelease" + +# Show only a subset of the version variables that match the regex in Dotenv format (Unix syntax) +gitversion /output dotenv | grep -iE "major|sha=|_prerelease" + +# Write version variables in Dotenv format into a file +gitversion /output dotenv > gitversion.env + # Read version variables from stdin and write to globbed .wxi files. cat gitversion.json | gitversion output wix --path ./**/*.wxi 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..f85ac29366 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 + "\n"); + } + + [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("\n").Length - 1; // ignore last item that also ends with \n + 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 + "\n"); + } + + [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("\n").Length - 1; // ignore last item that also ends with \n + 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..71eeedcbd0 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)) { From 482a8bcd7e801932d685c8ecdbf2db3de5dd2c5c Mon Sep 17 00:00:00 2001 From: Bi0T1N Date: Wed, 12 Feb 2025 16:47:55 +0100 Subject: [PATCH 2/5] Move usage examples into docs section --- docs/input/docs/usage/cli/output.md | 14 ++++++++++++++ new-cli/command.md | 12 ------------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/docs/input/docs/usage/cli/output.md b/docs/input/docs/usage/cli/output.md index d26bf5a84d..e8cdaf051d 100644 --- a/docs/input/docs/usage/cli/output.md +++ b/docs/input/docs/usage/cli/output.md @@ -21,3 +21,17 @@ 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/new-cli/command.md b/new-cli/command.md index 7e6d2aec3b..c137381b07 100644 --- a/new-cli/command.md +++ b/new-cli/command.md @@ -53,18 +53,6 @@ cat gitversion.json | gitversion output buildserver # Read version variables from stdin and write to Jenkins. cat gitversion.json | gitversion output buildserver --buildserver Jenkins -# Output version variables in Dotenv format -gitversion /output dotenv - -# Show only a subset of the version variables in Dotenv format (Unix syntax) -gitversion /output dotenv | grep -i "prerelease" - -# Show only a subset of the version variables that match the regex in Dotenv format (Unix syntax) -gitversion /output dotenv | grep -iE "major|sha=|_prerelease" - -# Write version variables in Dotenv format into a file -gitversion /output dotenv > gitversion.env - # Read version variables from stdin and write to globbed .wxi files. cat gitversion.json | gitversion output wix --path ./**/*.wxi From 7d301093fb4aad4e27cd3965b027b19a2c0fdc75 Mon Sep 17 00:00:00 2001 From: Bi0T1N Date: Thu, 13 Feb 2025 17:09:04 +0100 Subject: [PATCH 3/5] Make tests cross-platform compatible --- src/GitVersion.Output.Tests/Output/FormatArgumentTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/GitVersion.Output.Tests/Output/FormatArgumentTests.cs b/src/GitVersion.Output.Tests/Output/FormatArgumentTests.cs index f85ac29366..b034ff8e1e 100644 --- a/src/GitVersion.Output.Tests/Output/FormatArgumentTests.cs +++ b/src/GitVersion.Output.Tests/Output/FormatArgumentTests.cs @@ -97,7 +97,7 @@ public void ShouldOutputDotEnvEntries(string variableName, string expectedValue) outputGenerator.Execute(versionVariables, new()); var output = consoleBuilder.ToString(); - output.ShouldContain("GitVersion_" + variableName + "=" + expectedValue + "\n"); + output.ShouldContain($"GitVersion_{variableName}={expectedValue}{SysEnv.NewLine}"); } [TestCase] @@ -123,7 +123,7 @@ public void ShouldOutputAllCalculatedVariablesAsDotEnvEntries() outputGenerator.Execute(versionVariables, new()); var output = consoleBuilder.ToString(); - var totalOutputLines = output.Split("\n").Length - 1; // ignore last item that also ends with \n + 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())); } @@ -158,7 +158,7 @@ public void ShouldOutputAllDotEnvEntriesEvenForMinimalRepositories(string variab outputGenerator.Execute(versionVariables, new()); var output = consoleBuilder.ToString(); - output.ShouldContain("GitVersion_" + variableName + "=" + expectedValue + "\n"); + output.ShouldContain($"GitVersion_{variableName}={expectedValue}{SysEnv.NewLine}"); } [TestCase] @@ -184,7 +184,7 @@ public void ShouldOutputAllCalculatedVariablesAsDotEnvEntriesEvenForMinimalRepos outputGenerator.Execute(versionVariables, new()); var output = consoleBuilder.ToString(); - var totalOutputLines = output.Split("\n").Length - 1; // ignore last item that also ends with \n + 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())); } From 7eabdfd199f12dec29006c0f2f0ae1f16ca237a5 Mon Sep 17 00:00:00 2001 From: Bi0T1N Date: Wed, 26 Feb 2025 22:13:28 +0100 Subject: [PATCH 4/5] Add missing whitespace after foreach --- src/GitVersion.Output/OutputGenerator/OutputGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GitVersion.Output/OutputGenerator/OutputGenerator.cs b/src/GitVersion.Output/OutputGenerator/OutputGenerator.cs index 71eeedcbd0..ea9a1b06fe 100644 --- a/src/GitVersion.Output/OutputGenerator/OutputGenerator.cs +++ b/src/GitVersion.Output/OutputGenerator/OutputGenerator.cs @@ -48,7 +48,7 @@ public void Execute(GitVersionVariables variables, OutputContext context) dotEnvEntries.Add(prefixedKey + "=" + environmentValue); } - foreach(var dotEnvEntry in dotEnvEntries) + foreach (var dotEnvEntry in dotEnvEntries) { this.console.WriteLine(dotEnvEntry); } From 2602d51f55cd5b7e627b3a3723d88cd69c5e1fed Mon Sep 17 00:00:00 2001 From: Bi0T1N Date: Fri, 7 Mar 2025 20:30:57 +0100 Subject: [PATCH 5/5] Encapsulate all values into single quotes will be more future proof as it might not break on values that contain whitespaces etc --- .../Output/FormatArgumentTests.cs | 26 +++++++++---------- .../OutputGenerator/OutputGenerator.cs | 4 +-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/GitVersion.Output.Tests/Output/FormatArgumentTests.cs b/src/GitVersion.Output.Tests/Output/FormatArgumentTests.cs index b034ff8e1e..3bcec4b95c 100644 --- a/src/GitVersion.Output.Tests/Output/FormatArgumentTests.cs +++ b/src/GitVersion.Output.Tests/Output/FormatArgumentTests.cs @@ -68,13 +68,13 @@ 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")] + [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(); @@ -127,13 +127,13 @@ public void ShouldOutputAllCalculatedVariablesAsDotEnvEntries() Assert.That(totalOutputLines, Is.EqualTo(versionVariables.Count())); } - [TestCase("Major", "0")] - [TestCase("MajorMinorPatch", "0.0.1")] - [TestCase("SemVer", "0.0.1-1")] + [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("AssemblySemVer", "'0.0.1.0'")] + [TestCase("PreReleaseTagWithDash", "'-1'")] + [TestCase("BranchName", "'main'")] [TestCase("PreReleaseLabel", "''")] [TestCase("PreReleaseLabelWithDash", "''")] public void ShouldOutputAllDotEnvEntriesEvenForMinimalRepositories(string variableName, string expectedValue) diff --git a/src/GitVersion.Output/OutputGenerator/OutputGenerator.cs b/src/GitVersion.Output/OutputGenerator/OutputGenerator.cs index ea9a1b06fe..8e53f306f8 100644 --- a/src/GitVersion.Output/OutputGenerator/OutputGenerator.cs +++ b/src/GitVersion.Output/OutputGenerator/OutputGenerator.cs @@ -40,12 +40,12 @@ public void Execute(GitVersionVariables variables, OutputContext context) foreach (var (key, value) in variables.OrderBy(x => x.Key)) { string prefixedKey = "GitVersion_" + key; - string environmentValue = "''"; + string environmentValue = ""; if (!value.IsNullOrEmpty()) { environmentValue = value; } - dotEnvEntries.Add(prefixedKey + "=" + environmentValue); + dotEnvEntries.Add($"{prefixedKey}='{environmentValue}'"); } foreach (var dotEnvEntry in dotEnvEntries)