Skip to content

Extend git flow complex example to post merge and rebase behaviour #4585

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,12 @@ public static void WriteVersionVariables(this RepositoryFixtureBase fixture, str
}

public static void AssertFullSemver(this RepositoryFixtureBase fixture, string fullSemver,
IGitVersionConfiguration? configuration = null, IRepository? repository = null, string? commitId = null, bool onlyTrackedBranches = true, string? targetBranch = null)
IGitVersionConfiguration? configuration = null, IRepository? repository = null, string? commitId = null, bool onlyTrackedBranches = true, string? targetBranch = null, string? customMessage = null)
{
repository ??= fixture.Repository;

var variables = GetVersion(fixture, configuration, repository, commitId, onlyTrackedBranches, targetBranch);
variables.FullSemVer.ShouldBe(fullSemver);
variables.FullSemVer.ShouldBe(fullSemver, customMessage);
if (commitId == null)
{
fixture.SequenceDiagram.NoteOver(fullSemver, repository.Head.FriendlyName, color: "#D3D3D3");
Expand Down
2 changes: 1 addition & 1 deletion src/GitVersion.Core.Tests/Helpers/TestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace GitVersion.Core.Tests.Helpers;

public class TestBase
{
public const string MainBranch = "main";
public const string MainBranch = RepositoryFixtureBase.MainBranch;
Copy link
Contributor Author

@9swampy 9swampy Jun 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#4589 Magic strings >> BranchConfigurationKey.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May I ask you: Why do you care about how the branch is named in the unit tests? Does it make any different if it is named master or main? From the configuration point of view both are included and treated the same. At the end everyone can use their own name and their own configuration in the unit tests. There is no need for unification IMO.


protected static IServiceProvider ConfigureServices(Action<IServiceCollection>? overrideServices = null)
{
Expand Down
120 changes: 94 additions & 26 deletions src/GitVersion.Core.Tests/IntegrationTests/GitflowScenarios.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using GitVersion.Configuration;
using GitVersion.Core.Tests.Helpers;
using GitVersion.Helpers;
using LibGit2Sharp;

namespace GitVersion.Core.Tests.IntegrationTests;

Expand All @@ -8,75 +11,140 @@ public class GitflowScenarios : TestBase
[Test]
public void GitflowComplexExample()
{
var keepBranches = true;
const string developBranch = "develop";
const string feature1Branch = "feature/f1";
const string feature2Branch = "feature/f2";
const string release1Branch = "release/1.1.0";
const string release2Branch = "release/1.2.0";
const string hotfixBranch = "hotfix/hf";

using var fixture = new BaseGitFlowRepositoryFixture("1.0.0");
fixture.AssertFullSemver("1.1.0-alpha.1");
var configuration = GitFlowConfigurationBuilder.New.Build();

using var fixture = new BaseGitFlowRepositoryFixture(initialMainAction, deleteOnDispose: false);
var fullSemver = "1.1.0-alpha.1";
fixture.AssertFullSemver(fullSemver, configuration);

// Feature 1
fixture.BranchTo(feature1Branch);
fixture.MakeACommit("added feature 1");
fixture.AssertFullSemver("1.1.0-f1.1+2");

fixture.MakeACommit($"added feature 1 >> {fullSemver}");
fullSemver = "1.1.0-f1.1+2";
fixture.AssertFullSemver(fullSemver, configuration);
fixture.Checkout(developBranch);
fixture.MergeNoFF(feature1Branch);
fixture.Repository.Branches.Remove(fixture.Repository.Branches[feature1Branch]);
fixture.AssertFullSemver("1.1.0-alpha.3");
if (!keepBranches) fixture.Repository.Branches.Remove(fixture.Repository.Branches[feature1Branch]);
fixture.AssertFullSemver("1.1.0-alpha.3", configuration);

// Release 1.1.0
fixture.BranchTo(release1Branch);
fixture.MakeACommit("release stabilization");
fixture.AssertFullSemver("1.1.0-beta.1+4");
fixture.AssertFullSemver("1.1.0-beta.1+4", configuration);
fixture.Checkout(MainBranch);
fixture.MergeNoFF(release1Branch);
fixture.AssertFullSemver("1.1.0-5");
fixture.AssertFullSemver("1.1.0-5", configuration);
fixture.ApplyTag("1.1.0");
fixture.AssertFullSemver("1.1.0");
fixture.AssertFullSemver("1.1.0", configuration);
fixture.Checkout(developBranch);
fixture.MergeNoFF(release1Branch);
fixture.Repository.Branches.Remove(fixture.Repository.Branches[release1Branch]);
fixture.AssertFullSemver("1.2.0-alpha.1");
fixture.AssertFullSemver("1.2.0-alpha.1", configuration);

// Feature 2
fixture.BranchTo(feature2Branch);
fixture.MakeACommit("added feature 2");
fixture.AssertFullSemver("1.2.0-f2.1+2");
fullSemver = "1.2.0-f2.1+2";
fixture.MakeACommit($"added feature 2 >> {fullSemver}");
fixture.AssertFullSemver(fullSemver, configuration);
fixture.Checkout(developBranch);
fixture.MergeNoFF(feature2Branch);
fixture.Repository.Branches.Remove(fixture.Repository.Branches[feature2Branch]);
fixture.AssertFullSemver("1.2.0-alpha.3");
if (!keepBranches) fixture.Repository.Branches.Remove(fixture.Repository.Branches[feature2Branch]);
fixture.AssertFullSemver("1.2.0-alpha.3", configuration);

// Release 1.2.0
fixture.BranchTo(release2Branch);
fixture.MakeACommit("release stabilization");
fixture.AssertFullSemver("1.2.0-beta.1+8");
fullSemver = "1.2.0-beta.1+8";
fixture.MakeACommit($"release stabilization >> {fullSemver}");
fixture.AssertFullSemver(fullSemver, configuration);
fixture.Checkout(MainBranch);
fixture.MergeNoFF(release2Branch);
fixture.AssertFullSemver("1.2.0-5");
fixture.AssertFullSemver("1.2.0-5", configuration);
fixture.ApplyTag("1.2.0");
fixture.AssertFullSemver("1.2.0");
fixture.AssertFullSemver("1.2.0", configuration);
fixture.Checkout(developBranch);
fixture.MergeNoFF(release2Branch);
fixture.Repository.Branches.Remove(fixture.Repository.Branches[release2Branch]);
fixture.AssertFullSemver("1.3.0-alpha.1");
if (!keepBranches) fixture.Repository.Branches.Remove(fixture.Repository.Branches[release2Branch]);
fixture.AssertFullSemver("1.3.0-alpha.1", configuration);

// Hotfix
fixture.Checkout(MainBranch);
fixture.BranchTo(hotfixBranch);
fixture.MakeACommit("added hotfix");
fixture.AssertFullSemver("1.2.1-beta.1+1");
fullSemver = "1.2.1-beta.1+1";
fixture.MakeACommit($"added hotfix >> {fullSemver}");
fixture.AssertFullSemver(fullSemver, configuration);
fixture.Checkout(MainBranch);
fixture.MergeNoFF(hotfixBranch);
fixture.AssertFullSemver("1.2.1-2");
fixture.AssertFullSemver("1.2.1-2", configuration);
fixture.ApplyTag("1.2.1");
fixture.AssertFullSemver("1.2.1");
fixture.AssertFullSemver("1.2.1", configuration);
fixture.Checkout(developBranch);
fixture.MergeNoFF(hotfixBranch);
fixture.Repository.Branches.Remove(fixture.Repository.Branches[hotfixBranch]);
fixture.AssertFullSemver("1.3.0-alpha.2");
if (!keepBranches) fixture.Repository.Branches.Remove(fixture.Repository.Branches[hotfixBranch]);
fixture.AssertFullSemver("1.3.0-alpha.2", configuration);

fixture.Checkout(feature2Branch);
fixture.AssertFullSemver(
"1.3.0-f2.1+0",
configuration,
customMessage:
"Feature branches use inherited versioning (increment: inherit), " + System.Environment.NewLine +
"and your config inherits from develop." + System.Environment.NewLine + System.Environment.NewLine +
"GitVersion uses the merge base between the feature and develop to determine the version." + System.Environment.NewLine + System.Environment.NewLine +
"As develop progresses (e.g., by releasing 1.2.0), rebuilding old feature branches can" + System.Environment.NewLine +
"produce different versions.");

fullSemver = "1.3.0-f2.1+1";
fixture.MakeACommit(
"feature 2 additional commit after original feature has been merged to develop " + System.Environment.NewLine +
$"and release/1.2.0 has already happened >> {fullSemver}" +
"Problem #1: 1.3.0-f2.1+0 is what I observe when I run dotnet-gitversion 6.3.0 but in the repo the assertion is 1.3.0-f2.1+1" +
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Problem 1:

  • 1.3.0-f2.1+1 is what the test asserts
  • 1.3.0-f2.1+0 is what I observe when I run dotnet-gitversion 6.3.0

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have executed gitversion (6.3.0) after the test step you mentioned and got 1.3.0-f2.1+1:

image

"After rebase 1.3.0-f2.1+3 is both what the test asserts and what I observe when I run dotnet-gitversion 6.3.0." +
"Problem #2: I expected to get the same before and after the rebase." +
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Problem 2:

  • Before rebase 1.3.0-f2.1+1 is what the test asserts (but not what dotnet-gitversion produces - see Problem 1).
  • After rebase 1.3.0-f2.1+3 is what both tests and dotnet-gitversion 6.3.0 produce.

I'd have anticipated1.3.0-f2.1+1 both before and after rebase, in this specific scenario.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The status of the repository is not the same and the information Gitversion uses to determine the next version is different:

Before rebase:
Repo_Befor_Rebase_1 3 0-f2 1+1

After rebase:
Repo_After_Rebase_1 3 0-f2 1+3

What is your point here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Odd. If I run the test now and flip out to the command line the config that's being persisted by the test is no longer valid for running with 6.3.0 but it works for you? I'll look again when it's a more sensible o'clock.

In the meantime, as I said on my opening post, if my expectations aren't correct then I would like to understand what would be correct.
1 - The merge bumps the root version on the same f2 commit from 1.2 to 1.3; that's an expected change due to changed state, and the +0 resets since there have now been no commits since version source. All fine.
2 - I'd expected the first commit on the branch since the last version bump to be f2.1+1 as the test asserts before rebase. I concur with the test. Dotnet-gitversion 6.3.0 tbc.
3 - If I rebase that commit, yes the whole tree is different but that first commit is no longer considered the first commit on the branch since the version source? I'd not expected that.

How does +1 jump to +3? If there's documentation, point me at it as I do appreciate you've been generous with your time already.

To be fair working out how to configure the majority minor patch bumps work now is what I'd expected to be playing with in the tests, not the CommitsSinceVersionSource idiosyncrasies...

"" +
"Whether my expectations are correct or not could we at least build upon the documentation I have started to add " +
"as an explanation of observed behaviour. I'm happy to translate an explanation in to test " +
"documentation if you confirm it would be accepted on PR."
);

var identity = new Identity(
fixture.Repository.Head.Tip.Committer.Name,
fixture.Repository.Head.Tip.Committer.Email);
fixture.AssertFullSemver(fullSemver, configuration);
var rebaseResult = fixture.Repository.Rebase.Start(
fixture.Repository.Branches[feature2Branch],
fixture.Repository.Branches[developBranch],
fixture.Repository.Branches[developBranch],
identity,
new RebaseOptions());
while (rebaseResult != null && rebaseResult.Status != RebaseStatus.Complete)
{
rebaseResult = fixture.Repository.Rebase.Continue(identity, new RebaseOptions());
}

fixture.AssertFullSemver(fullSemver, configuration, customMessage: "I expected to get the same before and after the rebase.");

void initialMainAction(IRepository r)
{
if (configuration is GitVersionConfiguration concreteConfig)
{
var yaml = new ConfigurationSerializer().Serialize(concreteConfig);
const string fileName = "GitVersion.yml";
var filePath = FileSystemHelper.Path.Combine(r.Info.Path, "..", fileName);
File.WriteAllText(filePath, yaml);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentionally writing out the GitVersion.yml to demonstrate inconsistency between UnitTest version calculation and running dotnet-gitversion from the commandline. Refer to 2 problems in commentary.

r.Index.Add(fileName);
r.Index.Write();
}

r.MakeATaggedCommit("1.0.0", $"Initial commit on {MainBranch}");
}
}
}
10 changes: 5 additions & 5 deletions src/GitVersion.Testing/Fixtures/BaseGitFlowRepositoryFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@ public class BaseGitFlowRepositoryFixture : EmptyRepositoryFixture
/// <para>Creates a repo with a develop branch off main which is a single commit ahead of main branch</para>
/// <para>Main will be tagged with the initial version before branching develop</para>
/// </summary>
public BaseGitFlowRepositoryFixture(string initialVersion, string branchName = "main") :
this(r => r.MakeATaggedCommit(initialVersion), branchName)
public BaseGitFlowRepositoryFixture(string initialVersion, string branchName = MainBranch, bool deleteOnDispose = true) :
this(r => r.MakeATaggedCommit(initialVersion), branchName, deleteOnDispose)
{
}

/// <summary>
/// <para>Creates a repo with a develop branch off main which is a single commit ahead of main</para>
/// <para>The initial setup actions will be performed before branching develop</para>
/// </summary>
public BaseGitFlowRepositoryFixture(Action<IRepository> initialMainAction, string branchName = "main") :
base(branchName) => SetupRepo(initialMainAction);
public BaseGitFlowRepositoryFixture(Action<IRepository> initialMainAction, string branchName = MainBranch, bool deleteOnDispose = true) :
base(branchName, deleteOnDispose) => SetupRepo(initialMainAction);

private void SetupRepo(Action<IRepository> initialMainAction)
{
Expand All @@ -33,6 +33,6 @@ private void SetupRepo(Action<IRepository> initialMainAction)
initialMainAction(Repository);

Commands.Checkout(Repository, Repository.CreateBranch("develop"));
Repository.MakeACommit();
Repository.MakeACommit("First commit on new branch 'develop'");
}
}
2 changes: 1 addition & 1 deletion src/GitVersion.Testing/Fixtures/EmptyRepositoryFixture.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
namespace GitVersion.Testing;

public class EmptyRepositoryFixture(string branchName = "main") : RepositoryFixtureBase(path => CreateNewRepository(path, branchName));
public class EmptyRepositoryFixture(string branchName = RepositoryFixtureBase.MainBranch, bool deleteOnDispose = true) : RepositoryFixtureBase(path => CreateNewRepository(path, branchName), deleteOnDispose);
2 changes: 1 addition & 1 deletion src/GitVersion.Testing/Fixtures/RemoteRepositoryFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class RemoteRepositoryFixture : RepositoryFixtureBase
public RemoteRepositoryFixture(Func<string, Repository> builder)
: base(builder) => LocalRepositoryFixture = CloneRepository();

public RemoteRepositoryFixture(string branchName = "main")
public RemoteRepositoryFixture(string branchName = MainBranch)
: this(path => CreateNewRepository(path, branchName, 5))
{
}
Expand Down
33 changes: 22 additions & 11 deletions src/GitVersion.Testing/Fixtures/RepositoryFixtureBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,21 @@ namespace GitVersion.Testing;
/// </summary>
public abstract class RepositoryFixtureBase : IDisposable
{
protected RepositoryFixtureBase(Func<string, Repository> repositoryBuilder)
: this(repositoryBuilder(FileSystemHelper.Path.GetRepositoryTempPath()))
public const string MainBranch = "main";
private readonly bool deleteOnDispose;

protected RepositoryFixtureBase(Func<string, Repository> repositoryBuilder, bool deleteOnDispose = true)
: this(repositoryBuilder(FileSystemHelper.Path.GetRepositoryTempPath()), deleteOnDispose)
{
}

protected RepositoryFixtureBase(Repository repository)
protected RepositoryFixtureBase(Repository repository, bool deleteOnDispose = true)
{
SequenceDiagram = new();
Repository = repository.ShouldNotBeNull();
Repository.Config.Set("user.name", "Test");
Repository.Config.Set("user.email", "test@email.com");
this.deleteOnDispose = deleteOnDispose;
}

public Repository Repository { get; }
Expand Down Expand Up @@ -47,16 +51,23 @@ protected virtual void Dispose(bool disposing)
Repository.Dispose();
var directoryPath = FileSystemHelper.Path.GetFileName(RepositoryPath);

try
if (deleteOnDispose)
{
Console.WriteLine("Cleaning up repository path at {0}", directoryPath);
FileSystemHelper.Directory.DeleteDirectory(RepositoryPath);
Console.WriteLine("Cleaned up repository path at {0}", directoryPath);
try
{
Console.WriteLine("Cleaning up repository path at {0}", directoryPath);
FileSystemHelper.Directory.DeleteDirectory(RepositoryPath);
Console.WriteLine("Cleaned up repository path at {0}", directoryPath);
}
catch (Exception e)
{
Console.WriteLine("Failed to clean up repository path at {0}. Received exception: {1}", directoryPath, e.Message);
// throw;
}
}
catch (Exception e)
else
{
Console.WriteLine("Failed to clean up repository path at {0}. Received exception: {1}", directoryPath, e.Message);
// throw;
Console.WriteLine("Leaving repository path at {0} intact", directoryPath);
}

this.SequenceDiagram.End();
Expand All @@ -73,7 +84,7 @@ public void Remove(string branch)
SequenceDiagram.Destroy(branch);
}

public static void Init(string path, string branchName = "main") => GitTestExtensions.ExecuteGitCmd($"init {path} -b {branchName}", ".");
public static void Init(string path, string branchName = MainBranch) => GitTestExtensions.ExecuteGitCmd($"init {path} -b {branchName}", ".");

public string MakeATaggedCommit(string tag)
{
Expand Down
4 changes: 2 additions & 2 deletions src/GitVersion.Testing/GitTestExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ private static Commit CreateFileAndCommit(this IRepository repository, string re
Generate.SignatureNow(), Generate.SignatureNow());
}

public static Tag MakeATaggedCommit(this IRepository repository, string tag)
public static Tag MakeATaggedCommit(this IRepository repository, string tag, string? commitMessage = null)
{
var commit = repository.MakeACommit();
var commit = repository.MakeACommit(commitMessage);
var existingTag = repository.Tags.SingleOrDefault(t => t.FriendlyName == tag);
return existingTag ?? repository.Tags.Add(tag, commit);
}
Expand Down
Loading