Skip to content

Support for Dotenv output format #4413

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/input/docs/usage/cli/arguments.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 21 additions & 0 deletions docs/input/docs/usage/cli/output.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
2 changes: 1 addition & 1 deletion src/GitVersion.App.Tests/ArgumentParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public void UnknownOutputShouldThrow()
{
var exception = Assert.Throws<WarningException>(() => 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]
Expand Down
2 changes: 1 addition & 1 deletion src/GitVersion.App/ArgumentParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ private static void ParseOutput(Arguments arguments, IEnumerable<string>? 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);
Expand Down
3 changes: 2 additions & 1 deletion src/GitVersion.Core/Options/OutputType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ public enum OutputType
{
BuildServer,
Json,
File
File,
DotEnv
}
1 change: 1 addition & 0 deletions src/GitVersion.Core/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
127 changes: 127 additions & 0 deletions src/GitVersion.Output.Tests/Output/FormatArgumentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<IGitVersionCalculateTool>().CalculateVersionVariables();
var outputGenerator = sp.GetRequiredService<IOutputGenerator>();

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<IGitVersionCalculateTool>().CalculateVersionVariables();
var outputGenerator = sp.GetRequiredService<IOutputGenerator>();

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<IGitVersionCalculateTool>().CalculateVersionVariables();
var outputGenerator = sp.GetRequiredService<IOutputGenerator>();

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<IGitVersionCalculateTool>().CalculateVersionVariables();
var outputGenerator = sp.GetRequiredService<IOutputGenerator>();

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();
Expand All @@ -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;
}
}
23 changes: 23 additions & 0 deletions src/GitVersion.Output/OutputGenerator/OutputGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> 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))
{
Expand Down