diff --git a/docs/input/docs/reference/build-servers/bitbucket-pipelines.md b/docs/input/docs/reference/build-servers/bitbucket-pipelines.md new file mode 100644 index 0000000000..610031ecbe --- /dev/null +++ b/docs/input/docs/reference/build-servers/bitbucket-pipelines.md @@ -0,0 +1,77 @@ +--- +Order: 35 +Title: BitBucket Pipelines +Description: Details on the Atlassian BitBucket Pipelines support in GitVersion +--- + +## Basic Usage + +To use GitVersion with Atlassian BitBucket Pipelines, you will need to install and run the GitVersion CLI tool +in your build step. + +## Executing GitVersion + +### Using the GitVersion CLI tool + +An example pipeline is shown below: + +```yml +image: mcr.microsoft.com/dotnet/sdk:6.0 + +clone: + depth: full + +pipelines: + default: + - step: + name: Version and build + script: + - export PATH="$PATH:/root/.dotnet/tools" + - dotnet tool install --global GitVersion.Tool --version 5.* + - dotnet-gitversion /buildserver + - source gitversion.properties + - echo Building with semver $GITVERSION_FULLSEMVER + - dotnet build +``` + +:::{.alert .alert-danger} +**Important** + +You must set the `clone:depth` setting as shown above; without it, BitBucket Pipelines will perform a shallow clone, which will +cause GitVersion will display an error message. +::: + +When the action `dotnet-gitversion /buildserver` is executed, it will detect that it is running in BitBucket Pipelines by the presence of +the `BITBUCKET_WORKSPACE` environment variable, which is set by the BitBucket Pipelines engine. It will generate a text file named `gitversion.properties` +which contains all the output of the GitVersion tool, exported as individual environment variables prefixed with `GITVERSION_`. +These environment variables can then be imported back into the build step using the `source gitversion.properties` action. + +If you want to share the text file across multiple build steps, then you will need to save it as an artifact. A more complex example pipeline +is shown below: + +```yml +image: mcr.microsoft.com/dotnet/sdk:6.0 + +clone: + depth: full + +pipelines: + default: + - step: + name: Version + script: + - export PATH="$PATH:/root/.dotnet/tools" + - dotnet tool install --global GitVersion.Tool --version 5.* + - dotnet-gitversion /buildserver + artifacts: + - gitversion.properties + - step: + name: Build + script: + - source gitversion.properties + - echo Building with semver $GITVERSION_FULLSEMVER + - dotnet build +``` + +[Variables and Secrets](https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/) +[Clone Options](https://bitbucket.org/blog/support-for-more-clone-options-at-the-step-level) diff --git a/src/GitVersion.App.Tests/PullRequestInBuildAgentTest.cs b/src/GitVersion.App.Tests/PullRequestInBuildAgentTest.cs index cf9e7427e3..7e12374d66 100644 --- a/src/GitVersion.App.Tests/PullRequestInBuildAgentTest.cs +++ b/src/GitVersion.App.Tests/PullRequestInBuildAgentTest.cs @@ -128,6 +128,19 @@ public async Task VerifyTravisCIPullRequest(string pullRequestRef) await VerifyPullRequestVersionIsCalculatedProperly(pullRequestRef, env); } + + [TestCaseSource(nameof(PrMergeRefs))] + public async Task VerifyBitBucketPipelinesPullRequest(string pullRequestRef) + { + + var env = new Dictionary + { + { BitBucketPipelines.EnvironmentVariableName, "MyWorkspace" }, + { BitBucketPipelines.PullRequestEnvironmentVariableName, pullRequestRef } + }; + await VerifyPullRequestVersionIsCalculatedProperly(pullRequestRef, env); + } + private static async Task VerifyPullRequestVersionIsCalculatedProperly(string pullRequestRef, Dictionary env) { using var fixture = new EmptyRepositoryFixture(); diff --git a/src/GitVersion.Core.Tests/BuildAgents/BitBucketPipelinesTests.cs b/src/GitVersion.Core.Tests/BuildAgents/BitBucketPipelinesTests.cs new file mode 100644 index 0000000000..ecd5218fa0 --- /dev/null +++ b/src/GitVersion.Core.Tests/BuildAgents/BitBucketPipelinesTests.cs @@ -0,0 +1,173 @@ +using GitVersion.BuildAgents; +using GitVersion.Core.Tests.Helpers; +using GitVersion.Helpers; +using GitVersion.VersionCalculation; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using Shouldly; + +namespace GitVersion.Core.Tests.BuildAgents; + +[TestFixture] +public class BitBucketPipelinesTests : TestBase +{ + private IEnvironment environment; + private BitBucketPipelines buildServer; + private IServiceProvider sp; + + [SetUp] + public void SetEnvironmentVariableForTest() + { + this.sp = ConfigureServices(services => services.AddSingleton()); + this.environment = sp.GetRequiredService(); + this.buildServer = sp.GetRequiredService(); + + this.environment.SetEnvironmentVariable(BitBucketPipelines.EnvironmentVariableName, "MyWorkspace"); + } + + + [Test] + public void CanNotApplyToCurrentContextWhenEnvironmentVariableNotSet() + { + // Arrange + this.environment.SetEnvironmentVariable(BitBucketPipelines.EnvironmentVariableName, ""); + + // Act + var result = this.buildServer.CanApplyToCurrentContext(); + + // Assert + result.ShouldBeFalse(); + } + + [Test] + public void CalculateVersionOnMainBranch() + { + // Arrange + this.environment.SetEnvironmentVariable(BitBucketPipelines.BranchEnvironmentVariableName, "refs/heads/main"); + + var vars = new TestableVersionVariables(fullSemVer: "1.2.3"); + var vsVersion = this.buildServer.GenerateSetVersionMessage(vars); + + vsVersion.ShouldBe("1.2.3"); + } + + [Test] + public void CalculateVersionOnDevelopBranch() + { + // Arrange + this.environment.SetEnvironmentVariable(BitBucketPipelines.BranchEnvironmentVariableName, "refs/heads/develop"); + + var vars = new TestableVersionVariables(fullSemVer: "1.2.3-unstable.4"); + var vsVersion = this.buildServer.GenerateSetVersionMessage(vars); + + vsVersion.ShouldBe("1.2.3-unstable.4"); + } + + [Test] + public void CalculateVersionOnFeatureBranch() + { + // Arrange + this.environment.SetEnvironmentVariable(BitBucketPipelines.BranchEnvironmentVariableName, "refs/heads/feature/my-work"); + + var vars = new TestableVersionVariables(fullSemVer: "1.2.3-beta.4"); + var vsVersion = this.buildServer.GenerateSetVersionMessage(vars); + + vsVersion.ShouldBe("1.2.3-beta.4"); + } + + [Test] + public void GetCurrentBranchShouldHandleBranches() + { + // Arrange + this.environment.SetEnvironmentVariable(BitBucketPipelines.BranchEnvironmentVariableName, "refs/heads/feature/my-work"); + + // Act + var result = this.buildServer.GetCurrentBranch(false); + + // Assert + result.ShouldBe($"refs/heads/feature/my-work"); + } + + [Test] + public void GetCurrentBranchShouldHandleTags() + { + // Arrange + this.environment.SetEnvironmentVariable(BitBucketPipelines.BranchEnvironmentVariableName, null); + this.environment.SetEnvironmentVariable(BitBucketPipelines.TagEnvironmentVariableName, "refs/heads/tags/1.2.3"); + + // Act + var result = this.buildServer.GetCurrentBranch(false); + + // Assert + result.ShouldBeNull(); + } + + [Test] + public void GetCurrentBranchShouldHandlePullRequests() + { + // Arrange + this.environment.SetEnvironmentVariable(BitBucketPipelines.BranchEnvironmentVariableName, null); + this.environment.SetEnvironmentVariable(BitBucketPipelines.TagEnvironmentVariableName, null); + this.environment.SetEnvironmentVariable(BitBucketPipelines.PullRequestEnvironmentVariableName, "refs/pull/1/merge"); + + // Act + var result = this.buildServer.GetCurrentBranch(false); + + // Assert + result.ShouldBeNull(); + } + + + [Test] + public void WriteAllVariablesToTheTextWriter() + { + var assemblyLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + assemblyLocation.ShouldNotBeNull(); + var f = PathHelper.Combine(assemblyLocation, "gitversion.properties"); + + try + { + AssertVariablesAreWrittenToFile(f); + } + finally + { + File.Delete(f); + } + } + + private void AssertVariablesAreWrittenToFile(string file) + { + var writes = new List(); + var semanticVersion = new SemanticVersion + { + Major = 1, + Minor = 2, + Patch = 3, + PreReleaseTag = "beta1", + BuildMetaData = "5" + }; + + semanticVersion.BuildMetaData.CommitDate = new DateTimeOffset(2022, 4, 6, 16, 10, 59, TimeSpan.FromHours(10)); + semanticVersion.BuildMetaData.Sha = "f28807e615e9f06aec8a33c87780374e0c1f6fb8"; + + var config = new TestEffectiveConfiguration(); + var variableProvider = this.sp.GetRequiredService(); + + var variables = variableProvider.GetVariablesFor(semanticVersion, config, false); + + this.buildServer.WithPropertyFile(file); + + this.buildServer.WriteIntegration(writes.Add, variables); + + writes[1].ShouldBe("1.2.3-beta.1+5"); + + File.Exists(file).ShouldBe(true); + + var props = File.ReadAllText(file); + + props.ShouldContain("export GITVERSION_MAJOR=1"); + props.ShouldContain("export GITVERSION_MINOR=2"); + props.ShouldContain("export GITVERSION_SHA=f28807e615e9f06aec8a33c87780374e0c1f6fb8"); + props.ShouldContain("export GITVERSION_COMMITDATE=2022-04-06"); + } +} diff --git a/src/GitVersion.Core/BuildAgents/BitBucketPipelines.cs b/src/GitVersion.Core/BuildAgents/BitBucketPipelines.cs new file mode 100644 index 0000000000..ae58d7f1fc --- /dev/null +++ b/src/GitVersion.Core/BuildAgents/BitBucketPipelines.cs @@ -0,0 +1,65 @@ +using GitVersion.Logging; +using GitVersion.OutputVariables; + +namespace GitVersion.BuildAgents; + +public class BitBucketPipelines : BuildAgentBase +{ + public const string EnvironmentVariableName = "BITBUCKET_WORKSPACE"; + public const string BranchEnvironmentVariableName = "BITBUCKET_BRANCH"; + public const string TagEnvironmentVariableName = "BITBUCKET_TAG"; + public const string PullRequestEnvironmentVariableName = "BITBUCKET_PR_ID"; + private string? file; + + public BitBucketPipelines(IEnvironment environment, ILog log) : base(environment, log) => WithPropertyFile("gitversion.properties"); + + protected override string EnvironmentVariable => EnvironmentVariableName; + + public override string? GenerateSetVersionMessage(VersionVariables variables) => variables.FullSemVer; + + public void WithPropertyFile(string propertiesFileName) => this.file = propertiesFileName; + + public override string[] GenerateSetParameterMessage(string name, string value) => new[] + { + $"GITVERSION_{name.ToUpperInvariant()}={value}" + }; + + public override void WriteIntegration(Action writer, VersionVariables variables, bool updateBuildNumber = true) + { + if (this.file is null) + return; + + base.WriteIntegration(writer, variables, updateBuildNumber); + writer($"Outputting variables to '{this.file}' ... "); + writer("To import the file into your build environment, add the following line to your build step:"); + writer($" - source {this.file}"); + writer(""); + writer("To reuse the file across build steps, add the file as a build artifact:"); + writer(" artifacts:"); + writer($" - {this.file}"); + + var exports = variables + .Select(variable => $"export GITVERSION_{variable.Key.ToUpperInvariant()}={variable.Value}") + .ToList(); + + File.WriteAllLines(this.file, exports); + } + + public override string? GetCurrentBranch(bool usingDynamicRepos) + { + var branchName = EvaluateEnvironmentVariable(BranchEnvironmentVariableName); + if (branchName != null && branchName.StartsWith("refs/heads/")) + { + return branchName; + } + + return null; + } + + private string? EvaluateEnvironmentVariable(string variableName) + { + var branchName = Environment.GetEnvironmentVariable(variableName); + Log.Info("Evaluating environment variable {0} : {1}", variableName, branchName!); + return branchName; + } +}