From 5ada1ae15ff11a0ac5ad6f4d40e368145d6bdea2 Mon Sep 17 00:00:00 2001
From: bitbonk <bitbonk@msn.com>
Date: Fri, 9 Sep 2022 18:07:28 +0200
Subject: [PATCH 1/4] Add demo test screnarion

---
 .../VersioningDemoScenario.cs                 | 114 ++++++++++++++++++
 1 file changed, 114 insertions(+)
 create mode 100644 src/GitVersion.Core.Tests/IntegrationTests/VersioningDemoScenario.cs

diff --git a/src/GitVersion.Core.Tests/IntegrationTests/VersioningDemoScenario.cs b/src/GitVersion.Core.Tests/IntegrationTests/VersioningDemoScenario.cs
new file mode 100644
index 0000000000..4bea88d09c
--- /dev/null
+++ b/src/GitVersion.Core.Tests/IntegrationTests/VersioningDemoScenario.cs
@@ -0,0 +1,114 @@
+using GitTools.Testing;
+using GitVersion.Model.Configuration;
+using GitVersion.OutputVariables;
+using LibGit2Sharp;
+using NUnit.Framework;
+using Shouldly;
+
+namespace GitVersion.Core.Tests.IntegrationTests;
+
+public class VersioningDemoScenario
+{
+    [Test]
+    public void ReleaseAndDevelopProblemDemo()
+    {
+        var configuration = new Config
+        {
+            // Settings the below NextVersion results in an exception
+            // I wanted to set it to globally start with version 1
+            // Setting the version to 1.0 (which is not a valid semantic version)
+            // works in GitVersion.yml but not here.
+
+            // NextVersion = "1.0"
+        };
+
+        // create main and develop branch, develop is two commits ahead of main
+        using var fixture = new EmptyRepositoryFixture();
+        fixture.Repository.MakeACommit();
+        Commands.Checkout(fixture.Repository, fixture.Repository.CreateBranch("develop"));
+        fixture.Repository.MakeACommit();
+        fixture.Repository.MakeACommit();
+
+        var previousVersion = GetSemVer(fixture.GetVersion(configuration));
+
+        // make a 3rd commit on develop
+        fixture.Repository.MakeACommit();
+
+        var currentVersion = GetSemVer(fixture.GetVersion(configuration));
+
+        currentVersion.ShouldBeGreaterThan(previousVersion,
+            "the semver should be incremented after a commit on develop");
+
+        // we are ready to prepare the 1.0.0 release, create and checkout release/1.0.0
+        Commands.Checkout(fixture.Repository, fixture.Repository.CreateBranch("release/1.0.0"));
+
+        fixture.GetVersion(configuration).SemVer.ShouldBe("1.0.0-beta.1",
+            "the first semver on release/1.0.0 should be beta1");
+
+        // make another commit on release/1.0.0 to prepare the actual beta1 release
+        fixture.Repository.MakeACommit();
+
+        fixture.GetVersion(configuration).SemVer.ShouldBe("1.0.0-beta.1",
+            "the semver on release/1.0.0 should still be be beta1");
+
+        Commands.Checkout(fixture.Repository, fixture.Repository.Branches["develop"]);
+
+        previousVersion = currentVersion;
+        currentVersion = GetSemVer(fixture.GetVersion(configuration));
+
+        currentVersion.ShouldBe(previousVersion,
+            "the semver on develop should not have changed " +
+            "even when release/1.0.0 has new commits due to beta 1 preparations");
+
+        // now some other team member makes changes on develop that may or may not end up in 1.0.0
+        fixture.Repository.MakeACommit();
+        fixture.Repository.MakeACommit();
+
+        previousVersion = currentVersion;
+        currentVersion = GetSemVer(fixture.GetVersion(configuration));
+
+        currentVersion.ShouldBeGreaterThan(previousVersion,
+            "the semver should be incremented after a even more commit on develop");
+
+        Commands.Checkout(fixture.Repository, fixture.Repository.Branches["release/1.0.0"]);
+
+        // now we release the beta 1
+        fixture.Repository.ApplyTag("1.0.0-beta1");
+
+        fixture.GetVersion(configuration).SemVer.ShouldBe("1.0.0-beta.1",
+            "the on release/1.0.0 should still be beta1 after the beta 1 tag");
+
+        // continue with more work on develop that may or may not end up in 1.0.0
+        Commands.Checkout(fixture.Repository, fixture.Repository.Branches["develop"]);
+        fixture.Repository.MakeACommit();
+        fixture.Repository.MakeACommit();
+
+        previousVersion = currentVersion;
+        currentVersion = GetSemVer(fixture.GetVersion(configuration));
+
+        currentVersion.ShouldBeGreaterThan(previousVersion,
+            "the semver should be incremented after a even more commit on develop");
+
+        // now we decide that the three commits on develop should be part of the beta 2 release
+        // se we merge it into release/1.0.0 with --no-ff because it is a protected branch
+        Commands.Checkout(fixture.Repository, fixture.Repository.Branches["release/1.0.0"]);
+        fixture.Repository.Merge(
+            fixture.Repository.Branches["develop"],
+            Generate.SignatureNow(),
+            new MergeOptions {FastForwardStrategy = FastForwardStrategy.NoFastForward});
+
+        fixture.GetVersion(configuration).SemVer.ShouldBe("1.0.0-beta.2",
+            "the next semver on release/1.0.0 should be beta2");
+
+        Commands.Checkout(fixture.Repository, fixture.Repository.Branches["develop"]);
+        previousVersion = currentVersion;
+        currentVersion = GetSemVer(fixture.GetVersion(configuration));
+
+        currentVersion.ShouldBeGreaterThanOrEqualTo(previousVersion,
+            "the semver should be incremented (or unchanged) " +
+            "after we merged develop into release/1.0.0");
+
+        static SemanticVersion GetSemVer(VersionVariables ver)
+            => SemanticVersion.Parse(ver.FullSemVer, null);
+    }
+}

From bc3204d8bbec61bdcc0994de6d316c04d7970e93 Mon Sep 17 00:00:00 2001
From: bitbonk <bitbonk@msn.com>
Date: Sat, 10 Sep 2022 14:34:12 +0200
Subject: [PATCH 2/4] Add a reduced demo test

---
 .../ReducedReleaseWorkflowDemo.cs             | 103 ++++++++++++++++++
 1 file changed, 103 insertions(+)
 create mode 100644 src/GitVersion.Core.Tests/IntegrationTests/ReducedReleaseWorkflowDemo.cs

diff --git a/src/GitVersion.Core.Tests/IntegrationTests/ReducedReleaseWorkflowDemo.cs b/src/GitVersion.Core.Tests/IntegrationTests/ReducedReleaseWorkflowDemo.cs
new file mode 100644
index 0000000000..312b46d245
--- /dev/null
+++ b/src/GitVersion.Core.Tests/IntegrationTests/ReducedReleaseWorkflowDemo.cs
@@ -0,0 +1,103 @@
+using GitTools.Testing;
+using GitVersion.Model.Configuration;
+using LibGit2Sharp;
+using NUnit.Framework;
+using Shouldly;
+
+namespace GitVersion.Core.Tests.IntegrationTests;
+
+public class ReducedReleaseWorkflowDemo : IDisposable
+{
+    private static readonly EmptyRepositoryFixture _fixture = new();
+
+    private static readonly Config _config = new()
+    {
+        // ❓ In my GitVersion.yml I actually have set the version to "1.0"
+        // but that will cause an exception when I do it here in the tests
+        NextVersion = "1.0.0"
+    };
+
+    public void Dispose() => _fixture.Dispose();
+
+    [Test]
+    public void Demo()
+    {
+        // create main and develop branches
+        // develop is one commits ahead of main
+        MakeACommit();
+        CreateAndCheckoutBranch("develop");
+        MakeACommit();
+
+        // ✅ succeeds as expected
+        GetCurrentSemVer().ShouldBe("1.0.0-alpha.1");
+
+        // now we are ready to start with the preparation of the 1.0.0 release
+        CreateAndCheckoutBranch("release/1.0.0");
+
+        // ✅ succeeds as expected
+        GetCurrentSemVer().ShouldBe("1.0.0-beta.1");
+
+        // make another commit on release/1.0.0 to prepare the actual beta1 release
+        MakeACommit();
+
+        // ✅ succeeds as expected
+        GetCurrentSemVer().ShouldBe("1.0.0-beta.1");
+
+        // now we makes changes on develop that may or may not end up in the 1.0.0 release
+        CheckoutBranch("develop");
+        MakeACommit();
+
+        // ❌ fails! actual: "1.1.0-alpha.1"
+        // We have not released 1.0.0, not even a beta, so why increment to 1.1.0?
+        // Even though this surprising, it might actually be OK,
+        // at least the version is incremented, which is needed for the CI nuget feed
+        GetCurrentSemVer().ShouldBe("1.0.0-alpha.2");
+
+        // now we do the actual release of beta 1
+        CheckoutBranch("release/1.0.0");
+        ApplyTag("1.0.0-beta1");
+
+        // ✅ succeeds as expected
+        GetCurrentSemVer().ShouldBe("1.0.0-beta.1");
+
+        // continue with more work on develop that may or may not end up in the 1.0.0 release
+        CheckoutBranch("develop");
+        MakeACommit();
+
+        // ❌ fails! actual: "1.1.0-alpha.2"
+        // We still have not finally released 1.0.0 yet, only a beta, so why increment to 1.1.0?
+        // Even though this surprising, it might actually be OK,
+        // at least the version is incremented, which is needed for the CI nuget feed
+        GetCurrentSemVer().ShouldBe("1.0.0-alpha.3");
+
+        // now we decide that the new changes on develop should be part of the beta 2 release
+        // se we merge it into release/1.0.0 with --no-ff because it is a protected branch
+        // but we don't do the release of beta 2 jus yet
+        CheckoutBranch("release/1.0.0");
+        MergeWithNoFF("develop");
+
+        // ✅ succeeds as expected
+        GetCurrentSemVer().ShouldBe("1.0.0-beta.2");
+
+        CheckoutBranch("develop");
+
+        // ❌ fails! actual "1.0.0-alpha.3"
+        // This is now really a problem. Why did it decrement the minor version?
+        // All subsequent changes on develop will now a lower version that previously
+        // and the nuget packages end up on the CI feed with a lower version
+        // so users of that feed would need to _downgrade_ to get a _newer_ version.
+        GetCurrentSemVer().ShouldBe("1.1.0-alpha.2");
+    }
+
+    private static void MakeACommit() => _fixture.Repository.MakeACommit();
+
+    private void CheckoutBranch(string branchName) => Commands.Checkout(_fixture.Repository, _fixture.Repository.Branches[branchName]);
+
+    private void CreateAndCheckoutBranch(string branchName) => Commands.Checkout(_fixture.Repository, _fixture.Repository.CreateBranch(branchName));
+
+    private string GetCurrentSemVer() => _fixture.GetVersion(_config).SemVer;
+
+    private void ApplyTag(string tag) => _fixture.Repository.ApplyTag(tag);
+
+    private void MergeWithNoFF(string sourceBranch) => _fixture.Repository.MergeNoFF(sourceBranch);
+}

From e78e9d25e0dc2791c693a50b7021b283ed77a21f Mon Sep 17 00:00:00 2001
From: bitbonk <bitbonk@msn.com>
Date: Sun, 11 Sep 2022 11:56:17 +0200
Subject: [PATCH 3/4] Add full release workflow demo with workaround

---
 .../ReleaseWorkflowDemoWithWorkaround.cs      | 141 ++++++++++++++++++
 1 file changed, 141 insertions(+)
 create mode 100644 src/GitVersion.Core.Tests/IntegrationTests/ReleaseWorkflowDemoWithWorkaround.cs

diff --git a/src/GitVersion.Core.Tests/IntegrationTests/ReleaseWorkflowDemoWithWorkaround.cs b/src/GitVersion.Core.Tests/IntegrationTests/ReleaseWorkflowDemoWithWorkaround.cs
new file mode 100644
index 0000000000..6db72bec81
--- /dev/null
+++ b/src/GitVersion.Core.Tests/IntegrationTests/ReleaseWorkflowDemoWithWorkaround.cs
@@ -0,0 +1,141 @@
+using GitTools.Testing;
+using GitVersion.Model.Configuration;
+using LibGit2Sharp;
+using NUnit.Framework;
+using Shouldly;
+
+namespace GitVersion.Core.Tests.IntegrationTests;
+
+public class ReleaseWorkflowDemoWithWorkaround : IDisposable
+{
+    private readonly EmptyRepositoryFixture _fixture = new();
+
+    private readonly Config _config = new()
+    {
+        // ❓ In my GitVersion.yml I actually have set the version to "1.0"
+        // but that will cause an exception when I do it here in the tests
+        NextVersion = "1.0.0"
+    };
+
+    public void Dispose() => _fixture.Dispose();
+
+    [Test]
+    public void Demo()
+    {
+        // create main and develop branches
+        // develop is one commits ahead of main
+        MakeACommit("Commit 1 (made on main)");
+        CreateAndCheckoutBranch("develop");
+        MakeACommit("Commit 2 (made on develop)");
+
+        // ✅ succeeds as expected
+        GetCurrentSemVer().ShouldBe("1.0.0-alpha.1");
+
+        // now we are ready to start with the preparation of the 1.0.0 release
+        CreateAndCheckoutBranch("release/1.0.0");
+
+        // ✅ succeeds as expected
+        GetCurrentSemVer().ShouldBe("1.0.0-beta.1");
+        GetCurrentSemVer("develop").ShouldBe("1.0.0-alpha.1");
+
+        // make another commit on release/1.0.0 to prepare the actual beta1 release
+        MakeACommit("Commit 3 (made on release/1.0.0)");
+
+        // ✅ succeeds as expected
+        GetCurrentSemVer().ShouldBe("1.0.0-beta.1");
+        GetCurrentSemVer("develop").ShouldBe("1.0.0-alpha.1");
+
+        // now we makes changes on develop that may or may not end up in the 1.0.0 release
+        CheckoutBranch("develop");
+        MakeACommit("Commit 4 (made on develop)");
+
+        // ✅ succeeds as expected
+        GetCurrentSemVer().ShouldBe("1.1.0-alpha.1");
+        GetCurrentSemVer("release/1.0.0").ShouldBe("1.0.0-beta.1");
+
+        // now we do the actual release of beta 1
+        CheckoutBranch("release/1.0.0");
+        ApplyTag("1.0.0-beta1");
+
+        // ✅ succeeds as expected
+        GetCurrentSemVer().ShouldBe("1.0.0-beta.1");
+        GetCurrentSemVer("develop").ShouldBe("1.1.0-alpha.1");
+
+        // continue with more work on develop that may or may not end up in the 1.0.0 release
+        CheckoutBranch("develop");
+        MakeACommit("Commit 5 (made on develop)");
+
+        // ✅ succeeds as expected
+        GetCurrentSemVer().ShouldBe("1.1.0-alpha.2");
+        GetCurrentSemVer("release/1.0.0").ShouldBe("1.0.0-beta.1");
+
+        // now we decide that Commit 5 made on develop should be part of the beta 2 release
+        // se we cherry pick it
+        CheckoutBranch("release/1.0.0");
+        CherryPickLatestCommitFromBranch("develop");
+
+        // ✅ succeeds as expected
+        GetCurrentSemVer().ShouldBe("1.0.0-beta.2");
+        GetCurrentSemVer("develop").ShouldBe("1.1.0-alpha.2");
+
+        // now we do an important bugfix that we found while preparing beta 2
+        MakeACommit("Commit 6 (made on release/1.0.0)");
+
+        // ✅ succeeds as expected
+        GetCurrentSemVer().ShouldBe("1.0.0-beta.2");
+        GetCurrentSemVer("develop").ShouldBe("1.1.0-alpha.2");
+
+        // we want everything (Commit 3 and 6) that we made only on release/1.0.0 be in develop
+        CheckoutBranch("develop");
+        MergeWithNoFF("release/1.0.0");
+
+        // ✅ succeeds as expected
+        GetCurrentSemVer().ShouldBe("1.1.0-alpha.6");
+        GetCurrentSemVer("release/1.0.0").ShouldBe("1.0.0-beta.2");
+
+        // now we do the actual 1.0.0 release
+        CheckoutBranch("release/1.0.0");
+        MakeACommit("Commit 7 (made on release/1.0.0)");
+        CheckoutBranch("main");
+        MergeWithNoFF("release/1.0.0");
+        ApplyTag("1.0.0");
+        CheckoutBranch("develop");
+        MergeWithNoFF("release/1.0.0");
+        DeleteBranch("relesase/1.0.0");
+
+        // ✅ succeeds as expected
+        GetCurrentSemVer().ShouldBe("1.1.0-alpha.8");
+        GetCurrentSemVer("main").ShouldBe("1.0.0");
+
+
+    }
+
+    private void DeleteBranch(string branch) => _fixture.Repository.Branches.Remove(branch);
+
+    private void MakeACommit(string? message = null) => _fixture.Repository.MakeACommit(message);
+
+    private void CheckoutBranch(string branchName) => Commands.Checkout(_fixture.Repository, _fixture.Repository.Branches[branchName]);
+
+    private void CreateAndCheckoutBranch(string branchName) => Commands.Checkout(_fixture.Repository, _fixture.Repository.CreateBranch(branchName));
+
+    private string GetCurrentSemVer(string? branch = null)
+    {
+        if (branch == null)
+        {
+            return _fixture.GetVersion(_config).SemVer;
+        }
+
+        if (_fixture.Repository.Branches.All(b => b.FriendlyName != branch))
+        {
+            throw new InvalidOperationException($"Branch {branch} does not exist");
+        }
+
+        return _fixture.GetVersion(_config, branch: branch).SemVer;
+    }
+
+    private void ApplyTag(string tag) => _fixture.Repository.ApplyTag(tag);
+
+    private void MergeWithNoFF(string sourceBranch) => _fixture.Repository.MergeNoFF(sourceBranch);
+
+    private void CherryPickLatestCommitFromBranch(string sourceBranch) => this._fixture.Repository.CherryPick(this._fixture.Repository.Branches[sourceBranch].Commits.First(), Generate.SignatureNow());
+}

From b8192fb58719b366dc3de3dd1dc39d5b4ea54903 Mon Sep 17 00:00:00 2001
From: bitbonk <bitbonk@msn.com>
Date: Sun, 11 Sep 2022 17:08:31 +0200
Subject: [PATCH 4/4] Do some more cleanup

---
 ... => FullReleaseGitFlowWithCherryPickWorkaround.cs} | 11 +++++++++--
 ...moScenario.cs => GitFlowScenarioFromDiscussion.cs} | 10 ++++++++--
 ...easeWorkflowDemo.cs => ReducedGitFlowWithMerge.cs} |  9 +++++++--
 3 files changed, 24 insertions(+), 6 deletions(-)
 rename src/GitVersion.Core.Tests/IntegrationTests/{ReleaseWorkflowDemoWithWorkaround.cs => FullReleaseGitFlowWithCherryPickWorkaround.cs} (89%)
 rename src/GitVersion.Core.Tests/IntegrationTests/{VersioningDemoScenario.cs => GitFlowScenarioFromDiscussion.cs} (90%)
 rename src/GitVersion.Core.Tests/IntegrationTests/{ReducedReleaseWorkflowDemo.cs => ReducedGitFlowWithMerge.cs} (91%)

diff --git a/src/GitVersion.Core.Tests/IntegrationTests/ReleaseWorkflowDemoWithWorkaround.cs b/src/GitVersion.Core.Tests/IntegrationTests/FullReleaseGitFlowWithCherryPickWorkaround.cs
similarity index 89%
rename from src/GitVersion.Core.Tests/IntegrationTests/ReleaseWorkflowDemoWithWorkaround.cs
rename to src/GitVersion.Core.Tests/IntegrationTests/FullReleaseGitFlowWithCherryPickWorkaround.cs
index 6db72bec81..20b76f3b5c 100644
--- a/src/GitVersion.Core.Tests/IntegrationTests/ReleaseWorkflowDemoWithWorkaround.cs
+++ b/src/GitVersion.Core.Tests/IntegrationTests/FullReleaseGitFlowWithCherryPickWorkaround.cs
@@ -6,7 +6,14 @@
 
 namespace GitVersion.Core.Tests.IntegrationTests;
 
-public class ReleaseWorkflowDemoWithWorkaround : IDisposable
+/// <summary>
+///     This demonstrates a full cycle to the release of 1.0.0 with the git flow workflow.
+///     Since merging stuff to release/* from develop has a bug then decrements versions on develop
+///     (as shown in the failing test <see cref="ReducedReleaseWorkflowDemo"/>),
+///     we use cherry picking instead of merging to get stuff from develop to release/*.
+///     For simplicity, we ignore the fact that the develop branch is usually updated via feature/* branches.
+/// </summary>
+public class FullReleaseGitFlowWithCherryPickWorkaround : IDisposable
 {
     private readonly EmptyRepositoryFixture _fixture = new();
 
@@ -20,7 +27,7 @@ public class ReleaseWorkflowDemoWithWorkaround : IDisposable
     public void Dispose() => _fixture.Dispose();
 
     [Test]
-    public void Demo()
+    public void Demonstrate()
     {
         // create main and develop branches
         // develop is one commits ahead of main
diff --git a/src/GitVersion.Core.Tests/IntegrationTests/VersioningDemoScenario.cs b/src/GitVersion.Core.Tests/IntegrationTests/GitFlowScenarioFromDiscussion.cs
similarity index 90%
rename from src/GitVersion.Core.Tests/IntegrationTests/VersioningDemoScenario.cs
rename to src/GitVersion.Core.Tests/IntegrationTests/GitFlowScenarioFromDiscussion.cs
index 4bea88d09c..af8a3696aa 100644
--- a/src/GitVersion.Core.Tests/IntegrationTests/VersioningDemoScenario.cs
+++ b/src/GitVersion.Core.Tests/IntegrationTests/GitFlowScenarioFromDiscussion.cs
@@ -7,10 +7,16 @@
 
 namespace GitVersion.Core.Tests.IntegrationTests;
 
-public class VersioningDemoScenario
+/// <summary>
+///     This demonstrates exactly the git flow described in https://github.com/GitTools/GitVersion/discussions/3177
+///     The assertions expect that the version on develop always increments on develop.
+///     The eventually fail (at the end of this test) after we merged develop -> release/1.0.0.
+///     For simplicity, we ignore the fact that the develop branch is usually updated via feature/* branches.
+/// </summary>
+public class GitFlowScenarioFromDiscussion
 {
     [Test]
-    public void ReleaseAndDevelopProblemDemo()
+    public void Demonstrate()
     {
         var configuration = new Config
         {
diff --git a/src/GitVersion.Core.Tests/IntegrationTests/ReducedReleaseWorkflowDemo.cs b/src/GitVersion.Core.Tests/IntegrationTests/ReducedGitFlowWithMerge.cs
similarity index 91%
rename from src/GitVersion.Core.Tests/IntegrationTests/ReducedReleaseWorkflowDemo.cs
rename to src/GitVersion.Core.Tests/IntegrationTests/ReducedGitFlowWithMerge.cs
index 312b46d245..dd9e14612d 100644
--- a/src/GitVersion.Core.Tests/IntegrationTests/ReducedReleaseWorkflowDemo.cs
+++ b/src/GitVersion.Core.Tests/IntegrationTests/ReducedGitFlowWithMerge.cs
@@ -6,7 +6,12 @@
 
 namespace GitVersion.Core.Tests.IntegrationTests;
 
-public class ReducedReleaseWorkflowDemo : IDisposable
+/// <summary>
+///     This demonstrates a bug that decrements the version on develop when we merge stuff from develop to release/*
+///     in the default git flow workflow.
+///     For simplicity, we ignore the fact that the develop branch is usually updated via feature/* branches.
+/// </summary>
+public class ReducedGitFlowWithMerge : IDisposable
 {
     private static readonly EmptyRepositoryFixture _fixture = new();
 
@@ -20,7 +25,7 @@ public class ReducedReleaseWorkflowDemo : IDisposable
     public void Dispose() => _fixture.Dispose();
 
     [Test]
-    public void Demo()
+    public void Demonstrate()
     {
         // create main and develop branches
         // develop is one commits ahead of main