Skip to content
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
31 changes: 31 additions & 0 deletions docs/input/docs/reference/build-servers/gitlab.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,37 @@ To use GitVersion with GitLab CI, either use the [MSBuild
Task](/docs/usage/msbuild) or put the GitVersion executable in your
runner's `PATH`.

### Merge Request pipelines

In merge request pipelines GitLab sets `CI_MERGE_REQUEST_REF_PATH` (for example
`refs/merge-requests/15/head` or `refs/merge-requests/15/merge`). GitVersion
reads this variable through the `GitLabCi` build agent when it is present,
following the same pass-through pattern as Azure Pipelines (`BUILD_SOURCEBRANCH`)
and GitHub Actions (`GITHUB_REF`).

Branch resolution order in `GitLabCi`:

1. `CI_COMMIT_TAG` set — treat as a tag pipeline (`GetCurrentBranch` returns `null`)
2. `CI_MERGE_REQUEST_REF_PATH` set — use the merge request ref
3. otherwise — `CI_COMMIT_REF_NAME` (branch name)

After repository normalisation the friendly branch name becomes
`merge-requests/<iid>/head` or `merge-requests/<iid>/merge`. Extend the
`pull-request` branch configuration in `GitVersion.yml` so the regex matches
GitLab's namespace (the default `pull-requests|pull|pr` pattern does not):

```yaml
workflow: GitFlow/v1
branches:
pull-request:
regex: ^merge-requests/(?<Number>\d+)/(head|merge)$
label: PullRequest{Number}
```

`CI_COMMIT_REF_NAME` still contains the source branch name (for example
`feature/foo`) in MR pipelines; it is ignored when `CI_MERGE_REQUEST_REF_PATH`
is set.

A working example of integrating GitVersion with GitLab is maintained in the project [Utterly Automated Versioning][utterly-automated-versioning]

Here is a summary of what it demonstrated (many more details in the [Readme][readme])
Expand Down
35 changes: 31 additions & 4 deletions src/GitVersion.App.Tests/PullRequestInBuildAgentTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ public class PullRequestInBuildAgentTest
"refs/remotes/pull-requests/5/merge"
];

private static readonly string[] GitLabMergeRequestRefs =
[
"refs/merge-requests/5/head",
"refs/merge-requests/5/merge"
];

[TestCaseSource(nameof(PrMergeRefs))]
public async Task VerifyAzurePipelinesPullRequest(string pullRequestRef)
{
Expand Down Expand Up @@ -75,15 +81,16 @@ public async Task VerifyGitHubActionsPullRequest(string pullRequestRef)
await VerifyPullRequestVersionIsCalculatedProperly(pullRequestRef, env);
}

[TestCaseSource(nameof(PrMergeRefs))]
public async Task VerifyGitLabCIPullRequest(string pullRequestRef)
[TestCaseSource(nameof(GitLabMergeRequestRefs))]
public async Task VerifyGitLabCIPullRequest(string mergeRequestRef)
{
var env = new Dictionary<string, string>
{
{ GitLabCi.EnvironmentVariableName, "true" },
{ "CI_COMMIT_REF_NAME", PullRequestBranchName }
{ GitLabCi.MergeRequestRefPathEnvironmentVariableName, mergeRequestRef },
{ GitLabCi.CommitRefNameEnvironmentVariableName, "FeatureBranch" }
};
await VerifyPullRequestVersionIsCalculatedProperly(pullRequestRef, env);
await VerifyGitLabMergeRequestVersionIsCalculatedProperly(mergeRequestRef, env);
}

[TestCaseSource(nameof(PrMergeRefs))]
Expand Down Expand Up @@ -142,9 +149,29 @@ public async Task VerifyBitBucketPipelinesPullRequest(string pullRequestRef)
await VerifyPullRequestVersionIsCalculatedProperly(pullRequestRef, env);
}

private const string GitLabMergeRequestPullRequestConfig = """
workflow: GitFlow/v1
branches:
pull-request:
regex: ^merge-requests/(?<Number>\d+)/(head|merge)$
""";

private static async Task VerifyGitLabMergeRequestVersionIsCalculatedProperly(string mergeRequestRef, Dictionary<string, string> env)
{
using var fixture = new EmptyRepositoryFixture();
var configPath = FileSystemHelper.Path.Combine(fixture.RepositoryPath, "GitVersion.yml");
await File.WriteAllTextAsync(configPath, GitLabMergeRequestPullRequestConfig);
await VerifyPullRequestVersionIsCalculatedProperly(fixture, mergeRequestRef, env);
}

private static async Task VerifyPullRequestVersionIsCalculatedProperly(string pullRequestRef, Dictionary<string, string> env)
{
using var fixture = new EmptyRepositoryFixture();
await VerifyPullRequestVersionIsCalculatedProperly(fixture, pullRequestRef, env);
}

private static async Task VerifyPullRequestVersionIsCalculatedProperly(EmptyRepositoryFixture fixture, string pullRequestRef, Dictionary<string, string> env)
{
var remoteRepositoryPath = FileSystemHelper.Path.GetRepositoryTempPath();
RepositoryFixtureBase.Init(remoteRepositoryPath);
using var remoteRepository = new Repository(remoteRepositoryPath);
Expand Down
47 changes: 34 additions & 13 deletions src/GitVersion.BuildAgents.Tests/Agents/GitLabCiTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@ public void SetUp()
}

[TearDown]
public void TearDown() => this.environment.SetEnvironmentVariable(GitLabCi.EnvironmentVariableName, null);
public void TearDown()
{
this.environment.SetEnvironmentVariable(GitLabCi.EnvironmentVariableName, null);
this.environment.SetEnvironmentVariable(GitLabCi.CommitRefNameEnvironmentVariableName, null);
this.environment.SetEnvironmentVariable(GitLabCi.CommitTagEnvironmentVariableName, null);
this.environment.SetEnvironmentVariable(GitLabCi.MergeRequestRefPathEnvironmentVariableName, null);
}

[Test]
public void ShouldSetBuildNumber()
Expand All @@ -51,7 +57,7 @@ public void ShouldSetOutputVariables()
[TestCase("#3-change_projectname", "#3-change_projectname")]
public void GetCurrentBranchShouldHandleBranches(string branchName, string expectedResult)
{
this.environment.SetEnvironmentVariable("CI_COMMIT_REF_NAME", branchName);
this.environment.SetEnvironmentVariable(GitLabCi.CommitRefNameEnvironmentVariableName, branchName);

var result = this.buildServer.GetCurrentBranch(false);

Expand All @@ -64,8 +70,8 @@ public void GetCurrentBranchShouldHandleBranches(string branchName, string expec
[TestCase("v1.2.1", "v1.2.1", null)]
public void GetCurrentBranchShouldHandleTags(string branchName, string commitTag, string? expectedResult)
{
this.environment.SetEnvironmentVariable("CI_COMMIT_REF_NAME", branchName);
this.environment.SetEnvironmentVariable("CI_COMMIT_TAG", commitTag); // only set in pipelines for tags
this.environment.SetEnvironmentVariable(GitLabCi.CommitRefNameEnvironmentVariableName, branchName);
this.environment.SetEnvironmentVariable(GitLabCi.CommitTagEnvironmentVariableName, commitTag); // only set in pipelines for tags

var result = this.buildServer.GetCurrentBranch(false);

Expand All @@ -79,18 +85,33 @@ public void GetCurrentBranchShouldHandleTags(string branchName, string commitTag
}
}

[TestCase("main", "main")]
[TestCase("dev", "dev")]
[TestCase("development", "development")]
[TestCase("my_cool_feature", "my_cool_feature")]
[TestCase("#3-change_projectname", "#3-change_projectname")]
public void GetCurrentBranchShouldHandlePullRequests(string branchName, string expectedResult)
[TestCase("refs/merge-requests/1/head", "refs/merge-requests/1/head")]
[TestCase("refs/merge-requests/42/merge", "refs/merge-requests/42/merge")]
public void GetCurrentBranchShouldHandleMergeRequestRefPaths(string refPath, string expected)
{
this.environment.SetEnvironmentVariable("CI_COMMIT_REF_NAME", branchName);
this.environment.SetEnvironmentVariable(GitLabCi.MergeRequestRefPathEnvironmentVariableName, refPath);
this.environment.SetEnvironmentVariable(GitLabCi.CommitRefNameEnvironmentVariableName, "developer");

var result = this.buildServer.GetCurrentBranch(false);
this.buildServer.GetCurrentBranch(false).ShouldBe(expected);
}

result.ShouldBe(expectedResult);
[Test]
public void GetCurrentBranchShouldPreferTagOverMergeRequestRefPath()
{
this.environment.SetEnvironmentVariable(GitLabCi.MergeRequestRefPathEnvironmentVariableName, "refs/merge-requests/1/head");
this.environment.SetEnvironmentVariable(GitLabCi.CommitRefNameEnvironmentVariableName, "v1.0.0");
this.environment.SetEnvironmentVariable(GitLabCi.CommitTagEnvironmentVariableName, "v1.0.0");

this.buildServer.GetCurrentBranch(false).ShouldBeNull();
}

[Test]
public void GetCurrentBranchShouldFallBackToRefNameWhenMergeRequestRefPathIsEmpty()
{
this.environment.SetEnvironmentVariable(GitLabCi.MergeRequestRefPathEnvironmentVariableName, "");
this.environment.SetEnvironmentVariable(GitLabCi.CommitRefNameEnvironmentVariableName, "developer");

this.buildServer.GetCurrentBranch(false).ShouldBe("developer");
}

[Test]
Expand Down
21 changes: 17 additions & 4 deletions src/GitVersion.BuildAgents/Agents/GitLabCi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ namespace GitVersion.Agents;
internal class GitLabCi : BuildAgentBase
{
public const string EnvironmentVariableName = "GITLAB_CI";
public const string CommitRefNameEnvironmentVariableName = "CI_COMMIT_REF_NAME";
public const string CommitTagEnvironmentVariableName = "CI_COMMIT_TAG";
public const string MergeRequestRefPathEnvironmentVariableName = "CI_MERGE_REQUEST_REF_PATH";
private string? file;

public GitLabCi(IEnvironment environment, ILog log, IFileSystem fileSystem) : base(environment, log, fileSystem) => WithPropertyFile("gitversion.properties");
Expand All @@ -22,14 +25,24 @@ public override string[] SetOutputVariables(string name, string? value) =>
$"GitVersion_{name}={value}"
];

// CI_MERGE_REQUEST_REF_PATH is only available in merge request pipelines,
// CI_COMMIT_REF_NAME can contain either the branch or the tag
// See https://docs.gitlab.com/ee/ci/variables/predefined_variables.html
// CI_COMMIT_TAG is only available in tag pipelines,
// so we can exit if CI_COMMIT_REF_NAME would return the tag
public override string? GetCurrentBranch(bool usingDynamicRepos) =>
string.IsNullOrEmpty(this.environment.GetEnvironmentVariable("CI_COMMIT_TAG"))
? this.environment.GetEnvironmentVariable("CI_COMMIT_REF_NAME")
: null;
public override string? GetCurrentBranch(bool usingDynamicRepos)
{
if (!string.IsNullOrEmpty(this.environment.GetEnvironmentVariable(CommitTagEnvironmentVariableName)))
{
return null;
}
var mergeRequestRefPath = this.environment.GetEnvironmentVariable(MergeRequestRefPathEnvironmentVariableName);
if (!string.IsNullOrEmpty(mergeRequestRefPath))
{
return mergeRequestRefPath;
}
return this.environment.GetEnvironmentVariable(CommitRefNameEnvironmentVariableName);
}

public override bool PreventFetch() => true;

Expand Down
Loading