From 024d671b2d60a29880033bc947462714a97b23b0 Mon Sep 17 00:00:00 2001 From: Morax Date: Fri, 21 Aug 2026 18:37:27 +0200 Subject: [PATCH 01/24] feat: resolve increments from merged branches --- ...AlignGitFlowWithMainlineVersionStrategy.cs | 168 +++++------ ...gnGitHubFlowWithMainlineVersionStrategy.cs | 168 +++++------ .../FeatureBranchScenarios.cs | 4 +- ...PreventIncrementOfMergedBranchScenarios.cs | 279 ++++++++++++++++++ .../IntegrationTests/PullRequestScenarios.cs | 2 +- .../VersionInMergedBranchNameScenarios.cs | 8 +- .../IncrementStrategyFinder.cs | 268 ++++++++++++++++- 7 files changed, 719 insertions(+), 178 deletions(-) create mode 100644 src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs diff --git a/src/GitVersion.Core.Tests/IntegrationTests/AlignGitFlowWithMainlineVersionStrategy.cs b/src/GitVersion.Core.Tests/IntegrationTests/AlignGitFlowWithMainlineVersionStrategy.cs index b0b64ed02a..6d3954f0e8 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/AlignGitFlowWithMainlineVersionStrategy.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/AlignGitFlowWithMainlineVersionStrategy.cs @@ -158,7 +158,7 @@ public void EnsureFeatureWithIncrementInheritOnMainAndNoneOnFeatureBranch(bool u else { // ❔ expected: "2.0.0-2+4" - fixture.AssertFullSemver("3.0.0-1+4", configuration); + fixture.AssertFullSemver("2.0.0-1+4", configuration); } } @@ -237,8 +237,8 @@ public void EnsureFeatureWithIncrementInheritOnMainAndPatchOnFeatureBranch(bool } else { - // ❔ expected: "2.0.1-1+4" - fixture.AssertFullSemver("3.0.0-1+4", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("2.0.1-1+4", configuration); } } @@ -317,8 +317,8 @@ public void EnsureFeatureWithIncrementInheritOnMainAndMinorOnFeatureBranch(bool } else { - // ❔ expected: "2.1.0-1+4" - fixture.AssertFullSemver("3.0.0-1+4", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("2.1.0-1+4", configuration); } } @@ -868,7 +868,7 @@ public void EnsureFeatureWithIncrementPatchOnMainAndNoneOnFeatureBranch(bool use else { // ❔ expected: "0.0.2-2+4" - fixture.AssertFullSemver("0.0.3-1+4", configuration); + fixture.AssertFullSemver("0.0.2-1+4", configuration); } } @@ -1236,7 +1236,7 @@ public void EnsureFeatureWithIncrementMinorOnMainAndNoneOnFeatureBranch(bool use else { // ❔ expected: "0.2.0-2+4" - fixture.AssertFullSemver("0.3.0-1+4", configuration); + fixture.AssertFullSemver("0.2.0-1+4", configuration); } } @@ -1315,8 +1315,8 @@ public void EnsureFeatureWithIncrementMinorOnMainAndPatchOnFeatureBranch(bool us } else { - // ❔ expected: "0.2.1-1+4" - fixture.AssertFullSemver("0.3.0-1+4", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("0.2.1-1+4", configuration); } } @@ -1612,7 +1612,7 @@ public void EnsureFeatureWithIncrementMajorOnMainAndNoneOnFeatureBranch(bool use else { // ❔ expected: "2.0.0-2+4" - fixture.AssertFullSemver("3.0.0-1+4", configuration); + fixture.AssertFullSemver("2.0.0-1+4", configuration); } } @@ -1692,7 +1692,7 @@ public void EnsureFeatureWithIncrementMajorOnMainAndPatchOnFeatureBranch(bool us else { // ❔ expected: "2.0.1-foo.2+3" - fixture.AssertFullSemver("3.0.0-1+4", configuration); + fixture.AssertFullSemver("2.0.1-1+4", configuration); } } @@ -1771,8 +1771,8 @@ public void EnsureFeatureWithIncrementMajorOnMainAndMinorOnFeatureBranch(bool us } else { - // ❔ expected: "2.1.0-1+4" - fixture.AssertFullSemver("3.0.0-1+4", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("2.1.0-1+4", configuration); } } @@ -2018,7 +2018,7 @@ public void EnsureMergeMainToFeatureWithIncrementInheritOnMainAndNoneOnFeatureBr else { // ❔ expected: "2.0.0-2+6" - fixture.AssertFullSemver("3.0.0-1+6", configuration); + fixture.AssertFullSemver("2.0.0-1+6", configuration); } } @@ -2108,8 +2108,8 @@ public void EnsureMergeMainToFeatureWithIncrementInheritOnMainAndPatchOnFeatureB } else { - // ❔ expected: "2.0.1-1+6" - fixture.AssertFullSemver("3.0.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("2.0.1-1+6", configuration); } } @@ -2199,8 +2199,8 @@ public void EnsureMergeMainToFeatureWithIncrementInheritOnMainAndMinorOnFeatureB } else { - // ❔ expected: "2.1.0-1+6" - fixture.AssertFullSemver("3.0.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("2.1.0-1+6", configuration); } } @@ -2944,7 +2944,7 @@ public void EnsureMergeMainToFeatureWithIncrementPatchOnMainAndNoneOnFeatureBran else { // ❔ expected: "0.0.2-2+6" - fixture.AssertFullSemver("0.0.3-1+6", configuration); + fixture.AssertFullSemver("0.0.2-1+6", configuration); } } @@ -3366,8 +3366,8 @@ public void EnsureMergeMainToFeatureWithIncrementMinorOnMainAndNoneOnFeatureBran } else { - // ❔ expected: "0.2.0-1+6" - fixture.AssertFullSemver("0.3.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("0.2.0-1+6", configuration); } } @@ -3457,8 +3457,8 @@ public void EnsureMergeMainToFeatureWithIncrementMinorOnMainAndPatchOnFeatureBra } else { - // ❔ expected: "0.2.1-1+6" - fixture.AssertFullSemver("0.3.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("0.2.1-1+6", configuration); } } @@ -3798,7 +3798,7 @@ public void EnsureMergeMainToFeatureWithIncrementMajorOnMainAndNoneOnFeatureBran else { // ❔ expected: "2.0.0-2+6" - fixture.AssertFullSemver("3.0.0-1+6", configuration); + fixture.AssertFullSemver("2.0.0-1+6", configuration); } } @@ -3888,8 +3888,8 @@ public void EnsureMergeMainToFeatureWithIncrementMajorOnMainAndPatchOnFeatureBra } else { - // ❔ expected: "2.0.1-1+6" - fixture.AssertFullSemver("3.0.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("2.0.1-1+6", configuration); } } @@ -3979,8 +3979,8 @@ public void EnsureMergeMainToFeatureWithIncrementMajorOnMainAndMinorOnFeatureBra } else { - // ❔ expected: "2.1.0-1+6" - fixture.AssertFullSemver("3.0.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("2.1.0-1+6", configuration); } } @@ -4190,8 +4190,8 @@ public void EnsurePullRequestWithIncrementInheritOnMainAndNoneOnFeatureBranch(bo } else { - // ❔ expected: "1.0.0-1+2" - fixture.AssertFullSemver("2.0.0-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.0-1+2", configuration); } } @@ -4262,8 +4262,8 @@ public void EnsurePullRequestWithIncrementInheritOnMainAndPatchOnFeatureBranch(b } else { - // ❔ expected: "1.0.1-1+2" - fixture.AssertFullSemver("2.0.0-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.1-1+2", configuration); } } @@ -4334,8 +4334,8 @@ public void EnsurePullRequestWithIncrementInheritOnMainAndMinorOnFeatureBranch(b } else { - // ❔ expected: "1.1.0-1+2" - fixture.AssertFullSemver("2.0.0-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.1.0-1+2", configuration); } } @@ -4582,8 +4582,8 @@ public void EnsurePullRequestWithIncrementNoneOnMainAndPatchOnFeatureBranch(bool } else { - // ❔ expected: "0.0.1-1+2" - fixture.AssertFullSemver("0.0.0-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("0.0.1-1+2", configuration); } } @@ -4646,8 +4646,8 @@ public void EnsurePullRequestWithIncrementNoneOnMainAndMinorOnFeatureBranch(bool } else { - // ❔ expected: "0.1.0-1+2" - fixture.AssertFullSemver("0.0.0-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("0.1.0-1+2", configuration); } } @@ -4710,8 +4710,8 @@ public void EnsurePullRequestWithIncrementNoneOnMainAndMajorOnFeatureBranch(bool } else { - // ❔ expected: "1.0.0-1+2" - fixture.AssertFullSemver("0.0.0-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.0-1+2", configuration); } } @@ -4839,7 +4839,7 @@ public void EnsurePullRequestWithIncrementPatchOnMainAndNoneOnFeatureBranch(bool else { // ❔ expected: "0.0.1-2+2" - fixture.AssertFullSemver("0.0.2-1+2", configuration); + fixture.AssertFullSemver("0.0.1-1+2", configuration); } } @@ -4958,8 +4958,8 @@ public void EnsurePullRequestWithIncrementPatchOnMainAndMinorOnFeatureBranch(boo } else { - // ❔ expected: "0.1.0-1+2" - fixture.AssertFullSemver("0.0.2-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("0.1.0-1+2", configuration); } } @@ -5022,8 +5022,8 @@ public void EnsurePullRequestWithIncrementPatchOnMainAndMajorOnFeatureBranch(boo } else { - // ❔ expected: "1.0.0-1+2" - fixture.AssertFullSemver("0.0.2-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.0-1+2", configuration); } } @@ -5150,8 +5150,8 @@ public void EnsurePullRequestWithIncrementMinorOnMainAndNoneOnFeatureBranch(bool } else { - // ❔ expected: "0.1.0-1+2" - fixture.AssertFullSemver("0.2.0-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("0.1.0-1+2", configuration); } } @@ -5222,8 +5222,8 @@ public void EnsurePullRequestWithIncrementMinorOnMainAndPatchOnFeatureBranch(boo } else { - // ❔ expected: "0.1.1-1+2" - fixture.AssertFullSemver("0.2.0-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("0.1.1-1+2", configuration); } } @@ -5342,8 +5342,8 @@ public void EnsurePullRequestWithIncrementMinorOnMainAndMajorOnFeatureBranch(boo } else { - // ❔ expected: "1.0.0-1+2" - fixture.AssertFullSemver("0.2.0-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.0-1+2", configuration); } } @@ -5470,8 +5470,8 @@ public void EnsurePullRequestWithIncrementMajorOnMainAndNoneOnFeatureBranch(bool } else { - // ❔ expected: "1.0.0-1+2" - fixture.AssertFullSemver("2.0.0-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.0-1+2", configuration); } } @@ -5542,8 +5542,8 @@ public void EnsurePullRequestWithIncrementMajorOnMainAndPatchOnFeatureBranch(boo } else { - // ❔ expected: "1.0.1-1+2" - fixture.AssertFullSemver("2.0.0-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.1-1+2", configuration); } } @@ -5614,8 +5614,8 @@ public void EnsurePullRequestWithIncrementMajorOnMainAndMinorOnFeatureBranch(boo } else { - // ❔ expected: "1.1.0-1+2" - fixture.AssertFullSemver("2.0.0-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.1.0-1+2", configuration); } } @@ -5979,7 +5979,7 @@ public void EnsureReleaseBranchWithIncrementInheritOnMainAndNoneOnReleaseBranch( else { // ❔ expected: "2.0.0-2+6" - fixture.AssertFullSemver("3.0.0-1+6", configuration); + fixture.AssertFullSemver("2.0.0-1+6", configuration); } if (!useMainline) @@ -6090,8 +6090,8 @@ public void EnsureReleaseBranchWithIncrementInheritOnMainAndPatchOnReleaseBranch } else { - // ❔ expected: "2.0.1-1+6" - fixture.AssertFullSemver("3.0.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("2.0.1-1+6", configuration); } if (!useMainline) @@ -6204,8 +6204,8 @@ public void EnsureReleaseBranchWithIncrementInheritOnMainAndMinorOnReleaseBranch } else { - // ❔ expected: "2.1.0-1+6" - fixture.AssertFullSemver("3.0.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("2.1.0-1+6", configuration); } if (!useMainline) @@ -7313,7 +7313,7 @@ public void EnsureReleaseBranchWithIncrementPatchOnMainAndNoneOnReleaseBranch(bo else { // ❔ expected: "0.0.2-2+6" - fixture.AssertFullSemver("0.0.3-1+6", configuration); + fixture.AssertFullSemver("0.0.2-1+6", configuration); } if (!useMainline) @@ -7919,7 +7919,7 @@ public void EnsureReleaseBranchWithIncrementMinorOnMainAndNoneOnReleaseBranch(bo else { // ❔ expected: "0.2.0-2+6" - fixture.AssertFullSemver("0.3.0-1+6", configuration); + fixture.AssertFullSemver("0.2.0-1+6", configuration); } if (!useMainline) @@ -8026,8 +8026,8 @@ public void EnsureReleaseBranchWithIncrementMinorOnMainAndPatchOnReleaseBranch(b } else { - // ❔ expected: "0.2.1-1+6" - fixture.AssertFullSemver("0.3.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("0.2.1-1+6", configuration); } if (!useMainline) @@ -8545,7 +8545,7 @@ public void EnsureReleaseBranchWithIncrementMajorOnMainAndNoneOnReleaseBranch(bo else { // ❔ expected: "2.0.0-2+6" - fixture.AssertFullSemver("3.0.0-1+6", configuration); + fixture.AssertFullSemver("2.0.0-1+6", configuration); } if (!useMainline) @@ -8654,8 +8654,8 @@ public void EnsureReleaseBranchWithIncrementMajorOnMainAndPatchOnReleaseBranch(b } else { - // ❔ expected: "2.0.1-1+6" - fixture.AssertFullSemver("3.0.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("2.0.1-1+6", configuration); } if (!useMainline) @@ -8766,8 +8766,8 @@ public void EnsureReleaseBranchWithIncrementMajorOnMainAndMinorOnReleaseBranch(b } else { - // ❔ expected: "2.1.0-1+6" - fixture.AssertFullSemver("3.0.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("2.1.0-1+6", configuration); } if (!useMainline) @@ -9213,7 +9213,7 @@ public void EnsureReleaseAndFeatureBranchWithIncrementInheritOnMainAndNoneOnRele else { // ❔ expected: "1.0.0-2+6" - fixture.AssertFullSemver("2.0.0-1+6", configuration); + fixture.AssertFullSemver("1.0.0-1+6", configuration); } if (!useMainline) @@ -9333,8 +9333,8 @@ public void EnsureReleaseAndFeatureBranchWithIncrementInheritOnMainAndPatchOnRel } else { - // ❔ expected: "1.0.1-1+6" - fixture.AssertFullSemver("2.0.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.1-1+6", configuration); } if (!useMainline) @@ -9458,8 +9458,8 @@ public void EnsureReleaseAndFeatureBranchWithIncrementInheritOnMainAndMinorOnRel } else { - // ❔ expected: "1.1.0-1+6" - fixture.AssertFullSemver("2.0.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.1.0-1+6", configuration); } if (!useMainline) @@ -10569,7 +10569,7 @@ public void EnsureReleaseAndFeatureBranchWithIncrementPatchOnMainAndNoneOnReleas else { // ❔ expected: "0.0.1-2+6" - fixture.AssertFullSemver("0.0.2-1+6", configuration); + fixture.AssertFullSemver("0.0.1-1+6", configuration); } if (!useMainline) @@ -11206,7 +11206,7 @@ public void EnsureReleaseAndFeatureBranchWithIncrementMinorOnMainAndNoneOnReleas else { // ❔ not expected - fixture.AssertFullSemver("0.2.0-1+6", configuration); + fixture.AssertFullSemver("0.1.0-1+6", configuration); } if (!useMainline) @@ -11321,7 +11321,7 @@ public void EnsureReleaseAndFeatureBranchWithIncrementMinorOnMainAndPatchOnRelea else { // ❔ not expected - fixture.AssertFullSemver("0.2.0-1+6", configuration); + fixture.AssertFullSemver("0.1.1-1+6", configuration); } if (!useMainline) @@ -11875,7 +11875,7 @@ public void EnsureReleaseAndFeatureBranchWithIncrementMajorOnMainAndNoneOnReleas else { // ❔ expected: "1.0.0-2+6" - fixture.AssertFullSemver("2.0.0-1+6", configuration); + fixture.AssertFullSemver("1.0.0-1+6", configuration); } if (!useMainline) @@ -11995,8 +11995,8 @@ public void EnsureReleaseAndFeatureBranchWithIncrementMajorOnMainAndPatchOnRelea } else { - // ❔ expected: "1.0.1-1+6" - fixture.AssertFullSemver("2.0.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.1-1+6", configuration); } if (!useMainline) @@ -12120,8 +12120,8 @@ public void EnsureReleaseAndFeatureBranchWithIncrementMajorOnMainAndMinorOnRelea } else { - // ❔ expected: "1.1.0-1+6" - fixture.AssertFullSemver("2.0.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.1.0-1+6", configuration); } if (!useMainline) diff --git a/src/GitVersion.Core.Tests/IntegrationTests/AlignGitHubFlowWithMainlineVersionStrategy.cs b/src/GitVersion.Core.Tests/IntegrationTests/AlignGitHubFlowWithMainlineVersionStrategy.cs index 33738c6136..6df3907dec 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/AlignGitHubFlowWithMainlineVersionStrategy.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/AlignGitHubFlowWithMainlineVersionStrategy.cs @@ -158,7 +158,7 @@ public void EnsureFeatureWithIncrementInheritOnMainAndNoneOnFeatureBranch(bool u else { // ❔ expected: "2.0.0-2+4" - fixture.AssertFullSemver("3.0.0-1+4", configuration); + fixture.AssertFullSemver("2.0.0-1+4", configuration); } } @@ -237,8 +237,8 @@ public void EnsureFeatureWithIncrementInheritOnMainAndPatchOnFeatureBranch(bool } else { - // ❔ expected: "2.0.1-1+4" - fixture.AssertFullSemver("3.0.0-1+4", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("2.0.1-1+4", configuration); } } @@ -317,8 +317,8 @@ public void EnsureFeatureWithIncrementInheritOnMainAndMinorOnFeatureBranch(bool } else { - // ❔ expected: "2.1.0-1+4" - fixture.AssertFullSemver("3.0.0-1+4", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("2.1.0-1+4", configuration); } } @@ -868,7 +868,7 @@ public void EnsureFeatureWithIncrementPatchOnMainAndNoneOnFeatureBranch(bool use else { // ❔ expected: "0.0.2-2+4" - fixture.AssertFullSemver("0.0.3-1+4", configuration); + fixture.AssertFullSemver("0.0.2-1+4", configuration); } } @@ -1236,7 +1236,7 @@ public void EnsureFeatureWithIncrementMinorOnMainAndNoneOnFeatureBranch(bool use else { // ❔ expected: "0.2.0-2+4" - fixture.AssertFullSemver("0.3.0-1+4", configuration); + fixture.AssertFullSemver("0.2.0-1+4", configuration); } } @@ -1315,8 +1315,8 @@ public void EnsureFeatureWithIncrementMinorOnMainAndPatchOnFeatureBranch(bool us } else { - // ❔ expected: "0.2.1-1+4" - fixture.AssertFullSemver("0.3.0-1+4", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("0.2.1-1+4", configuration); } } @@ -1612,7 +1612,7 @@ public void EnsureFeatureWithIncrementMajorOnMainAndNoneOnFeatureBranch(bool use else { // ❔ expected: "2.0.0-2+4" - fixture.AssertFullSemver("3.0.0-1+4", configuration); + fixture.AssertFullSemver("2.0.0-1+4", configuration); } } @@ -1692,7 +1692,7 @@ public void EnsureFeatureWithIncrementMajorOnMainAndPatchOnFeatureBranch(bool us else { // ❔ expected: "2.0.1-foo.2+3" - fixture.AssertFullSemver("3.0.0-1+4", configuration); + fixture.AssertFullSemver("2.0.1-1+4", configuration); } } @@ -1771,8 +1771,8 @@ public void EnsureFeatureWithIncrementMajorOnMainAndMinorOnFeatureBranch(bool us } else { - // ❔ expected: "2.1.0-1+4" - fixture.AssertFullSemver("3.0.0-1+4", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("2.1.0-1+4", configuration); } } @@ -2018,7 +2018,7 @@ public void EnsureMergeMainToFeatureWithIncrementInheritOnMainAndNoneOnFeatureBr else { // ❔ expected: "2.0.0-2+6" - fixture.AssertFullSemver("3.0.0-1+6", configuration); + fixture.AssertFullSemver("2.0.0-1+6", configuration); } } @@ -2108,8 +2108,8 @@ public void EnsureMergeMainToFeatureWithIncrementInheritOnMainAndPatchOnFeatureB } else { - // ❔ expected: "2.0.1-1+6" - fixture.AssertFullSemver("3.0.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("2.0.1-1+6", configuration); } } @@ -2199,8 +2199,8 @@ public void EnsureMergeMainToFeatureWithIncrementInheritOnMainAndMinorOnFeatureB } else { - // ❔ expected: "2.1.0-1+6" - fixture.AssertFullSemver("3.0.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("2.1.0-1+6", configuration); } } @@ -2944,7 +2944,7 @@ public void EnsureMergeMainToFeatureWithIncrementPatchOnMainAndNoneOnFeatureBran else { // ❔ expected: "0.0.2-2+6" - fixture.AssertFullSemver("0.0.3-1+6", configuration); + fixture.AssertFullSemver("0.0.2-1+6", configuration); } } @@ -3366,8 +3366,8 @@ public void EnsureMergeMainToFeatureWithIncrementMinorOnMainAndNoneOnFeatureBran } else { - // ❔ expected: "0.2.0-1+6" - fixture.AssertFullSemver("0.3.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("0.2.0-1+6", configuration); } } @@ -3457,8 +3457,8 @@ public void EnsureMergeMainToFeatureWithIncrementMinorOnMainAndPatchOnFeatureBra } else { - // ❔ expected: "0.2.1-1+6" - fixture.AssertFullSemver("0.3.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("0.2.1-1+6", configuration); } } @@ -3798,7 +3798,7 @@ public void EnsureMergeMainToFeatureWithIncrementMajorOnMainAndNoneOnFeatureBran else { // ❔ expected: "2.0.0-2+6" - fixture.AssertFullSemver("3.0.0-1+6", configuration); + fixture.AssertFullSemver("2.0.0-1+6", configuration); } } @@ -3888,8 +3888,8 @@ public void EnsureMergeMainToFeatureWithIncrementMajorOnMainAndPatchOnFeatureBra } else { - // ❔ expected: "2.0.1-1+6" - fixture.AssertFullSemver("3.0.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("2.0.1-1+6", configuration); } } @@ -3979,8 +3979,8 @@ public void EnsureMergeMainToFeatureWithIncrementMajorOnMainAndMinorOnFeatureBra } else { - // ❔ expected: "2.1.0-1+6" - fixture.AssertFullSemver("3.0.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("2.1.0-1+6", configuration); } } @@ -4190,8 +4190,8 @@ public void EnsurePullRequestWithIncrementInheritOnMainAndNoneOnFeatureBranch(bo } else { - // ❔ expected: "1.0.0-1+2" - fixture.AssertFullSemver("2.0.0-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.0-1+2", configuration); } } @@ -4262,8 +4262,8 @@ public void EnsurePullRequestWithIncrementInheritOnMainAndPatchOnFeatureBranch(b } else { - // ❔ expected: "1.0.1-1+2" - fixture.AssertFullSemver("2.0.0-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.1-1+2", configuration); } } @@ -4334,8 +4334,8 @@ public void EnsurePullRequestWithIncrementInheritOnMainAndMinorOnFeatureBranch(b } else { - // ❔ expected: "1.1.0-1+2" - fixture.AssertFullSemver("2.0.0-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.1.0-1+2", configuration); } } @@ -4582,8 +4582,8 @@ public void EnsurePullRequestWithIncrementNoneOnMainAndPatchOnFeatureBranch(bool } else { - // ❔ expected: "0.0.1-1+2" - fixture.AssertFullSemver("0.0.0-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("0.0.1-1+2", configuration); } } @@ -4646,8 +4646,8 @@ public void EnsurePullRequestWithIncrementNoneOnMainAndMinorOnFeatureBranch(bool } else { - // ❔ expected: "0.1.0-1+2" - fixture.AssertFullSemver("0.0.0-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("0.1.0-1+2", configuration); } } @@ -4710,8 +4710,8 @@ public void EnsurePullRequestWithIncrementNoneOnMainAndMajorOnFeatureBranch(bool } else { - // ❔ expected: "1.0.0-1+2" - fixture.AssertFullSemver("0.0.0-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.0-1+2", configuration); } } @@ -4839,7 +4839,7 @@ public void EnsurePullRequestWithIncrementPatchOnMainAndNoneOnFeatureBranch(bool else { // ❔ expected: "0.0.1-2+2" - fixture.AssertFullSemver("0.0.2-1+2", configuration); + fixture.AssertFullSemver("0.0.1-1+2", configuration); } } @@ -4958,8 +4958,8 @@ public void EnsurePullRequestWithIncrementPatchOnMainAndMinorOnFeatureBranch(boo } else { - // ❔ expected: "0.1.0-1+2" - fixture.AssertFullSemver("0.0.2-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("0.1.0-1+2", configuration); } } @@ -5022,8 +5022,8 @@ public void EnsurePullRequestWithIncrementPatchOnMainAndMajorOnFeatureBranch(boo } else { - // ❔ expected: "1.0.0-1+2" - fixture.AssertFullSemver("0.0.2-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.0-1+2", configuration); } } @@ -5150,8 +5150,8 @@ public void EnsurePullRequestWithIncrementMinorOnMainAndNoneOnFeatureBranch(bool } else { - // ❔ expected: "0.1.0-1+2" - fixture.AssertFullSemver("0.2.0-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("0.1.0-1+2", configuration); } } @@ -5222,8 +5222,8 @@ public void EnsurePullRequestWithIncrementMinorOnMainAndPatchOnFeatureBranch(boo } else { - // ❔ expected: "0.1.1-1+2" - fixture.AssertFullSemver("0.2.0-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("0.1.1-1+2", configuration); } } @@ -5342,8 +5342,8 @@ public void EnsurePullRequestWithIncrementMinorOnMainAndMajorOnFeatureBranch(boo } else { - // ❔ expected: "1.0.0-1+2" - fixture.AssertFullSemver("0.2.0-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.0-1+2", configuration); } } @@ -5470,8 +5470,8 @@ public void EnsurePullRequestWithIncrementMajorOnMainAndNoneOnFeatureBranch(bool } else { - // ❔ expected: "1.0.0-1+2" - fixture.AssertFullSemver("2.0.0-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.0-1+2", configuration); } } @@ -5542,8 +5542,8 @@ public void EnsurePullRequestWithIncrementMajorOnMainAndPatchOnFeatureBranch(boo } else { - // ❔ expected: "1.0.1-1+2" - fixture.AssertFullSemver("2.0.0-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.1-1+2", configuration); } } @@ -5614,8 +5614,8 @@ public void EnsurePullRequestWithIncrementMajorOnMainAndMinorOnFeatureBranch(boo } else { - // ❔ expected: "1.1.0-1+2" - fixture.AssertFullSemver("2.0.0-1+2", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.1.0-1+2", configuration); } } @@ -5979,7 +5979,7 @@ public void EnsureReleaseBranchWithIncrementInheritOnMainAndNoneOnReleaseBranch( else { // ❔ expected: "2.0.0-2+6" - fixture.AssertFullSemver("3.0.0-1+6", configuration); + fixture.AssertFullSemver("2.0.0-1+6", configuration); } if (!useMainline) @@ -6090,8 +6090,8 @@ public void EnsureReleaseBranchWithIncrementInheritOnMainAndPatchOnReleaseBranch } else { - // ❔ expected: "2.0.1-1+6" - fixture.AssertFullSemver("3.0.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("2.0.1-1+6", configuration); } if (!useMainline) @@ -6204,8 +6204,8 @@ public void EnsureReleaseBranchWithIncrementInheritOnMainAndMinorOnReleaseBranch } else { - // ❔ expected: "2.1.0-1+6" - fixture.AssertFullSemver("3.0.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("2.1.0-1+6", configuration); } if (!useMainline) @@ -7313,7 +7313,7 @@ public void EnsureReleaseBranchWithIncrementPatchOnMainAndNoneOnReleaseBranch(bo else { // ❔ expected: "0.0.2-2+6" - fixture.AssertFullSemver("0.0.3-1+6", configuration); + fixture.AssertFullSemver("0.0.2-1+6", configuration); } if (!useMainline) @@ -7919,7 +7919,7 @@ public void EnsureReleaseBranchWithIncrementMinorOnMainAndNoneOnReleaseBranch(bo else { // ❔ expected: "0.2.0-2+6" - fixture.AssertFullSemver("0.3.0-1+6", configuration); + fixture.AssertFullSemver("0.2.0-1+6", configuration); } if (!useMainline) @@ -8026,8 +8026,8 @@ public void EnsureReleaseBranchWithIncrementMinorOnMainAndPatchOnReleaseBranch(b } else { - // ❔ expected: "0.2.1-1+6" - fixture.AssertFullSemver("0.3.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("0.2.1-1+6", configuration); } if (!useMainline) @@ -8545,7 +8545,7 @@ public void EnsureReleaseBranchWithIncrementMajorOnMainAndNoneOnReleaseBranch(bo else { // ❔ expected: "2.0.0-2+6" - fixture.AssertFullSemver("3.0.0-1+6", configuration); + fixture.AssertFullSemver("2.0.0-1+6", configuration); } if (!useMainline) @@ -8654,8 +8654,8 @@ public void EnsureReleaseBranchWithIncrementMajorOnMainAndPatchOnReleaseBranch(b } else { - // ❔ expected: "2.0.1-1+6" - fixture.AssertFullSemver("3.0.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("2.0.1-1+6", configuration); } if (!useMainline) @@ -8766,8 +8766,8 @@ public void EnsureReleaseBranchWithIncrementMajorOnMainAndMinorOnReleaseBranch(b } else { - // ❔ expected: "2.1.0-1+6" - fixture.AssertFullSemver("3.0.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("2.1.0-1+6", configuration); } if (!useMainline) @@ -9213,7 +9213,7 @@ public void EnsureReleaseAndFeatureBranchWithIncrementInheritOnMainAndNoneOnRele else { // ❔ expected: "1.0.0-2+6" - fixture.AssertFullSemver("2.0.0-1+6", configuration); + fixture.AssertFullSemver("1.0.0-1+6", configuration); } if (!useMainline) @@ -9333,8 +9333,8 @@ public void EnsureReleaseAndFeatureBranchWithIncrementInheritOnMainAndPatchOnRel } else { - // ❔ expected: "1.0.1-1+6" - fixture.AssertFullSemver("2.0.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.1-1+6", configuration); } if (!useMainline) @@ -9458,8 +9458,8 @@ public void EnsureReleaseAndFeatureBranchWithIncrementInheritOnMainAndMinorOnRel } else { - // ❔ expected: "1.1.0-1+6" - fixture.AssertFullSemver("2.0.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.1.0-1+6", configuration); } if (!useMainline) @@ -10569,7 +10569,7 @@ public void EnsureReleaseAndFeatureBranchWithIncrementPatchOnMainAndNoneOnReleas else { // ❔ expected: "0.0.1-2+6" - fixture.AssertFullSemver("0.0.2-1+6", configuration); + fixture.AssertFullSemver("0.0.1-1+6", configuration); } if (!useMainline) @@ -11206,7 +11206,7 @@ public void EnsureReleaseAndFeatureBranchWithIncrementMinorOnMainAndNoneOnReleas else { // ❔ not expected - fixture.AssertFullSemver("0.2.0-1+6", configuration); + fixture.AssertFullSemver("0.1.0-1+6", configuration); } if (!useMainline) @@ -11321,7 +11321,7 @@ public void EnsureReleaseAndFeatureBranchWithIncrementMinorOnMainAndPatchOnRelea else { // ❔ not expected - fixture.AssertFullSemver("0.2.0-1+6", configuration); + fixture.AssertFullSemver("0.1.1-1+6", configuration); } if (!useMainline) @@ -11875,7 +11875,7 @@ public void EnsureReleaseAndFeatureBranchWithIncrementMajorOnMainAndNoneOnReleas else { // ❔ expected: "1.0.0-2+6" - fixture.AssertFullSemver("2.0.0-1+6", configuration); + fixture.AssertFullSemver("1.0.0-1+6", configuration); } if (!useMainline) @@ -11995,8 +11995,8 @@ public void EnsureReleaseAndFeatureBranchWithIncrementMajorOnMainAndPatchOnRelea } else { - // ❔ expected: "1.0.1-1+6" - fixture.AssertFullSemver("2.0.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.1-1+6", configuration); } if (!useMainline) @@ -12120,8 +12120,8 @@ public void EnsureReleaseAndFeatureBranchWithIncrementMajorOnMainAndMinorOnRelea } else { - // ❔ expected: "1.1.0-1+6" - fixture.AssertFullSemver("2.0.0-1+6", configuration); + // ✅ succeeds as expected + fixture.AssertFullSemver("1.1.0-1+6", configuration); } if (!useMainline) diff --git a/src/GitVersion.Core.Tests/IntegrationTests/FeatureBranchScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/FeatureBranchScenarios.cs index 2dd69830da..63ff14d548 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/FeatureBranchScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/FeatureBranchScenarios.cs @@ -322,7 +322,7 @@ public void ShouldPickUpVersionFromMainAfterReleaseBranchMergedBack() // merge release into main fixture.Checkout(MainBranch); fixture.MergeNoFF("release/1.0.0"); - fixture.AssertFullSemver("1.0.1-2", configuration); + fixture.AssertFullSemver("1.1.0-2", configuration); // create a feature branch from main and verify the version fixture.BranchTo("feature/test"); @@ -412,7 +412,7 @@ public void ShouldPickUpVersionFromMainAfterReleaseBranchMergedBack() // merge release into main fixture.Checkout(MainBranch); fixture.MergeNoFF("release/1.0.0"); - fixture.AssertFullSemver("1.0.1-2", configuration); + fixture.AssertFullSemver("1.1.0-2", configuration); // create a misnamed feature branch (i.e. it uses the default configuration) from main and verify the version fixture.BranchTo("misnamed"); diff --git a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs new file mode 100644 index 0000000000..8fda9e3ddb --- /dev/null +++ b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs @@ -0,0 +1,279 @@ +using GitVersion.Configuration; +using GitVersion.Testing.Extensions; + +namespace GitVersion.Tests.IntegrationTests; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class PreventIncrementOfMergedBranchScenarios +{ + [TestCase(false, false, "1.0.1-2")] + [TestCase(false, true, "1.0.1-2")] + [TestCase(false, null, "1.1.0-2")] + [TestCase(true, false, "1.1.0-2")] + [TestCase(true, true, "1.0.1-2")] + [TestCase(true, null, "1.1.0-2")] + public void SelectsIncrementFromTargetAndMergedBranchConfiguration( + bool preventIncrementOfMergedBranch, + bool? preventIncrementWhenBranchMerged, + string expectedVersion) + { + var configuration = GitFlowConfigurationBuilder.New + .WithPreventIncrementWhenBranchMerged(null) + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(preventIncrementOfMergedBranch) + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Minor) + .WithPreventIncrementWhenBranchMerged(preventIncrementWhenBranchMerged) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main"); + + fixture.AssertFullSemver(expectedVersion, configuration); + } + + [Test] + public void UsesHotfixIncrementWhenHotfixIsMergedIntoMain() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Minor) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("hotfix", builder => builder + .WithIncrement(IncrementStrategy.Patch) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("hotfix/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main"); + + fixture.AssertFullSemver("1.0.1-2", configuration); + } + + [Test] + public void UsesFeatureIncrementWhenFeatureIsMergedIntoMain() + { + var configuration = GitHubFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Minor) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main"); + + fixture.AssertFullSemver("1.1.0-2", configuration); + } + + [Test] + public void RetainsMergedBranchIncrementAfterSubsequentTargetCommit() + { + var configuration = GitHubFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Minor) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main"); + fixture.MakeACommit(); + + fixture.AssertFullSemver("1.1.0-3", configuration); + } + + [Test] + public void UsesHighestIncrementFromMultipleMergedBranches() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("hotfix", builder => builder + .WithIncrement(IncrementStrategy.Patch) + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Minor) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("hotfix/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main"); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main"); + + fixture.AssertFullSemver("1.1.0-4", configuration); + } + + [TestCase(false)] + [TestCase(true)] + public void IncludesTargetIncrementForTargetCommit(bool commitAfterMerge) + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Minor) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("hotfix", builder => builder + .WithIncrement(IncrementStrategy.Patch) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + if (!commitAfterMerge) + { + fixture.MakeACommit(); + } + fixture.BranchTo("hotfix/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main"); + if (commitAfterMerge) + { + fixture.MakeACommit(); + } + + fixture.AssertFullSemver("1.1.0-3", configuration); + } + + [Test] + public void IgnoresSourceCommitMessageWhenSourceIncrementIsPrevented() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Minor) + .WithPreventIncrementWhenBranchMerged(true) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit("Breaking change +semver: major"); + fixture.MergeTo("main"); + + fixture.AssertFullSemver("1.0.1-2", configuration); + } + + [TestCase("Feature +semver: minor", "1.1.0-2")] + [TestCase("Feature =semver: patch", "1.0.1-2")] + public void UsesEffectiveSourceCommitMessageIncrement(string message, string expectedVersion) + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Major) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Patch) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit(message); + fixture.MergeTo("main"); + + fixture.AssertFullSemver(expectedVersion, configuration); + } + + [Test] + public void ResolvesInheritedIncrementFromMergedBranchSource() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("develop", builder => builder + .WithIncrement(IncrementStrategy.Minor) + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Inherit) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("develop"); + fixture.MakeACommit(); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main", removeBranchAfterMerging: true); + + fixture.AssertFullSemver("1.1.0-3", configuration); + } + + [Test] + public void ResolvesInheritedIncrementFromHistoricalMergedBranchTip() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("develop", builder => builder + .WithIncrement(IncrementStrategy.Minor) + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Inherit) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("develop"); + fixture.MakeACommit(); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main", removeBranchAfterMerging: true); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit(); + fixture.Checkout("main"); + + fixture.AssertFullSemver("1.1.0-3", configuration); + } + + [Test] + public void PrefersHistoricalSourceOverLaterAbsorbingBranch() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("develop", builder => builder + .WithIncrement(IncrementStrategy.Minor) + ).WithBranch("support", builder => builder + .WithRegularExpression("^support$") + .WithIncrement(IncrementStrategy.Major) + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Inherit) + .WithSourceBranches("develop", "support") + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.CreateBranch("support"); + fixture.BranchTo("develop"); + fixture.MakeACommit(); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main"); + fixture.Checkout("feature/foo"); + fixture.MergeTo("support", removeBranchAfterMerging: true); + fixture.Checkout("main"); + + fixture.AssertFullSemver("1.1.0-3", configuration); + } +} diff --git a/src/GitVersion.Core.Tests/IntegrationTests/PullRequestScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/PullRequestScenarios.cs index 17cba2fa5a..8aa715a36a 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/PullRequestScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/PullRequestScenarios.cs @@ -49,7 +49,7 @@ public void EnsurePullRequestWithIncrementMajorOnMainAndMinorOnFeatureBranch() fixture.MergeNoFF("feature/foo"); // ✅ succeeds as expected - fixture.AssertFullSemver("2.0.0-2", configuration); + fixture.AssertFullSemver("1.1.0-2", configuration); } [Test] diff --git a/src/GitVersion.Core.Tests/IntegrationTests/VersionInMergedBranchNameScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/VersionInMergedBranchNameScenarios.cs index 90830d7144..fc27d1c515 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/VersionInMergedBranchNameScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/VersionInMergedBranchNameScenarios.cs @@ -25,9 +25,9 @@ public void DoesNotTakeVersionFromNameOfNonReleaseBranch() fixture.AssertFullSemver("1.1.0-alpha.5"); } - [TestCase("release")] - [TestCase("hotfix")] - public void DoesNotTakeVersionFromBranchWithAccidentalVersion(string branch) + [TestCase("release", "1.1.0-2")] + [TestCase("hotfix", "1.0.1-2")] + public void DoesNotTakeVersionFromBranchWithAccidentalVersion(string branch, string expectedVersion) { using var fixture = new EmptyRepositoryFixture(); @@ -37,7 +37,7 @@ public void DoesNotTakeVersionFromBranchWithAccidentalVersion(string branch) fixture.Checkout("main"); fixture.MergeNoFF($"{branch}/downgrade-some-lib-to-3.2.1"); - fixture.AssertFullSemver("1.0.1-2"); + fixture.AssertFullSemver(expectedVersion); } [Test] diff --git a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs index 4b35dabb2d..6e757c1f28 100644 --- a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs @@ -6,16 +6,27 @@ namespace GitVersion.VersionCalculation; internal class IncrementStrategyFinder( + Lazy contextLazy, IRepositoryStore repositoryStore, - ITaggedSemanticVersionRepository taggedSemanticVersionRepository) + ITaggedSemanticVersionRepository taggedSemanticVersionRepository, + IEffectiveBranchConfigurationFinder effectiveBranchConfigurationFinder, + IEnvironment environment) : IIncrementStrategyFinder { private readonly Dictionary commitIncrementCache = []; + private readonly Dictionary<(string Commit, EffectiveConfiguration Target), VersionField[]> mergedBranchIncrementCache = []; + private readonly Dictionary<(string Branch, string? Tip), EffectiveBranchConfiguration[]> effectiveBranchConfigurationCache = []; + private readonly Dictionary> firstParentHistoryCache = []; private readonly Dictionary> headCommitsMapCache = []; private readonly Dictionary headCommitsCache = []; + private readonly Lazy contextLazy = contextLazy.NotNull(); private readonly IRepositoryStore repositoryStore = repositoryStore.NotNull(); private readonly ITaggedSemanticVersionRepository taggedSemanticVersionRepository = taggedSemanticVersionRepository.NotNull(); + private readonly IEffectiveBranchConfigurationFinder effectiveBranchConfigurationFinder = effectiveBranchConfigurationFinder.NotNull(); + private readonly IEnvironment environment = environment.NotNull(); + + private GitVersionContext Context => this.contextLazy.Value; public VersionField DetermineIncrementedField( ICommit currentCommit, ICommit? baseVersionSource, bool shouldIncrement, EffectiveConfiguration configuration, string? label) @@ -23,7 +34,31 @@ public VersionField DetermineIncrementedField( currentCommit.NotNull(); configuration.NotNull(); - var commitMessageIncrement = FindCommitMessageIncrement(configuration, baseVersionSource, currentCommit, label); + var targetIncrement = DetermineIncrementedFieldInternal( + currentCommit, baseVersionSource, shouldIncrement, configuration, label); + + if (!configuration.IsMainBranch + || !configuration.TrackMergeMessage + || Context.Configuration.GetBranchConfiguration(Context.CurrentBranch.Name).IsMainBranch != true) + { + return targetIncrement; + } + + var increments = GetIncrementsFromCommitHistory( + currentCommit, baseVersionSource, shouldIncrement, configuration, label, targetIncrement).ToArray(); + + return increments.Length == 0 + ? targetIncrement + : increments.Aggregate(VersionField.None, (result, increment) => result.Consolidate(increment)); + } + + private VersionField DetermineIncrementedFieldInternal( + ICommit currentCommit, ICommit? baseVersionSource, bool shouldIncrement, + EffectiveConfiguration configuration, string? label, + IReadOnlySet? includedCommits = null) + { + var commitMessageIncrement = FindCommitMessageIncrement( + configuration, baseVersionSource, currentCommit, label, includedCommits); var defaultIncrement = configuration.Increment.ToVersionField(); @@ -44,6 +79,227 @@ public VersionField DetermineIncrementedField( return commitMessageIncrement.Value.Increment; } + private IEnumerable GetIncrementsFromCommitHistory( + ICommit currentCommit, ICommit? baseVersionSource, bool shouldIncrement, + EffectiveConfiguration targetConfiguration, string? targetLabel, VersionField targetIncrement) + { + var configuration = Context.Configuration; + var commitLog = this.repositoryStore + .GetCommitLog(baseVersionSource, currentCommit, targetConfiguration.Ignore) + .Select(commit => commit.Sha) + .ToHashSet(); + List targetCommits = []; + Dictionary mergedBranches = []; + + for (ICommit? commit = currentCommit; commit is not null; commit = commit.Parents.FirstOrDefault()) + { + if (baseVersionSource?.Equals(commit) == true) + { + break; + } + if (!commitLog.Contains(commit.Sha)) + { + continue; + } + + targetCommits.Add(commit); + if (!commit.IsMergeCommit + || commit.Parents.Count != 2 + || !MergeMessage.TryParse(commit, configuration, out var mergeMessage) + || mergeMessage.MergedBranch is not { } mergedBranch) + { + continue; + } + + mergedBranches.Add(commit.Sha, (commit, mergedBranch)); + } + + if (mergedBranches.Count == 0) + { + yield return targetIncrement; + yield break; + } + + var targetCommitShas = targetCommits.Select(commit => commit.Sha).ToHashSet(); + targetIncrement = DetermineIncrementedFieldInternal( + currentCommit, baseVersionSource, shouldIncrement, + targetConfiguration, targetLabel, targetCommitShas); + + var mergedBranchCommits = mergedBranches.Keys.ToHashSet(); + var hasTargetContribution = targetCommits.Any(commit => !mergedBranchCommits.Contains(commit.Sha)) + || FindCommitMessageIncrement( + targetConfiguration, baseVersionSource, currentCommit, targetLabel, mergedBranchCommits) is not null; + if (hasTargetContribution) + { + yield return targetIncrement; + } + + foreach (var (commit, mergedBranch) in mergedBranches.Values) + { + var sourceBranchConfiguration = configuration.GetBranchConfiguration(mergedBranch); + var preventIncrementWhenBranchMerged = sourceBranchConfiguration.PreventIncrement.WhenBranchMerged + ?? configuration.PreventIncrement.WhenBranchMerged; + + foreach (var sourceIncrement in this.mergedBranchIncrementCache.GetOrAdd( + (commit.Sha, targetConfiguration), () => + { + var mergeBase = this.repositoryStore.FindMergeBase(commit.Parents[0], commit.Parents[1]); + + return [.. GetSourceConfigurations( + mergedBranch, commit.Parents[1], sourceBranchConfiguration, targetConfiguration) + .Select(sourceConfiguration => DetermineIncrementedFieldInternal( + currentCommit: commit.Parents[1], + baseVersionSource: mergeBase, + shouldIncrement: true, + configuration: sourceConfiguration, + label: sourceConfiguration.GetBranchSpecificLabel( + mergedBranch, null, this.environment) + ))]; + })) + { + yield return SelectIncrement( + targetConfiguration.PreventIncrementOfMergedBranch, + preventIncrementWhenBranchMerged, + targetIncrement, + sourceIncrement + ); + } + } + } + + private IEnumerable GetSourceConfigurations( + ReferenceName mergedBranch, ICommit mergedBranchTip, + IBranchConfiguration sourceBranchConfiguration, EffectiveConfiguration targetConfiguration) + { + var existingBranch = this.repositoryStore.Branches + .Where(candidate => candidate.Name.EquivalentTo(mergedBranch.WithoutOrigin) + && candidate.Tip?.Equals(mergedBranchTip) == true) + .MinBy(candidate => candidate.IsRemote); + + if (existingBranch is not null) + { + var configurations = GetEffectiveBranchConfigurations(existingBranch) + .Select(candidate => candidate.Value) + .Distinct() + .ToArray(); + if (configurations.Length != 0) + { + return configurations; + } + } + + if (sourceBranchConfiguration.Increment != IncrementStrategy.Inherit) + { + return [Context.Configuration.GetEffectiveConfiguration(mergedBranch)]; + } + + var inheritedConfigurations = FindClosestSourceBranches( + mergedBranchTip, sourceBranchConfiguration, Context.Configuration, + Context.CurrentBranch.Name, this.repositoryStore) + .SelectMany(GetEffectiveBranchConfigurations) + .Select(source => new EffectiveConfiguration( + Context.Configuration, sourceBranchConfiguration, source.Value)) + .Distinct() + .ToArray(); + + return inheritedConfigurations.Length != 0 + ? inheritedConfigurations + : [Context.Configuration.GetEffectiveConfiguration(mergedBranch, targetConfiguration)]; + } + + private EffectiveBranchConfiguration[] GetEffectiveBranchConfigurations(IBranch branch) => + this.effectiveBranchConfigurationCache.GetOrAdd( + (branch.Name.ToString(), branch.Tip?.Sha), + () => [.. this.effectiveBranchConfigurationFinder + .GetConfigurations(branch, Context.Configuration) + ]); + + private IEnumerable FindClosestSourceBranches( + ICommit mergedBranchTip, IBranchConfiguration mergedBranchConfiguration, + IGitVersionConfiguration configuration, ReferenceName currentBranch, + IRepositoryStore repositoryStore) + { + var candidates = repositoryStore.Branches + .Where(branch => IsConfiguredSourceBranch(branch, mergedBranchConfiguration, configuration) + // The current target contains the merged tip through this merge and cannot be the + // topic's source. Other configured source branches may legitimately absorb it later. + && !branch.Name.EquivalentTo(currentBranch.WithoutOrigin)) + .GroupBy(branch => branch.Name.WithoutOrigin, StringComparer.OrdinalIgnoreCase) + .Select(group => group.MinBy(branch => branch.IsRemote)) + .OfType(); + + var closestDistance = int.MaxValue; + List result = []; + foreach (var candidate in candidates) + { + if (candidate.Tip is null) + { + continue; + } + + if (GetFirstParentSourceDistance(mergedBranchTip, candidate.Tip) is not { } distance) + { + continue; + } + if (distance < closestDistance) + { + closestDistance = distance; + result.Clear(); + } + if (distance == closestDistance) + { + result.Add(candidate); + } + } + + return result; + } + + private int? GetFirstParentSourceDistance(ICommit mergedBranchTip, ICommit sourceBranchTip) + { + var sourceHistory = this.firstParentHistoryCache.GetOrAdd(sourceBranchTip.Sha, () => + { + HashSet result = []; + for (ICommit? commit = sourceBranchTip; commit is not null; commit = commit.Parents.FirstOrDefault()) + { + result.Add(commit.Sha); + } + return result; + }); + + var distance = 0; + for (ICommit? commit = mergedBranchTip; commit is not null; commit = commit.Parents.FirstOrDefault()) + { + if (sourceHistory.Contains(commit.Sha)) + { + return distance; + } + distance++; + } + return null; + } + + private static bool IsConfiguredSourceBranch( + IBranch candidate, IBranchConfiguration mergedBranchConfiguration, + IGitVersionConfiguration configuration) => + mergedBranchConfiguration.SourceBranches.Any(sourceBranch => + configuration.Branches.TryGetValue(sourceBranch, out var sourceBranchConfiguration) + && sourceBranchConfiguration.IsMatch(candidate.Name.WithoutOrigin)); + + private static VersionField SelectIncrement( + bool preventIncrementOfMergedBranch, bool? preventIncrementWhenBranchMerged, + VersionField targetIncrement, VersionField sourceIncrement) + { + if (preventIncrementOfMergedBranch) + { + return preventIncrementWhenBranchMerged == true ? targetIncrement : sourceIncrement; + } + + return preventIncrementWhenBranchMerged is null + ? targetIncrement.Consolidate(sourceIncrement) + : targetIncrement; + } + private CommitMessageIncrement? GetIncrementForCommits(EffectiveConfiguration configuration, ICommit[] commits) { commits.NotNull(); @@ -79,7 +335,8 @@ public VersionField DetermineIncrementedField( } private CommitMessageIncrement? FindCommitMessageIncrement( - EffectiveConfiguration configuration, ICommit? baseVersionSource, ICommit currentCommit, string? label) + EffectiveConfiguration configuration, ICommit? baseVersionSource, ICommit currentCommit, string? label, + IReadOnlySet? includedCommits = null) { if (configuration.CommitMessageIncrementing == CommitMessageIncrementMode.Disabled) { @@ -95,6 +352,11 @@ public VersionField DetermineIncrementedField( ignore: configuration.Ignore ); + if (includedCommits is not null) + { + commits = commits.Where(commit => includedCommits.Contains(commit.Sha)); + } + if (configuration.CommitMessageIncrementing == CommitMessageIncrementMode.MergeMessageOnly) { commits = commits.Where(c => c.Parents.Count > 1); From c2bcbca97966b9f1ce4c6d571d9799521c17cdf5 Mon Sep 17 00:00:00 2001 From: Morax Date: Fri, 21 Aug 2026 18:46:13 +0200 Subject: [PATCH 02/24] test: collapse equivalent alignment branches --- ...AlignGitFlowWithMainlineVersionStrategy.cs | 374 +++--------------- ...gnGitHubFlowWithMainlineVersionStrategy.cs | 374 +++--------------- 2 files changed, 128 insertions(+), 620 deletions(-) diff --git a/src/GitVersion.Core.Tests/IntegrationTests/AlignGitFlowWithMainlineVersionStrategy.cs b/src/GitVersion.Core.Tests/IntegrationTests/AlignGitFlowWithMainlineVersionStrategy.cs index 6d3954f0e8..23591aea02 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/AlignGitFlowWithMainlineVersionStrategy.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/AlignGitFlowWithMainlineVersionStrategy.cs @@ -230,16 +230,8 @@ public void EnsureFeatureWithIncrementInheritOnMainAndPatchOnFeatureBranch(bool fixture.MergeTo("main"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.0.1-1+4", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.0.1-1+4", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("2.0.1-1+4", configuration); } /// @@ -310,16 +302,8 @@ public void EnsureFeatureWithIncrementInheritOnMainAndMinorOnFeatureBranch(bool fixture.MergeTo("main"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.1.0-1+4", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.1.0-1+4", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("2.1.0-1+4", configuration); } /// @@ -1308,16 +1292,8 @@ public void EnsureFeatureWithIncrementMinorOnMainAndPatchOnFeatureBranch(bool us fixture.MergeTo("main"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.2.1-1+4", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.2.1-1+4", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("0.2.1-1+4", configuration); } /// @@ -1684,16 +1660,9 @@ public void EnsureFeatureWithIncrementMajorOnMainAndPatchOnFeatureBranch(bool us fixture.MergeTo("main"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.0.1-1+4", configuration); - } - else - { - // ❔ expected: "2.0.1-foo.2+3" - fixture.AssertFullSemver("2.0.1-1+4", configuration); - } + // Mainline: ✅ succeeds as expected + // Non-mainline: ❔ expected: "2.0.1-foo.2+3" + fixture.AssertFullSemver("2.0.1-1+4", configuration); } /// @@ -1764,16 +1733,8 @@ public void EnsureFeatureWithIncrementMajorOnMainAndMinorOnFeatureBranch(bool us fixture.MergeTo("main"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.1.0-1+4", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.1.0-1+4", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("2.1.0-1+4", configuration); } /// @@ -2101,16 +2062,8 @@ public void EnsureMergeMainToFeatureWithIncrementInheritOnMainAndPatchOnFeatureB fixture.MergeTo("main"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.0.1-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.0.1-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("2.0.1-1+6", configuration); } /// @@ -2192,16 +2145,8 @@ public void EnsureMergeMainToFeatureWithIncrementInheritOnMainAndMinorOnFeatureB fixture.MergeTo("main"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.1.0-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.1.0-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("2.1.0-1+6", configuration); } /// @@ -3450,16 +3395,8 @@ public void EnsureMergeMainToFeatureWithIncrementMinorOnMainAndPatchOnFeatureBra fixture.MergeTo("main"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.2.1-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.2.1-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("0.2.1-1+6", configuration); } /// @@ -3881,16 +3818,8 @@ public void EnsureMergeMainToFeatureWithIncrementMajorOnMainAndPatchOnFeatureBra fixture.MergeTo("main"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.0.1-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.0.1-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("2.0.1-1+6", configuration); } /// @@ -3972,16 +3901,8 @@ public void EnsureMergeMainToFeatureWithIncrementMajorOnMainAndMinorOnFeatureBra fixture.MergeTo("main"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.1.0-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.1.0-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("2.1.0-1+6", configuration); } /// @@ -4255,16 +4176,8 @@ public void EnsurePullRequestWithIncrementInheritOnMainAndPatchOnFeatureBranch(b fixture.Remove("pull/2/merge"); fixture.MergeNoFF("feature/foo"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.1-1+2", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.1-1+2", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.1-1+2", configuration); } /// @@ -4327,16 +4240,8 @@ public void EnsurePullRequestWithIncrementInheritOnMainAndMinorOnFeatureBranch(b fixture.Remove("pull/2/merge"); fixture.MergeNoFF("feature/foo"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.1.0-1+2", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.1.0-1+2", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("1.1.0-1+2", configuration); } /// @@ -4575,16 +4480,8 @@ public void EnsurePullRequestWithIncrementNoneOnMainAndPatchOnFeatureBranch(bool fixture.Remove("pull/2/merge"); fixture.MergeNoFF("feature/foo"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.0.1-1+2", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.0.1-1+2", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("0.0.1-1+2", configuration); } /// @@ -4639,16 +4536,8 @@ public void EnsurePullRequestWithIncrementNoneOnMainAndMinorOnFeatureBranch(bool fixture.Remove("pull/2/merge"); fixture.MergeNoFF("feature/foo"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.1.0-1+2", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.1.0-1+2", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("0.1.0-1+2", configuration); } /// @@ -4703,16 +4592,8 @@ public void EnsurePullRequestWithIncrementNoneOnMainAndMajorOnFeatureBranch(bool fixture.Remove("pull/2/merge"); fixture.MergeNoFF("feature/foo"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.0-1+2", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.0-1+2", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.0-1+2", configuration); } /// @@ -4951,16 +4832,8 @@ public void EnsurePullRequestWithIncrementPatchOnMainAndMinorOnFeatureBranch(boo fixture.Remove("pull/2/merge"); fixture.MergeNoFF("feature/foo"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.1.0-1+2", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.1.0-1+2", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("0.1.0-1+2", configuration); } /// @@ -5015,16 +4888,8 @@ public void EnsurePullRequestWithIncrementPatchOnMainAndMajorOnFeatureBranch(boo fixture.Remove("pull/2/merge"); fixture.MergeNoFF("feature/foo"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.0-1+2", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.0-1+2", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.0-1+2", configuration); } /// @@ -5215,16 +5080,8 @@ public void EnsurePullRequestWithIncrementMinorOnMainAndPatchOnFeatureBranch(boo fixture.Remove("pull/2/merge"); fixture.MergeNoFF("feature/foo"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.1.1-1+2", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.1.1-1+2", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("0.1.1-1+2", configuration); } /// @@ -5335,16 +5192,8 @@ public void EnsurePullRequestWithIncrementMinorOnMainAndMajorOnFeatureBranch(boo fixture.Remove("pull/2/merge"); fixture.MergeNoFF("feature/foo"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.0-1+2", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.0-1+2", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.0-1+2", configuration); } /// @@ -5535,16 +5384,8 @@ public void EnsurePullRequestWithIncrementMajorOnMainAndPatchOnFeatureBranch(boo fixture.Remove("pull/2/merge"); fixture.MergeNoFF("feature/foo"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.1-1+2", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.1-1+2", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.1-1+2", configuration); } /// @@ -5607,16 +5448,8 @@ public void EnsurePullRequestWithIncrementMajorOnMainAndMinorOnFeatureBranch(boo fixture.Remove("pull/2/merge"); fixture.MergeNoFF("feature/foo"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.1.0-1+2", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.1.0-1+2", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("1.1.0-1+2", configuration); } /// @@ -6083,16 +5916,8 @@ public void EnsureReleaseBranchWithIncrementInheritOnMainAndPatchOnReleaseBranch fixture.MergeTo("main", removeBranchAfterMerging: true); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.0.1-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.0.1-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("2.0.1-1+6", configuration); if (!useMainline) { @@ -6197,16 +6022,8 @@ public void EnsureReleaseBranchWithIncrementInheritOnMainAndMinorOnReleaseBranch fixture.MergeTo("main", removeBranchAfterMerging: true); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.1.0-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.1.0-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("2.1.0-1+6", configuration); if (!useMainline) { @@ -8019,16 +7836,8 @@ public void EnsureReleaseBranchWithIncrementMinorOnMainAndPatchOnReleaseBranch(b fixture.MergeTo("main", removeBranchAfterMerging: true); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.2.1-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.2.1-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("0.2.1-1+6", configuration); if (!useMainline) { @@ -8647,16 +8456,8 @@ public void EnsureReleaseBranchWithIncrementMajorOnMainAndPatchOnReleaseBranch(b fixture.MergeTo("main", removeBranchAfterMerging: true); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.0.1-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.0.1-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("2.0.1-1+6", configuration); if (!useMainline) { @@ -8759,16 +8560,8 @@ public void EnsureReleaseBranchWithIncrementMajorOnMainAndMinorOnReleaseBranch(b fixture.MergeTo("main", removeBranchAfterMerging: true); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.1.0-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.1.0-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("2.1.0-1+6", configuration); if (!useMainline) { @@ -9326,16 +9119,8 @@ public void EnsureReleaseAndFeatureBranchWithIncrementInheritOnMainAndPatchOnRel fixture.MergeTo("main", removeBranchAfterMerging: true); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.1-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.1-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.1-1+6", configuration); if (!useMainline) { @@ -9451,16 +9236,8 @@ public void EnsureReleaseAndFeatureBranchWithIncrementInheritOnMainAndMinorOnRel fixture.MergeTo("main", removeBranchAfterMerging: true); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.1.0-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.1.0-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("1.1.0-1+6", configuration); if (!useMainline) { @@ -11313,16 +11090,9 @@ public void EnsureReleaseAndFeatureBranchWithIncrementMinorOnMainAndPatchOnRelea fixture.MergeTo("main", removeBranchAfterMerging: true); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.1.1-1+6", configuration); - } - else - { - // ❔ not expected - fixture.AssertFullSemver("0.1.1-1+6", configuration); - } + // Mainline: ✅ succeeds as expected + // Non-mainline: ❔ not expected + fixture.AssertFullSemver("0.1.1-1+6", configuration); if (!useMainline) { @@ -11988,16 +11758,8 @@ public void EnsureReleaseAndFeatureBranchWithIncrementMajorOnMainAndPatchOnRelea fixture.MergeTo("main", removeBranchAfterMerging: true); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.1-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.1-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.1-1+6", configuration); if (!useMainline) { @@ -12113,16 +11875,8 @@ public void EnsureReleaseAndFeatureBranchWithIncrementMajorOnMainAndMinorOnRelea fixture.MergeTo("main", removeBranchAfterMerging: true); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.1.0-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.1.0-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("1.1.0-1+6", configuration); if (!useMainline) { diff --git a/src/GitVersion.Core.Tests/IntegrationTests/AlignGitHubFlowWithMainlineVersionStrategy.cs b/src/GitVersion.Core.Tests/IntegrationTests/AlignGitHubFlowWithMainlineVersionStrategy.cs index 6df3907dec..87647d7c29 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/AlignGitHubFlowWithMainlineVersionStrategy.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/AlignGitHubFlowWithMainlineVersionStrategy.cs @@ -230,16 +230,8 @@ public void EnsureFeatureWithIncrementInheritOnMainAndPatchOnFeatureBranch(bool fixture.MergeTo("main"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.0.1-1+4", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.0.1-1+4", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("2.0.1-1+4", configuration); } /// @@ -310,16 +302,8 @@ public void EnsureFeatureWithIncrementInheritOnMainAndMinorOnFeatureBranch(bool fixture.MergeTo("main"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.1.0-1+4", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.1.0-1+4", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("2.1.0-1+4", configuration); } /// @@ -1308,16 +1292,8 @@ public void EnsureFeatureWithIncrementMinorOnMainAndPatchOnFeatureBranch(bool us fixture.MergeTo("main"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.2.1-1+4", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.2.1-1+4", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("0.2.1-1+4", configuration); } /// @@ -1684,16 +1660,9 @@ public void EnsureFeatureWithIncrementMajorOnMainAndPatchOnFeatureBranch(bool us fixture.MergeTo("main"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.0.1-1+4", configuration); - } - else - { - // ❔ expected: "2.0.1-foo.2+3" - fixture.AssertFullSemver("2.0.1-1+4", configuration); - } + // Mainline: ✅ succeeds as expected + // Non-mainline: ❔ expected: "2.0.1-foo.2+3" + fixture.AssertFullSemver("2.0.1-1+4", configuration); } /// @@ -1764,16 +1733,8 @@ public void EnsureFeatureWithIncrementMajorOnMainAndMinorOnFeatureBranch(bool us fixture.MergeTo("main"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.1.0-1+4", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.1.0-1+4", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("2.1.0-1+4", configuration); } /// @@ -2101,16 +2062,8 @@ public void EnsureMergeMainToFeatureWithIncrementInheritOnMainAndPatchOnFeatureB fixture.MergeTo("main"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.0.1-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.0.1-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("2.0.1-1+6", configuration); } /// @@ -2192,16 +2145,8 @@ public void EnsureMergeMainToFeatureWithIncrementInheritOnMainAndMinorOnFeatureB fixture.MergeTo("main"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.1.0-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.1.0-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("2.1.0-1+6", configuration); } /// @@ -3450,16 +3395,8 @@ public void EnsureMergeMainToFeatureWithIncrementMinorOnMainAndPatchOnFeatureBra fixture.MergeTo("main"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.2.1-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.2.1-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("0.2.1-1+6", configuration); } /// @@ -3881,16 +3818,8 @@ public void EnsureMergeMainToFeatureWithIncrementMajorOnMainAndPatchOnFeatureBra fixture.MergeTo("main"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.0.1-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.0.1-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("2.0.1-1+6", configuration); } /// @@ -3972,16 +3901,8 @@ public void EnsureMergeMainToFeatureWithIncrementMajorOnMainAndMinorOnFeatureBra fixture.MergeTo("main"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.1.0-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.1.0-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("2.1.0-1+6", configuration); } /// @@ -4255,16 +4176,8 @@ public void EnsurePullRequestWithIncrementInheritOnMainAndPatchOnFeatureBranch(b fixture.Remove("pull/2/merge"); fixture.MergeNoFF("feature/foo"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.1-1+2", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.1-1+2", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.1-1+2", configuration); } /// @@ -4327,16 +4240,8 @@ public void EnsurePullRequestWithIncrementInheritOnMainAndMinorOnFeatureBranch(b fixture.Remove("pull/2/merge"); fixture.MergeNoFF("feature/foo"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.1.0-1+2", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.1.0-1+2", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("1.1.0-1+2", configuration); } /// @@ -4575,16 +4480,8 @@ public void EnsurePullRequestWithIncrementNoneOnMainAndPatchOnFeatureBranch(bool fixture.Remove("pull/2/merge"); fixture.MergeNoFF("feature/foo"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.0.1-1+2", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.0.1-1+2", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("0.0.1-1+2", configuration); } /// @@ -4639,16 +4536,8 @@ public void EnsurePullRequestWithIncrementNoneOnMainAndMinorOnFeatureBranch(bool fixture.Remove("pull/2/merge"); fixture.MergeNoFF("feature/foo"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.1.0-1+2", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.1.0-1+2", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("0.1.0-1+2", configuration); } /// @@ -4703,16 +4592,8 @@ public void EnsurePullRequestWithIncrementNoneOnMainAndMajorOnFeatureBranch(bool fixture.Remove("pull/2/merge"); fixture.MergeNoFF("feature/foo"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.0-1+2", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.0-1+2", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.0-1+2", configuration); } /// @@ -4951,16 +4832,8 @@ public void EnsurePullRequestWithIncrementPatchOnMainAndMinorOnFeatureBranch(boo fixture.Remove("pull/2/merge"); fixture.MergeNoFF("feature/foo"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.1.0-1+2", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.1.0-1+2", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("0.1.0-1+2", configuration); } /// @@ -5015,16 +4888,8 @@ public void EnsurePullRequestWithIncrementPatchOnMainAndMajorOnFeatureBranch(boo fixture.Remove("pull/2/merge"); fixture.MergeNoFF("feature/foo"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.0-1+2", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.0-1+2", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.0-1+2", configuration); } /// @@ -5215,16 +5080,8 @@ public void EnsurePullRequestWithIncrementMinorOnMainAndPatchOnFeatureBranch(boo fixture.Remove("pull/2/merge"); fixture.MergeNoFF("feature/foo"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.1.1-1+2", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.1.1-1+2", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("0.1.1-1+2", configuration); } /// @@ -5335,16 +5192,8 @@ public void EnsurePullRequestWithIncrementMinorOnMainAndMajorOnFeatureBranch(boo fixture.Remove("pull/2/merge"); fixture.MergeNoFF("feature/foo"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.0-1+2", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.0-1+2", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.0-1+2", configuration); } /// @@ -5535,16 +5384,8 @@ public void EnsurePullRequestWithIncrementMajorOnMainAndPatchOnFeatureBranch(boo fixture.Remove("pull/2/merge"); fixture.MergeNoFF("feature/foo"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.1-1+2", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.1-1+2", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.1-1+2", configuration); } /// @@ -5607,16 +5448,8 @@ public void EnsurePullRequestWithIncrementMajorOnMainAndMinorOnFeatureBranch(boo fixture.Remove("pull/2/merge"); fixture.MergeNoFF("feature/foo"); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.1.0-1+2", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.1.0-1+2", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("1.1.0-1+2", configuration); } /// @@ -6083,16 +5916,8 @@ public void EnsureReleaseBranchWithIncrementInheritOnMainAndPatchOnReleaseBranch fixture.MergeTo("main", removeBranchAfterMerging: true); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.0.1-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.0.1-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("2.0.1-1+6", configuration); if (!useMainline) { @@ -6197,16 +6022,8 @@ public void EnsureReleaseBranchWithIncrementInheritOnMainAndMinorOnReleaseBranch fixture.MergeTo("main", removeBranchAfterMerging: true); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.1.0-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.1.0-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("2.1.0-1+6", configuration); if (!useMainline) { @@ -8019,16 +7836,8 @@ public void EnsureReleaseBranchWithIncrementMinorOnMainAndPatchOnReleaseBranch(b fixture.MergeTo("main", removeBranchAfterMerging: true); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.2.1-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.2.1-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("0.2.1-1+6", configuration); if (!useMainline) { @@ -8647,16 +8456,8 @@ public void EnsureReleaseBranchWithIncrementMajorOnMainAndPatchOnReleaseBranch(b fixture.MergeTo("main", removeBranchAfterMerging: true); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.0.1-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.0.1-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("2.0.1-1+6", configuration); if (!useMainline) { @@ -8759,16 +8560,8 @@ public void EnsureReleaseBranchWithIncrementMajorOnMainAndMinorOnReleaseBranch(b fixture.MergeTo("main", removeBranchAfterMerging: true); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.1.0-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("2.1.0-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("2.1.0-1+6", configuration); if (!useMainline) { @@ -9326,16 +9119,8 @@ public void EnsureReleaseAndFeatureBranchWithIncrementInheritOnMainAndPatchOnRel fixture.MergeTo("main", removeBranchAfterMerging: true); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.1-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.1-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.1-1+6", configuration); if (!useMainline) { @@ -9451,16 +9236,8 @@ public void EnsureReleaseAndFeatureBranchWithIncrementInheritOnMainAndMinorOnRel fixture.MergeTo("main", removeBranchAfterMerging: true); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.1.0-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.1.0-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("1.1.0-1+6", configuration); if (!useMainline) { @@ -11313,16 +11090,9 @@ public void EnsureReleaseAndFeatureBranchWithIncrementMinorOnMainAndPatchOnRelea fixture.MergeTo("main", removeBranchAfterMerging: true); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("0.1.1-1+6", configuration); - } - else - { - // ❔ not expected - fixture.AssertFullSemver("0.1.1-1+6", configuration); - } + // Mainline: ✅ succeeds as expected + // Non-mainline: ❔ not expected + fixture.AssertFullSemver("0.1.1-1+6", configuration); if (!useMainline) { @@ -11988,16 +11758,8 @@ public void EnsureReleaseAndFeatureBranchWithIncrementMajorOnMainAndPatchOnRelea fixture.MergeTo("main", removeBranchAfterMerging: true); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.1-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.0.1-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("1.0.1-1+6", configuration); if (!useMainline) { @@ -12113,16 +11875,8 @@ public void EnsureReleaseAndFeatureBranchWithIncrementMajorOnMainAndMinorOnRelea fixture.MergeTo("main", removeBranchAfterMerging: true); - if (useMainline) - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.1.0-1+6", configuration); - } - else - { - // ✅ succeeds as expected - fixture.AssertFullSemver("1.1.0-1+6", configuration); - } + // ✅ succeeds as expected + fixture.AssertFullSemver("1.1.0-1+6", configuration); if (!useMainline) { From 28cbde147ef579afb8bd9298e70a46232964d7f1 Mon Sep 17 00:00:00 2001 From: Morax Date: Fri, 21 Aug 2026 19:13:45 +0200 Subject: [PATCH 03/24] fix: preserve merged increment edge cases --- ...PreventIncrementOfMergedBranchScenarios.cs | 171 +++++++++++ .../IncrementStrategyFinder.cs | 284 +++++++++++++----- 2 files changed, 374 insertions(+), 81 deletions(-) diff --git a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs index 8fda9e3ddb..046229e3bc 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs @@ -123,6 +123,31 @@ public void UsesHighestIncrementFromMultipleMergedBranches() fixture.AssertFullSemver("1.1.0-4", configuration); } + [Test] + public void LatestMergedSourceResetDiscardsEarlierMergedIncrement() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Patch) + ).WithBranch("hotfix", builder => builder + .WithIncrement(IncrementStrategy.Patch) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit("Breaking change +semver: major"); + fixture.MergeTo("main", removeBranchAfterMerging: true); + fixture.BranchTo("hotfix/foo"); + fixture.MakeACommit("Hotfix =semver: patch"); + fixture.MergeTo("main", removeBranchAfterMerging: true); + + fixture.AssertFullSemver("1.0.1-4", configuration); + } + [TestCase(false)] [TestCase(true)] public void IncludesTargetIncrementForTargetCommit(bool commitAfterMerge) @@ -218,6 +243,120 @@ public void ResolvesInheritedIncrementFromMergedBranchSource() fixture.AssertFullSemver("1.1.0-3", configuration); } + [Test] + public void UsesInheritedPreventIncrementWhenBranchMergedSetting() + { + var configuration = GitFlowConfigurationBuilder.New + .WithPreventIncrementWhenBranchMerged(false) + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("develop", builder => builder + .WithIncrement(IncrementStrategy.Minor) + .WithPreventIncrementWhenBranchMerged(true) + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Inherit) + .WithPreventIncrementWhenBranchMerged(null) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("develop"); + fixture.MakeACommit(); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main", removeBranchAfterMerging: true); + + fixture.AssertFullSemver("1.0.1-3", configuration); + } + + [Test] + public void ScoresLocalAndRemoteInheritedSourceBranchesBeforePreferringLocal() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("develop", builder => builder + .WithIncrement(IncrementStrategy.Minor) + ).WithBranch("integration", builder => builder + .WithRegularExpression("^(origin/)?integration$") + .WithIncrement(IncrementStrategy.Inherit) + .WithSourceBranches("main", "develop") + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Inherit) + .WithSourceBranches("integration") + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("develop"); + var developTip = fixture.MakeACommit(); + fixture.BranchTo("integration"); + fixture.MakeACommit(); + fixture.Repository.Refs.Add( + "refs/remotes/origin/integration", fixture.Repository.Lookup(developTip).Id); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit(); + fixture.Checkout("main"); + fixture.Remove("integration"); + fixture.MakeACommit(); + fixture.BranchTo("integration"); + fixture.MakeACommit(); + fixture.Checkout("feature/foo"); + fixture.MergeTo("main", removeBranchAfterMerging: true); + + fixture.AssertFullSemver("1.1.0-5", configuration); + } + + [Test] + public void HonorsPreventIncrementForTaggedMergedSourceTip() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("hotfix", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementWhenCurrentCommitTagged(true) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("hotfix/foo"); + fixture.MakeATaggedCommit("2.0.0"); + fixture.MergeTo("main", removeBranchAfterMerging: true); + + fixture.AssertFullSemver("2.0.0-1", configuration); + } + + [Test] + public void RetainsTargetAsPossibleHistoricalSourceBranch() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("develop", builder => builder + .WithIncrement(IncrementStrategy.Minor) + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Inherit) + .WithSourceBranches("main", "develop") + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("develop"); + fixture.MakeACommit(); + fixture.Checkout("main"); + fixture.MakeACommit(); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main", removeBranchAfterMerging: true); + + fixture.AssertFullSemver("1.0.1-3", configuration); + } + [Test] public void ResolvesInheritedIncrementFromHistoricalMergedBranchTip() { @@ -276,4 +415,36 @@ public void PrefersHistoricalSourceOverLaterAbsorbingBranch() fixture.AssertFullSemver("1.1.0-3", configuration); } + + [Test] + public void ResolvesRetainedInheritedBranchFromHistoricalSource() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("develop", builder => builder + .WithIncrement(IncrementStrategy.Minor) + ).WithBranch("support", builder => builder + .WithRegularExpression("^support$") + .WithIncrement(IncrementStrategy.Major) + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Inherit) + .WithSourceBranches("develop", "support") + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.CreateBranch("support"); + fixture.BranchTo("develop"); + fixture.MakeACommit(); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main"); + fixture.Checkout("feature/foo"); + fixture.MergeTo("support"); + fixture.Checkout("main"); + + fixture.AssertFullSemver("1.1.0-3", configuration); + } } diff --git a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs index 6e757c1f28..914e6ae56a 100644 --- a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs @@ -14,7 +14,8 @@ internal class IncrementStrategyFinder( : IIncrementStrategyFinder { private readonly Dictionary commitIncrementCache = []; - private readonly Dictionary<(string Commit, EffectiveConfiguration Target), VersionField[]> mergedBranchIncrementCache = []; + private readonly Dictionary<(string Commit, bool IsMergedTipBaseVersionSource, EffectiveConfiguration Target), + MergedBranchIncrement[]> mergedBranchIncrementCache = []; private readonly Dictionary<(string Branch, string? Tip), EffectiveBranchConfiguration[]> effectiveBranchConfigurationCache = []; private readonly Dictionary> firstParentHistoryCache = []; private readonly Dictionary> headCommitsMapCache = []; @@ -41,18 +42,28 @@ public VersionField DetermineIncrementedField( || !configuration.TrackMergeMessage || Context.Configuration.GetBranchConfiguration(Context.CurrentBranch.Name).IsMainBranch != true) { - return targetIncrement; + return targetIncrement.Increment; } var increments = GetIncrementsFromCommitHistory( - currentCommit, baseVersionSource, shouldIncrement, configuration, label, targetIncrement).ToArray(); + currentCommit, baseVersionSource, shouldIncrement, configuration, label, targetIncrement); - return increments.Length == 0 - ? targetIncrement - : increments.Aggregate(VersionField.None, (result, increment) => result.Consolidate(increment)); + var result = VersionField.None; + var hasIncrement = false; + foreach (var increment in increments) + { + hasIncrement = true; + result = result.Consolidate(increment.Increment); + if (increment.VersionBumpNeedsToBeReset) + { + break; + } + } + + return hasIncrement ? result : targetIncrement.Increment; } - private VersionField DetermineIncrementedFieldInternal( + private CommitMessageIncrement DetermineIncrementedFieldInternal( ICommit currentCommit, ICommit? baseVersionSource, bool shouldIncrement, EffectiveConfiguration configuration, string? label, IReadOnlySet? includedCommits = null) @@ -60,12 +71,18 @@ private VersionField DetermineIncrementedFieldInternal( var commitMessageIncrement = FindCommitMessageIncrement( configuration, baseVersionSource, currentCommit, label, includedCommits); + return DetermineIncrementedField(commitMessageIncrement, shouldIncrement, configuration); + } + + private static CommitMessageIncrement DetermineIncrementedField( + CommitMessageIncrement? commitMessageIncrement, bool shouldIncrement, EffectiveConfiguration configuration) + { var defaultIncrement = configuration.Increment.ToVersionField(); // use the default branch configuration increment strategy if there are no commit message overrides if (commitMessageIncrement == null) { - return shouldIncrement ? defaultIncrement : VersionField.None; + return new(shouldIncrement ? defaultIncrement : VersionField.None, VersionBumpNeedsToBeReset: false); } // don't increment for less than the branch configuration increment, if the absence of commit messages would have @@ -73,100 +90,202 @@ private VersionField DetermineIncrementedFieldInternal( if (shouldIncrement && !commitMessageIncrement.Value.VersionBumpNeedsToBeReset && commitMessageIncrement.Value.Increment < defaultIncrement) { - return defaultIncrement; + return new(defaultIncrement, VersionBumpNeedsToBeReset: false); } - return commitMessageIncrement.Value.Increment; + return commitMessageIncrement.Value; } - private IEnumerable GetIncrementsFromCommitHistory( + private IEnumerable GetIncrementsFromCommitHistory( ICommit currentCommit, ICommit? baseVersionSource, bool shouldIncrement, - EffectiveConfiguration targetConfiguration, string? targetLabel, VersionField targetIncrement) + EffectiveConfiguration targetConfiguration, string? targetLabel, CommitMessageIncrement targetIncrement) + { + var history = GetFirstParentCommitHistory( + currentCommit, baseVersionSource, targetConfiguration).ToArray(); + + if (!history.Any(item => item.MergedBranch is not null)) + { + yield return targetIncrement; + yield break; + } + + targetIncrement = DetermineIncrementedFieldInternal( + currentCommit, baseVersionSource, shouldIncrement, + targetConfiguration, targetLabel, + history.Select(item => item.Commit.Sha).ToHashSet()); + + var targetCommitHistory = GetCommitHistory( + targetConfiguration.TagPrefixPattern, + targetConfiguration.SemanticVersionFormat, + baseVersionSource, + currentCommit, + targetLabel, + targetConfiguration.Ignore) + .Select(commit => commit.Sha) + .ToHashSet(); + List targetSegment = []; + + foreach (var entry in history) + { + if (entry.MergedBranch is not { } mergedBranch) + { + targetSegment.Add(entry.Commit); + continue; + } + + if (targetSegment.Count != 0) + { + yield return GetTargetIncrement( + targetSegment, targetCommitHistory, shouldIncrement, targetConfiguration); + targetSegment.Clear(); + } + + var targetMergeMessageIncrement = FindCommitMessageIncrement( + targetConfiguration, [entry.Commit], targetCommitHistory); + if (targetMergeMessageIncrement is not null) + { + yield return DetermineIncrementedField( + targetMergeMessageIncrement, shouldIncrement, targetConfiguration); + } + + var sourceIncrements = GetMergedBranchIncrements( + entry.Commit, mergedBranch, baseVersionSource, targetConfiguration); + if (sourceIncrements.Length != 0) + { + yield return ConsolidateMergedBranchIncrements( + sourceIncrements, targetConfiguration, targetIncrement.Increment); + } + } + + if (targetSegment.Count != 0) + { + yield return GetTargetIncrement( + targetSegment, targetCommitHistory, shouldIncrement, targetConfiguration); + } + } + + private IEnumerable GetFirstParentCommitHistory( + ICommit currentCommit, ICommit? baseVersionSource, EffectiveConfiguration targetConfiguration) { - var configuration = Context.Configuration; var commitLog = this.repositoryStore .GetCommitLog(baseVersionSource, currentCommit, targetConfiguration.Ignore) .Select(commit => commit.Sha) .ToHashSet(); - List targetCommits = []; - Dictionary mergedBranches = []; for (ICommit? commit = currentCommit; commit is not null; commit = commit.Parents.FirstOrDefault()) { if (baseVersionSource?.Equals(commit) == true) { - break; + yield break; } if (!commitLog.Contains(commit.Sha)) { continue; } - targetCommits.Add(commit); - if (!commit.IsMergeCommit - || commit.Parents.Count != 2 - || !MergeMessage.TryParse(commit, configuration, out var mergeMessage) - || mergeMessage.MergedBranch is not { } mergedBranch) + ReferenceName? mergedBranch = null; + if (commit.IsMergeCommit + && commit.Parents.Count == 2 + && MergeMessage.TryParse(commit, Context.Configuration, out var mergeMessage)) { - continue; + mergedBranch = mergeMessage.MergedBranch; } - mergedBranches.Add(commit.Sha, (commit, mergedBranch)); + yield return new(commit, mergedBranch); } + } + + private CommitMessageIncrement GetTargetIncrement( + IEnumerable targetCommits, IReadOnlySet targetCommitHistory, + bool shouldIncrement, EffectiveConfiguration targetConfiguration) => + DetermineIncrementedField( + FindCommitMessageIncrement(targetConfiguration, targetCommits, targetCommitHistory), + shouldIncrement, + targetConfiguration); + + private MergedBranchIncrement[] GetMergedBranchIncrements( + ICommit mergeCommit, ReferenceName mergedBranch, ICommit? baseVersionSource, + EffectiveConfiguration targetConfiguration) => + this.mergedBranchIncrementCache.GetOrAdd( + (mergeCommit.Sha, mergeCommit.Parents[1].Equals(baseVersionSource), targetConfiguration), () => + { + var sourceBranchConfiguration = Context.Configuration.GetBranchConfiguration(mergedBranch); + var mergeBase = this.repositoryStore.FindMergeBase( + mergeCommit.Parents[0], mergeCommit.Parents[1]); + + return [.. GetSourceConfigurations( + mergedBranch, mergeCommit.Parents[1], sourceBranchConfiguration, targetConfiguration) + .Select(sourceConfiguration => GetMergedBranchIncrement( + mergeCommit.Parents[1], mergedBranch, baseVersionSource, mergeBase, + sourceBranchConfiguration, sourceConfiguration))]; + }); + + private MergedBranchIncrement GetMergedBranchIncrement( + ICommit mergedBranchTip, ReferenceName mergedBranch, ICommit? baseVersionSource, ICommit? mergeBase, + IBranchConfiguration sourceBranchConfiguration, EffectiveConfiguration sourceConfiguration) + { + var sourceLabel = sourceConfiguration.GetBranchSpecificLabel( + mergedBranch, null, this.environment); + var preventIncrementWhenBranchMerged = sourceBranchConfiguration.PreventIncrement.WhenBranchMerged + ?? (sourceBranchConfiguration.Increment == IncrementStrategy.Inherit + ? sourceConfiguration.PreventIncrementWhenBranchMerged + : Context.Configuration.PreventIncrement.WhenBranchMerged); + var sourceIncrement = DetermineIncrementedFieldInternal( + currentCommit: mergedBranchTip, + baseVersionSource: mergeBase, + shouldIncrement: ShouldIncrementTaggedCommit( + mergedBranchTip, baseVersionSource, sourceConfiguration, sourceLabel), + configuration: sourceConfiguration, + label: sourceLabel + ); - if (mergedBranches.Count == 0) + return new(sourceIncrement, preventIncrementWhenBranchMerged); + } + + private static CommitMessageIncrement ConsolidateMergedBranchIncrements( + IEnumerable sourceIncrements, + EffectiveConfiguration targetConfiguration, VersionField targetIncrement) + { + var result = new CommitMessageIncrement(VersionField.None, VersionBumpNeedsToBeReset: false); + foreach (var sourceIncrement in sourceIncrements) { - yield return targetIncrement; - yield break; + result = result.Consolidate(SelectIncrement( + targetConfiguration.PreventIncrementOfMergedBranch, + sourceIncrement.PreventIncrementWhenBranchMerged, + targetIncrement, + sourceIncrement.Increment + )); } - var targetCommitShas = targetCommits.Select(commit => commit.Sha).ToHashSet(); - targetIncrement = DetermineIncrementedFieldInternal( - currentCommit, baseVersionSource, shouldIncrement, - targetConfiguration, targetLabel, targetCommitShas); + return result; + } - var mergedBranchCommits = mergedBranches.Keys.ToHashSet(); - var hasTargetContribution = targetCommits.Any(commit => !mergedBranchCommits.Contains(commit.Sha)) - || FindCommitMessageIncrement( - targetConfiguration, baseVersionSource, currentCommit, targetLabel, mergedBranchCommits) is not null; - if (hasTargetContribution) + private CommitMessageIncrement? FindCommitMessageIncrement( + EffectiveConfiguration configuration, IEnumerable commits, IReadOnlySet commitHistory) + { + if (configuration.CommitMessageIncrementing == CommitMessageIncrementMode.Disabled) { - yield return targetIncrement; + return null; } - foreach (var (commit, mergedBranch) in mergedBranches.Values) + commits = commits.Where(commit => commitHistory.Contains(commit.Sha)); + if (configuration.CommitMessageIncrementing == CommitMessageIncrementMode.MergeMessageOnly) { - var sourceBranchConfiguration = configuration.GetBranchConfiguration(mergedBranch); - var preventIncrementWhenBranchMerged = sourceBranchConfiguration.PreventIncrement.WhenBranchMerged - ?? configuration.PreventIncrement.WhenBranchMerged; - - foreach (var sourceIncrement in this.mergedBranchIncrementCache.GetOrAdd( - (commit.Sha, targetConfiguration), () => - { - var mergeBase = this.repositoryStore.FindMergeBase(commit.Parents[0], commit.Parents[1]); - - return [.. GetSourceConfigurations( - mergedBranch, commit.Parents[1], sourceBranchConfiguration, targetConfiguration) - .Select(sourceConfiguration => DetermineIncrementedFieldInternal( - currentCommit: commit.Parents[1], - baseVersionSource: mergeBase, - shouldIncrement: true, - configuration: sourceConfiguration, - label: sourceConfiguration.GetBranchSpecificLabel( - mergedBranch, null, this.environment) - ))]; - })) - { - yield return SelectIncrement( - targetConfiguration.PreventIncrementOfMergedBranch, - preventIncrementWhenBranchMerged, - targetIncrement, - sourceIncrement - ); - } + commits = commits.Where(commit => commit.Parents.Count > 1); } + + return GetIncrementForCommits(configuration, [.. commits]); } + private bool ShouldIncrementTaggedCommit( + ICommit commit, ICommit? baseVersionSource, EffectiveConfiguration configuration, string? label) => + !commit.Equals(baseVersionSource) + || !configuration.PreventIncrementWhenCurrentCommitTagged + || !this.taggedSemanticVersionRepository + .GetTaggedSemanticVersions( + configuration.TagPrefixPattern, configuration.SemanticVersionFormat, configuration.Ignore)[commit] + .Any(versionWithTag => versionWithTag.Value.IsMatchForBranchSpecificLabel(label)); + private IEnumerable GetSourceConfigurations( ReferenceName mergedBranch, ICommit mergedBranchTip, IBranchConfiguration sourceBranchConfiguration, EffectiveConfiguration targetConfiguration) @@ -195,7 +314,7 @@ private IEnumerable GetSourceConfigurations( var inheritedConfigurations = FindClosestSourceBranches( mergedBranchTip, sourceBranchConfiguration, Context.Configuration, - Context.CurrentBranch.Name, this.repositoryStore) + this.repositoryStore) .SelectMany(GetEffectiveBranchConfigurations) .Select(source => new EffectiveConfiguration( Context.Configuration, sourceBranchConfiguration, source.Value)) @@ -216,17 +335,10 @@ private EffectiveBranchConfiguration[] GetEffectiveBranchConfigurations(IBranch private IEnumerable FindClosestSourceBranches( ICommit mergedBranchTip, IBranchConfiguration mergedBranchConfiguration, - IGitVersionConfiguration configuration, ReferenceName currentBranch, - IRepositoryStore repositoryStore) + IGitVersionConfiguration configuration, IRepositoryStore repositoryStore) { var candidates = repositoryStore.Branches - .Where(branch => IsConfiguredSourceBranch(branch, mergedBranchConfiguration, configuration) - // The current target contains the merged tip through this merge and cannot be the - // topic's source. Other configured source branches may legitimately absorb it later. - && !branch.Name.EquivalentTo(currentBranch.WithoutOrigin)) - .GroupBy(branch => branch.Name.WithoutOrigin, StringComparer.OrdinalIgnoreCase) - .Select(group => group.MinBy(branch => branch.IsRemote)) - .OfType(); + .Where(branch => IsConfiguredSourceBranch(branch, mergedBranchConfiguration, configuration)); var closestDistance = int.MaxValue; List result = []; @@ -252,7 +364,10 @@ private IEnumerable FindClosestSourceBranches( } } - return result; + return result + .GroupBy(candidate => candidate.Name.WithoutOrigin, StringComparer.OrdinalIgnoreCase) + .Select(group => group.MinBy(candidate => candidate.IsRemote)) + .OfType(); } private int? GetFirstParentSourceDistance(ICommit mergedBranchTip, ICommit sourceBranchTip) @@ -286,20 +401,27 @@ private static bool IsConfiguredSourceBranch( configuration.Branches.TryGetValue(sourceBranch, out var sourceBranchConfiguration) && sourceBranchConfiguration.IsMatch(candidate.Name.WithoutOrigin)); - private static VersionField SelectIncrement( + private static CommitMessageIncrement SelectIncrement( bool preventIncrementOfMergedBranch, bool? preventIncrementWhenBranchMerged, - VersionField targetIncrement, VersionField sourceIncrement) + VersionField targetIncrement, CommitMessageIncrement sourceIncrement) { if (preventIncrementOfMergedBranch) { - return preventIncrementWhenBranchMerged == true ? targetIncrement : sourceIncrement; + return preventIncrementWhenBranchMerged == true + ? new(targetIncrement, VersionBumpNeedsToBeReset: false) + : sourceIncrement; } return preventIncrementWhenBranchMerged is null - ? targetIncrement.Consolidate(sourceIncrement) - : targetIncrement; + ? new(targetIncrement.Consolidate(sourceIncrement.Increment), sourceIncrement.VersionBumpNeedsToBeReset) + : new(targetIncrement, VersionBumpNeedsToBeReset: false); } + private readonly record struct MergedBranchIncrement( + CommitMessageIncrement Increment, bool? PreventIncrementWhenBranchMerged); + + private readonly record struct CommitHistoryEntry(ICommit Commit, ReferenceName? MergedBranch); + private CommitMessageIncrement? GetIncrementForCommits(EffectiveConfiguration configuration, ICommit[] commits) { commits.NotNull(); From 2dfa5d22f4d62efe98943c4e32cded255611b5a9 Mon Sep 17 00:00:00 2001 From: Morax Date: Fri, 21 Aug 2026 19:20:29 +0200 Subject: [PATCH 04/24] style: group increment helper overloads --- .../IncrementStrategyFinder.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs index 914e6ae56a..114506f8b9 100644 --- a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs @@ -260,23 +260,6 @@ private static CommitMessageIncrement ConsolidateMergedBranchIncrements( return result; } - private CommitMessageIncrement? FindCommitMessageIncrement( - EffectiveConfiguration configuration, IEnumerable commits, IReadOnlySet commitHistory) - { - if (configuration.CommitMessageIncrementing == CommitMessageIncrementMode.Disabled) - { - return null; - } - - commits = commits.Where(commit => commitHistory.Contains(commit.Sha)); - if (configuration.CommitMessageIncrementing == CommitMessageIncrementMode.MergeMessageOnly) - { - commits = commits.Where(commit => commit.Parents.Count > 1); - } - - return GetIncrementForCommits(configuration, [.. commits]); - } - private bool ShouldIncrementTaggedCommit( ICommit commit, ICommit? baseVersionSource, EffectiveConfiguration configuration, string? label) => !commit.Equals(baseVersionSource) @@ -489,6 +472,23 @@ private readonly record struct MergedBranchIncrement( ); } + private CommitMessageIncrement? FindCommitMessageIncrement( + EffectiveConfiguration configuration, IEnumerable commits, IReadOnlySet commitHistory) + { + if (configuration.CommitMessageIncrementing == CommitMessageIncrementMode.Disabled) + { + return null; + } + + commits = commits.Where(commit => commitHistory.Contains(commit.Sha)); + if (configuration.CommitMessageIncrementing == CommitMessageIncrementMode.MergeMessageOnly) + { + commits = commits.Where(commit => commit.Parents.Count > 1); + } + + return GetIncrementForCommits(configuration, [.. commits]); + } + private static Regex TryGetRegexOrDefault(string? messageRegex, Regex defaultRegex) => messageRegex == null ? defaultRegex From 91f66307ca27a955cc93e321bb94e32ba4f5adbd Mon Sep 17 00:00:00 2001 From: Morax Date: Fri, 21 Aug 2026 20:07:16 +0200 Subject: [PATCH 05/24] fix: respect merged history boundaries --- ...PreventIncrementOfMergedBranchScenarios.cs | 84 +++++++++++++++++++ .../IncrementStrategyFinder.cs | 10 ++- 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs index 046229e3bc..d4deb5876a 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs @@ -1,5 +1,6 @@ using GitVersion.Configuration; using GitVersion.Testing.Extensions; +using LibGit2Sharp; namespace GitVersion.Tests.IntegrationTests; @@ -330,6 +331,37 @@ public void HonorsPreventIncrementForTaggedMergedSourceTip() fixture.AssertFullSemver("2.0.0-1", configuration); } + [Test] + public void IgnoresFutureDatedTagWhenEvaluatingMergedSourceMessages() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + .WithIsMainBranch(true) + .WithTrackMergeMessage(true) + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Minor) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit("Breaking change +semver: major"); + var futureSignature = new Signature( + "A. U. Thor", "thor@valhalla.asgard.com", DateTimeOffset.Now.AddYears(10)); + var futureCommit = fixture.Repository.Commit( + "Breaking change +semver: major", futureSignature, futureSignature, + new CommitOptions { AmendPreviousCommit = true }); + fixture.ApplyTag("1.1.0"); + fixture.MergeTo("main", removeBranchAfterMerging: true); + var targetCommit = fixture.Repository.Head.Tip; + targetCommit.Parents.Count().ShouldBe(2); + futureCommit.Committer.When.ShouldBeGreaterThan(targetCommit.Committer.When); + + fixture.AssertFullSemver("2.0.0-2", configuration, commitId: targetCommit.Sha); + } + [Test] public void RetainsTargetAsPossibleHistoricalSourceBranch() { @@ -357,6 +389,58 @@ public void RetainsTargetAsPossibleHistoricalSourceBranch() fixture.AssertFullSemver("1.0.1-3", configuration); } + [Test] + public void ExcludesIgnoredBranchesFromHistoricalSourceInference() + { + var configuration = GitFlowConfigurationBuilder.New + .WithIgnoreConfiguration(new IgnoreConfiguration { Branches = ["^develop$"] }) + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("develop", builder => builder + .WithIncrement(IncrementStrategy.Minor) + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Inherit) + .WithSourceBranches("main", "develop") + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("develop"); + fixture.MakeACommit(); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main", removeBranchAfterMerging: true); + + fixture.AssertFullSemver("1.0.1-3", configuration); + } + + [Test] + public void StopsMergedSourceContributionsAtInterveningTargetTag() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Patch) + ).WithBranch("hotfix", builder => builder + .WithIncrement(IncrementStrategy.Patch) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("2.0.0"); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit("Breaking change +semver: major"); + fixture.MergeTo("main", removeBranchAfterMerging: true); + fixture.ApplyTag("1.0.0"); + fixture.BranchTo("hotfix/foo"); + fixture.MakeACommit("Hotfix +semver: patch"); + fixture.MergeTo("main", removeBranchAfterMerging: true); + + fixture.AssertFullSemver("2.0.1-4", configuration); + } + [Test] public void ResolvesInheritedIncrementFromHistoricalMergedBranchTip() { diff --git a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs index 114506f8b9..2dd55844ff 100644 --- a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs @@ -127,6 +127,11 @@ private IEnumerable GetIncrementsFromCommitHistory( foreach (var entry in history) { + if (!targetCommitHistory.Contains(entry.Commit.Sha)) + { + continue; + } + if (entry.MergedBranch is not { } mergedBranch) { targetSegment.Add(entry.Commit); @@ -267,6 +272,7 @@ private bool ShouldIncrementTaggedCommit( || !this.taggedSemanticVersionRepository .GetTaggedSemanticVersions( configuration.TagPrefixPattern, configuration.SemanticVersionFormat, configuration.Ignore)[commit] + .Where(versionWithTag => versionWithTag.Tag.Commit.When <= Context.CurrentCommit.When) .Any(versionWithTag => versionWithTag.Value.IsMatchForBranchSpecificLabel(label)); private IEnumerable GetSourceConfigurations( @@ -321,7 +327,8 @@ private IEnumerable FindClosestSourceBranches( IGitVersionConfiguration configuration, IRepositoryStore repositoryStore) { var candidates = repositoryStore.Branches - .Where(branch => IsConfiguredSourceBranch(branch, mergedBranchConfiguration, configuration)); + .Where(branch => !configuration.Ignore.IsBranchIgnored(branch.Name) + && IsConfiguredSourceBranch(branch, mergedBranchConfiguration, configuration)); var closestDistance = int.MaxValue; List result = []; @@ -501,6 +508,7 @@ private Dictionary.ValueCollection GetCommitHistory(string? tag [.. this.taggedSemanticVersionRepository .GetTaggedSemanticVersions(tagPrefix, semanticVersionFormat, ignore) .SelectMany(versionWithTags => versionWithTags) + .Where(versionWithTag => versionWithTag.Tag.Commit.When <= Context.CurrentCommit.When) .Where(versionWithTag => versionWithTag.Value.IsMatchForBranchSpecificLabel(label)) .Select(versionWithTag => versionWithTag.Tag.TargetSha)] ); From ca7c66d71866a037413aeef6d863178bee289274 Mon Sep 17 00:00:00 2001 From: Morax Date: Fri, 21 Aug 2026 20:43:39 +0200 Subject: [PATCH 06/24] fix: preserve historical merge increments --- .../FeatureBranchScenarios.cs | 6 +- ...PreventIncrementOfMergedBranchScenarios.cs | 39 ++++++ .../IncrementStrategyFinder.cs | 132 ++++++++++++++---- 3 files changed, 147 insertions(+), 30 deletions(-) diff --git a/src/GitVersion.Core.Tests/IntegrationTests/FeatureBranchScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/FeatureBranchScenarios.cs index 63ff14d548..06f1df605d 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/FeatureBranchScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/FeatureBranchScenarios.cs @@ -326,7 +326,9 @@ public void ShouldPickUpVersionFromMainAfterReleaseBranchMergedBack() // create a feature branch from main and verify the version fixture.BranchTo("feature/test"); - fixture.AssertFullSemver("1.0.1-test.1+2", configuration); + fixture.AssertFullSemver("1.1.0-test.1+2", configuration); + fixture.MakeACommit(); + fixture.AssertFullSemver("1.1.0-test.1+3", configuration); } } @@ -416,7 +418,7 @@ public void ShouldPickUpVersionFromMainAfterReleaseBranchMergedBack() // create a misnamed feature branch (i.e. it uses the default configuration) from main and verify the version fixture.BranchTo("misnamed"); - fixture.AssertFullSemver("1.0.1-misnamed.1+2", configuration); + fixture.AssertFullSemver("1.1.0-misnamed.1+2", configuration); } } } diff --git a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs index d4deb5876a..66f6ed974c 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs @@ -531,4 +531,43 @@ public void ResolvesRetainedInheritedBranchFromHistoricalSource() fixture.AssertFullSemver("1.1.0-3", configuration); } + + [Test] + public void ResolvesNestedInheritanceFromHistoricalSourceState() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("develop", builder => builder + .WithIncrement(IncrementStrategy.Minor) + ).WithBranch("support", builder => builder + .WithRegularExpression("^support$") + .WithIncrement(IncrementStrategy.Major) + ).WithBranch("integration", builder => builder + .WithRegularExpression("^integration$") + .WithIncrement(IncrementStrategy.Inherit) + .WithSourceBranches("develop", "support") + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Inherit) + .WithSourceBranches("integration") + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.CreateBranch("support"); + fixture.BranchTo("develop"); + fixture.MakeACommit(); + fixture.BranchTo("integration"); + fixture.MakeACommit(); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main", removeBranchAfterMerging: true); + fixture.Checkout("support"); + fixture.MakeACommit(); + fixture.MergeTo("integration"); + fixture.Checkout("main"); + + fixture.AssertFullSemver("1.1.0-4", configuration); + } } diff --git a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs index 2dd55844ff..4b003cb36d 100644 --- a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs @@ -16,10 +16,11 @@ internal class IncrementStrategyFinder( private readonly Dictionary commitIncrementCache = []; private readonly Dictionary<(string Commit, bool IsMergedTipBaseVersionSource, EffectiveConfiguration Target), MergedBranchIncrement[]> mergedBranchIncrementCache = []; - private readonly Dictionary<(string Branch, string? Tip), EffectiveBranchConfiguration[]> effectiveBranchConfigurationCache = []; + private readonly Dictionary<(string Branch, string? Tip), EffectiveConfiguration[]> effectiveConfigurationCache = []; private readonly Dictionary> firstParentHistoryCache = []; private readonly Dictionary> headCommitsMapCache = []; private readonly Dictionary headCommitsCache = []; + private readonly Dictionary linearMainBranchHistoryCache = []; private readonly Lazy contextLazy = contextLazy.NotNull(); private readonly IRepositoryStore repositoryStore = repositoryStore.NotNull(); @@ -38,9 +39,7 @@ public VersionField DetermineIncrementedField( var targetIncrement = DetermineIncrementedFieldInternal( currentCommit, baseVersionSource, shouldIncrement, configuration, label); - if (!configuration.IsMainBranch - || !configuration.TrackMergeMessage - || Context.Configuration.GetBranchConfiguration(Context.CurrentBranch.Name).IsMainBranch != true) + if (!configuration.TrackMergeMessage || !HasLinearMainBranchHistory(currentCommit)) { return targetIncrement.Increment; } @@ -286,10 +285,7 @@ private IEnumerable GetSourceConfigurations( if (existingBranch is not null) { - var configurations = GetEffectiveBranchConfigurations(existingBranch) - .Select(candidate => candidate.Value) - .Distinct() - .ToArray(); + var configurations = GetEffectiveConfigurations(existingBranch); if (configurations.Length != 0) { return configurations; @@ -304,9 +300,9 @@ private IEnumerable GetSourceConfigurations( var inheritedConfigurations = FindClosestSourceBranches( mergedBranchTip, sourceBranchConfiguration, Context.Configuration, this.repositoryStore) - .SelectMany(GetEffectiveBranchConfigurations) + .SelectMany(source => GetEffectiveConfigurations(source.Branch, source.Tip)) .Select(source => new EffectiveConfiguration( - Context.Configuration, sourceBranchConfiguration, source.Value)) + Context.Configuration, sourceBranchConfiguration, source)) .Distinct() .ToArray(); @@ -315,14 +311,60 @@ private IEnumerable GetSourceConfigurations( : [Context.Configuration.GetEffectiveConfiguration(mergedBranch, targetConfiguration)]; } - private EffectiveBranchConfiguration[] GetEffectiveBranchConfigurations(IBranch branch) => - this.effectiveBranchConfigurationCache.GetOrAdd( - (branch.Name.ToString(), branch.Tip?.Sha), - () => [.. this.effectiveBranchConfigurationFinder - .GetConfigurations(branch, Context.Configuration) - ]); + private EffectiveConfiguration[] GetEffectiveConfigurations(IBranch branch, ICommit? tip = null) + { + tip ??= branch.Tip; + return this.effectiveConfigurationCache.GetOrAdd( + (branch.Name.ToString(), tip?.Sha), + () => branch.Tip?.Equals(tip) == true + ? [.. this.effectiveBranchConfigurationFinder + .GetConfigurations(branch, Context.Configuration) + .Select(configuration => configuration.Value) + .Distinct()] + : [.. GetHistoricalEffectiveConfigurations( + branch, tip, new(StringComparer.OrdinalIgnoreCase)).Distinct()] + ); + } - private IEnumerable FindClosestSourceBranches( + private IEnumerable GetHistoricalEffectiveConfigurations( + IBranch branch, ICommit? tip, HashSet traversedBranches) + { + if (tip is null || !traversedBranches.Add(branch.Name.WithoutOrigin)) + { + yield break; + } + + var branchConfiguration = Context.Configuration.GetBranchConfiguration(branch.Name); + if (branchConfiguration.Increment != IncrementStrategy.Inherit) + { + yield return new(Context.Configuration, branchConfiguration); + yield break; + } + + var sources = FindClosestSourceBranches( + tip, branchConfiguration, Context.Configuration, this.repositoryStore) + .ToArray(); + if (sources.Length == 0) + { + if (Context.Configuration.Increment != IncrementStrategy.Inherit) + { + yield return new(Context.Configuration, branchConfiguration); + } + yield break; + } + + foreach (var source in sources) + { + foreach (var parentConfiguration in GetHistoricalEffectiveConfigurations( + source.Branch, source.Tip, traversedBranches)) + { + yield return new( + Context.Configuration, branchConfiguration, parentConfiguration); + } + } + } + + private IEnumerable FindClosestSourceBranches( ICommit mergedBranchTip, IBranchConfiguration mergedBranchConfiguration, IGitVersionConfiguration configuration, IRepositoryStore repositoryStore) { @@ -331,7 +373,7 @@ private IEnumerable FindClosestSourceBranches( && IsConfiguredSourceBranch(branch, mergedBranchConfiguration, configuration)); var closestDistance = int.MaxValue; - List result = []; + List result = []; foreach (var candidate in candidates) { if (candidate.Tip is null) @@ -339,28 +381,28 @@ private IEnumerable FindClosestSourceBranches( continue; } - if (GetFirstParentSourceDistance(mergedBranchTip, candidate.Tip) is not { } distance) + if (FindFirstParentSource(mergedBranchTip, candidate.Tip) is not { } source) { continue; } - if (distance < closestDistance) + if (source.Distance < closestDistance) { - closestDistance = distance; + closestDistance = source.Distance; result.Clear(); } - if (distance == closestDistance) + if (source.Distance == closestDistance) { - result.Add(candidate); + result.Add(new(candidate, source.Commit)); } } return result - .GroupBy(candidate => candidate.Name.WithoutOrigin, StringComparer.OrdinalIgnoreCase) - .Select(group => group.MinBy(candidate => candidate.IsRemote)) - .OfType(); + .GroupBy(candidate => candidate.Branch.Name.WithoutOrigin, StringComparer.OrdinalIgnoreCase) + .Select(group => group.OrderBy(candidate => candidate.Branch.IsRemote).First()); } - private int? GetFirstParentSourceDistance(ICommit mergedBranchTip, ICommit sourceBranchTip) + private (ICommit Commit, int Distance)? FindFirstParentSource( + ICommit mergedBranchTip, ICommit sourceBranchTip) { var sourceHistory = this.firstParentHistoryCache.GetOrAdd(sourceBranchTip.Sha, () => { @@ -377,13 +419,45 @@ private IEnumerable FindClosestSourceBranches( { if (sourceHistory.Contains(commit.Sha)) { - return distance; + return (commit, distance); } distance++; } return null; } + private bool HasLinearMainBranchHistory(ICommit commit) => + this.linearMainBranchHistoryCache.GetOrAdd(commit.Sha, () => + { + var closestDistance = int.MaxValue; + foreach (var branch in this.repositoryStore.Branches) + { + if (Context.Configuration.Ignore.IsBranchIgnored(branch.Name) + || Context.Configuration.GetBranchConfiguration(branch.Name).IsMainBranch != true + || branch.Tip is not { } tip + || FindFirstParentSource(commit, tip) is not { } source) + { + continue; + } + closestDistance = Math.Min(closestDistance, source.Distance); + } + + if (closestDistance == int.MaxValue) + { + return false; + } + + for (ICommit? current = commit; closestDistance > 0; + current = current?.Parents.FirstOrDefault(), closestDistance--) + { + if (current?.IsMergeCommit == true) + { + return false; + } + } + return true; + }); + private static bool IsConfiguredSourceBranch( IBranch candidate, IBranchConfiguration mergedBranchConfiguration, IGitVersionConfiguration configuration) => @@ -410,6 +484,8 @@ private static CommitMessageIncrement SelectIncrement( private readonly record struct MergedBranchIncrement( CommitMessageIncrement Increment, bool? PreventIncrementWhenBranchMerged); + private readonly record struct HistoricalSourceBranch(IBranch Branch, ICommit Tip); + private readonly record struct CommitHistoryEntry(ICommit Commit, ReferenceName? MergedBranch); private CommitMessageIncrement? GetIncrementForCommits(EffectiveConfiguration configuration, ICommit[] commits) From 974fc0e0318284954be778fb95f87a68c97468fb Mon Sep 17 00:00:00 2001 From: Morax Date: Fri, 21 Aug 2026 20:56:20 +0200 Subject: [PATCH 07/24] fix: preserve historical inheritance alternatives --- ...PreventIncrementOfMergedBranchScenarios.cs | 66 +++++++++++++++++++ .../IncrementStrategyFinder.cs | 6 +- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs index 66f6ed974c..6d010e1d47 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs @@ -415,6 +415,27 @@ public void ExcludesIgnoredBranchesFromHistoricalSourceInference() fixture.AssertFullSemver("1.0.1-3", configuration); } + [Test] + public void RetainsIgnoredCurrentMainForMergedIncrementResolution() + { + var configuration = GitFlowConfigurationBuilder.New + .WithIgnoreConfiguration(new IgnoreConfiguration { Branches = ["^main$"] }) + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Minor) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main", removeBranchAfterMerging: true); + + fixture.AssertFullSemver("1.1.0-2", configuration); + } + [Test] public void StopsMergedSourceContributionsAtInterveningTargetTag() { @@ -570,4 +591,49 @@ public void ResolvesNestedInheritanceFromHistoricalSourceState() fixture.AssertFullSemver("1.1.0-4", configuration); } + + [Test] + public void ResolvesAllSiblingHistoricalInheritancePaths() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("develop", builder => builder + .WithIncrement(IncrementStrategy.Minor) + ).WithBranch("integration-a", builder => builder + .WithRegularExpression("^integration-a$") + .WithIncrement(IncrementStrategy.Inherit) + .WithSourceBranches("develop") + .WithPreventIncrementWhenBranchMerged(true) + ).WithBranch("integration-b", builder => builder + .WithRegularExpression("^integration-b$") + .WithIncrement(IncrementStrategy.Inherit) + .WithSourceBranches("develop") + .WithPreventIncrementWhenBranchMerged(false) + ).WithBranch("aggregate", builder => builder + .WithRegularExpression("^aggregate$") + .WithIncrement(IncrementStrategy.Inherit) + .WithSourceBranches("integration-a", "integration-b") + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Inherit) + .WithSourceBranches("aggregate") + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("develop"); + fixture.MakeACommit(); + fixture.CreateBranch("integration-a"); + fixture.CreateBranch("integration-b"); + fixture.BranchTo("aggregate"); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main", removeBranchAfterMerging: true); + fixture.Checkout("aggregate"); + fixture.MakeACommit(); + fixture.Checkout("main"); + + fixture.AssertFullSemver("1.1.0-3", configuration); + } } diff --git a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs index 4b003cb36d..5a60351ec8 100644 --- a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs @@ -356,7 +356,8 @@ private IEnumerable GetHistoricalEffectiveConfigurations foreach (var source in sources) { foreach (var parentConfiguration in GetHistoricalEffectiveConfigurations( - source.Branch, source.Tip, traversedBranches)) + source.Branch, source.Tip, + new(traversedBranches, traversedBranches.Comparer))) { yield return new( Context.Configuration, branchConfiguration, parentConfiguration); @@ -432,8 +433,7 @@ private bool HasLinearMainBranchHistory(ICommit commit) => var closestDistance = int.MaxValue; foreach (var branch in this.repositoryStore.Branches) { - if (Context.Configuration.Ignore.IsBranchIgnored(branch.Name) - || Context.Configuration.GetBranchConfiguration(branch.Name).IsMainBranch != true + if (Context.Configuration.GetBranchConfiguration(branch.Name).IsMainBranch != true || branch.Tip is not { } tip || FindFirstParentSource(commit, tip) is not { } source) { From c089e7c524286b875a0e40e289711a5275b3987c Mon Sep 17 00:00:00 2001 From: Morax Date: Fri, 21 Aug 2026 21:17:17 +0200 Subject: [PATCH 08/24] fix: handle merged increment edge cases --- ...PreventIncrementOfMergedBranchScenarios.cs | 113 +++++++++++++++++- .../EffectiveBranchConfigurationFinder.cs | 4 +- .../IncrementStrategyFinder.cs | 34 ++++-- 3 files changed, 140 insertions(+), 11 deletions(-) diff --git a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs index 6d010e1d47..b1d7ec4682 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs @@ -8,7 +8,7 @@ namespace GitVersion.Tests.IntegrationTests; [Parallelizable(ParallelScope.All)] public class PreventIncrementOfMergedBranchScenarios { - [TestCase(false, false, "1.0.1-2")] + [TestCase(false, false, "1.1.0-2")] [TestCase(false, true, "1.0.1-2")] [TestCase(false, null, "1.1.0-2")] [TestCase(true, false, "1.1.0-2")] @@ -436,6 +436,55 @@ public void RetainsIgnoredCurrentMainForMergedIncrementResolution() fixture.AssertFullSemver("1.1.0-2", configuration); } + [Test] + public void HonorsInheritedMainBranchFlag() + { + var configuration = GitFlowConfigurationBuilder.New + .WithIsMainBranch(true) + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + .WithIsMainBranch(null) + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Minor) + .WithIsMainBranch(false) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main", removeBranchAfterMerging: true); + + fixture.AssertFullSemver("1.1.0-2", configuration); + } + + [Test] + public void RetainsIgnoredCurrentTargetAsHistoricalSource() + { + var configuration = GitFlowConfigurationBuilder.New + .WithIgnoreConfiguration(new IgnoreConfiguration { Branches = ["^main$"] }) + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("develop", builder => builder + .WithIncrement(IncrementStrategy.Minor) + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Inherit) + .WithSourceBranches("main", "develop") + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.CreateBranch("develop"); + fixture.MakeACommit(); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main", removeBranchAfterMerging: true); + + fixture.AssertFullSemver("1.0.1-3", configuration); + } + [Test] public void StopsMergedSourceContributionsAtInterveningTargetTag() { @@ -462,6 +511,30 @@ public void StopsMergedSourceContributionsAtInterveningTargetTag() fixture.AssertFullSemver("2.0.1-4", configuration); } + [Test] + public void PreservesCommitMessageDirectivesFromUnrecognizedMergedHistory() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("hotfix", builder => builder + .WithIncrement(IncrementStrategy.Patch) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("topic/foo"); + fixture.MakeACommit("Breaking change +semver: major"); + fixture.Checkout("main"); + fixture.Repository.MergeNoFF("topic/foo", "Integrate topic"); + fixture.BranchTo("hotfix/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main", removeBranchAfterMerging: true); + + fixture.AssertFullSemver("2.0.0-4", configuration); + } + [Test] public void ResolvesInheritedIncrementFromHistoricalMergedBranchTip() { @@ -636,4 +709,42 @@ public void ResolvesAllSiblingHistoricalInheritancePaths() fixture.AssertFullSemver("1.1.0-3", configuration); } + + [Test] + public void ResolvesAllSiblingInheritancePathsForRetainedTip() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("develop", builder => builder + .WithIncrement(IncrementStrategy.Minor) + ).WithBranch("integration-a", builder => builder + .WithRegularExpression("^integration-a$") + .WithIncrement(IncrementStrategy.Inherit) + .WithSourceBranches("develop") + .WithPreventIncrementWhenBranchMerged(true) + ).WithBranch("integration-b", builder => builder + .WithRegularExpression("^integration-b$") + .WithIncrement(IncrementStrategy.Inherit) + .WithSourceBranches("develop") + .WithPreventIncrementWhenBranchMerged(false) + ).WithBranch("aggregate", builder => builder + .WithRegularExpression("^aggregate$") + .WithIncrement(IncrementStrategy.Inherit) + .WithSourceBranches("integration-a", "integration-b") + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("develop"); + fixture.MakeACommit(); + fixture.CreateBranch("integration-a"); + fixture.CreateBranch("integration-b"); + fixture.BranchTo("aggregate"); + fixture.MakeACommit(); + fixture.MergeTo("main"); + + fixture.AssertFullSemver("1.1.0-3", configuration); + } } diff --git a/src/GitVersion.Core/VersionCalculation/EffectiveBranchConfigurationFinder.cs b/src/GitVersion.Core/VersionCalculation/EffectiveBranchConfigurationFinder.cs index b66c6af3ec..feac386118 100644 --- a/src/GitVersion.Core/VersionCalculation/EffectiveBranchConfigurationFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/EffectiveBranchConfigurationFinder.cs @@ -64,7 +64,9 @@ private IEnumerable GetEffectiveConfigurationsRecu foreach (var sourceBranch in sourceBranches) { foreach (var effectiveConfiguration - in GetEffectiveConfigurationsRecursive(sourceBranch, configuration, branchConfiguration, traversedBranches, resolvePullRequestTarget: false)) + in GetEffectiveConfigurationsRecursive( + sourceBranch, configuration, branchConfiguration, + new(traversedBranches, traversedBranches.Comparer), resolvePullRequestTarget: false)) { yield return effectiveConfiguration; } diff --git a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs index 5a60351ec8..831b2a4895 100644 --- a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs @@ -111,7 +111,7 @@ private IEnumerable GetIncrementsFromCommitHistory( targetIncrement = DetermineIncrementedFieldInternal( currentCommit, baseVersionSource, shouldIncrement, targetConfiguration, targetLabel, - history.Select(item => item.Commit.Sha).ToHashSet()); + history.SelectMany(item => item.TargetCommits).Select(commit => commit.Sha).ToHashSet()); var targetCommitHistory = GetCommitHistory( targetConfiguration.TagPrefixPattern, @@ -133,7 +133,7 @@ private IEnumerable GetIncrementsFromCommitHistory( if (entry.MergedBranch is not { } mergedBranch) { - targetSegment.Add(entry.Commit); + targetSegment.AddRange(entry.TargetCommits); continue; } @@ -195,7 +195,21 @@ private IEnumerable GetFirstParentCommitHistory( mergedBranch = mergeMessage.MergedBranch; } - yield return new(commit, mergedBranch); + ICommit[] targetCommits = [commit]; + if (commit.IsMergeCommit && mergedBranch is null) + { + var firstParent = commit.Parents[0]; + targetCommits = + [ + commit, + .. commit.Parents.Skip(1) + .SelectMany(parent => this.repositoryStore.GetCommitLog( + firstParent, parent, targetConfiguration.Ignore)) + .DistinctBy(parent => parent.Sha) + ]; + } + + yield return new(commit, mergedBranch, targetCommits); } } @@ -370,7 +384,8 @@ private IEnumerable FindClosestSourceBranches( IGitVersionConfiguration configuration, IRepositoryStore repositoryStore) { var candidates = repositoryStore.Branches - .Where(branch => !configuration.Ignore.IsBranchIgnored(branch.Name) + .Where(branch => (!configuration.Ignore.IsBranchIgnored(branch.Name) + || branch.Name.EquivalentTo(Context.CurrentBranch.Name.WithoutOrigin)) && IsConfiguredSourceBranch(branch, mergedBranchConfiguration, configuration)); var closestDistance = int.MaxValue; @@ -433,7 +448,7 @@ private bool HasLinearMainBranchHistory(ICommit commit) => var closestDistance = int.MaxValue; foreach (var branch in this.repositoryStore.Branches) { - if (Context.Configuration.GetBranchConfiguration(branch.Name).IsMainBranch != true + if (!Context.Configuration.GetEffectiveConfiguration(branch.Name).IsMainBranch || branch.Tip is not { } tip || FindFirstParentSource(commit, tip) is not { } source) { @@ -476,9 +491,9 @@ private static CommitMessageIncrement SelectIncrement( : sourceIncrement; } - return preventIncrementWhenBranchMerged is null - ? new(targetIncrement.Consolidate(sourceIncrement.Increment), sourceIncrement.VersionBumpNeedsToBeReset) - : new(targetIncrement, VersionBumpNeedsToBeReset: false); + return preventIncrementWhenBranchMerged == true + ? new(targetIncrement, VersionBumpNeedsToBeReset: false) + : new(targetIncrement.Consolidate(sourceIncrement.Increment), sourceIncrement.VersionBumpNeedsToBeReset); } private readonly record struct MergedBranchIncrement( @@ -486,7 +501,8 @@ private readonly record struct MergedBranchIncrement( private readonly record struct HistoricalSourceBranch(IBranch Branch, ICommit Tip); - private readonly record struct CommitHistoryEntry(ICommit Commit, ReferenceName? MergedBranch); + private readonly record struct CommitHistoryEntry( + ICommit Commit, ReferenceName? MergedBranch, IReadOnlyList TargetCommits); private CommitMessageIncrement? GetIncrementForCommits(EffectiveConfiguration configuration, ICommit[] commits) { From 4d14b37b3ea0fd33a89e19ee3f8eacce63471494 Mon Sep 17 00:00:00 2001 From: Morax Date: Fri, 21 Aug 2026 21:34:55 +0200 Subject: [PATCH 09/24] fix: scope increment cache and merge resets --- ...PreventIncrementOfMergedBranchScenarios.cs | 22 ++++++++ .../IncrementStrategyFinderTests.cs | 51 +++++++++++++++++++ .../IncrementStrategyFinder.cs | 22 ++++---- 3 files changed, 86 insertions(+), 9 deletions(-) create mode 100644 src/GitVersion.Core.Tests/VersionCalculation/IncrementStrategyFinderTests.cs diff --git a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs index b1d7ec4682..4aaf094db2 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs @@ -149,6 +149,28 @@ public void LatestMergedSourceResetDiscardsEarlierMergedIncrement() fixture.AssertFullSemver("1.0.1-4", configuration); } + [Test] + public void NewerMergedSourceResetDiscardsEarlierTargetDirective() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(false) + ).WithBranch("hotfix", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementWhenBranchMerged(false) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.MakeACommit("Breaking change +semver: major"); + fixture.BranchTo("hotfix/foo"); + fixture.MakeACommit("Hotfix =semver: patch"); + fixture.MergeTo("main", removeBranchAfterMerging: true); + + fixture.AssertFullSemver("1.0.1-3", configuration); + } + [TestCase(false)] [TestCase(true)] public void IncludesTargetIncrementForTargetCommit(bool commitAfterMerge) diff --git a/src/GitVersion.Core.Tests/VersionCalculation/IncrementStrategyFinderTests.cs b/src/GitVersion.Core.Tests/VersionCalculation/IncrementStrategyFinderTests.cs new file mode 100644 index 0000000000..c236c07db4 --- /dev/null +++ b/src/GitVersion.Core.Tests/VersionCalculation/IncrementStrategyFinderTests.cs @@ -0,0 +1,51 @@ +using GitVersion.Configuration; +using GitVersion.Git; +using GitVersion.VersionCalculation; + +namespace GitVersion.Tests.VersionCalculation; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class IncrementStrategyFinderTests +{ + [Test] + public void ParsesCachedCommitWithTheRequestedConfiguration() + { + var commit = Substitute.For(); + commit.Sha.Returns("0123456789012345678901234567890123456789"); + commit.Message.Returns("feature: custom increment"); + + var finder = new IncrementStrategyFinder( + new(() => throw new InvalidOperationException()), + Substitute.For(), + new StubTaggedSemanticVersionRepository(), + Substitute.For(), + Substitute.For()); + var nonMatchingConfiguration = GitFlowConfigurationBuilder.New + .WithMinorVersionBumpMessage("^minor:") + .Build(); + var matchingConfiguration = GitFlowConfigurationBuilder.New + .WithMinorVersionBumpMessage("^feature:") + .Build(); + + finder.GetIncrementForcedByCommit(commit, nonMatchingConfiguration).Increment + .ShouldBe(VersionField.None); + finder.GetIncrementForcedByCommit(commit, matchingConfiguration).Increment + .ShouldBe(VersionField.Minor); + } + + private sealed class StubTaggedSemanticVersionRepository : ITaggedSemanticVersionRepository + { + public ILookup GetTaggedSemanticVersionsOfBranch( + IBranch branch, string? tagPrefix, SemanticVersionFormat format, IIgnoreConfiguration ignore) => + throw new NotSupportedException(); + + public ILookup GetTaggedSemanticVersionsOfMergeTarget( + IBranch branch, string? tagPrefix, SemanticVersionFormat format, IIgnoreConfiguration ignore) => + throw new NotSupportedException(); + + public ILookup GetTaggedSemanticVersions( + string? tagPrefix, SemanticVersionFormat format, IIgnoreConfiguration ignore) => + throw new NotSupportedException(); + } +} diff --git a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs index 831b2a4895..079a232f19 100644 --- a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs @@ -13,7 +13,7 @@ internal class IncrementStrategyFinder( IEnvironment environment) : IIncrementStrategyFinder { - private readonly Dictionary commitIncrementCache = []; + private readonly Dictionary commitIncrementCache = []; private readonly Dictionary<(string Commit, bool IsMergedTipBaseVersionSource, EffectiveConfiguration Target), MergedBranchIncrement[]> mergedBranchIncrementCache = []; private readonly Dictionary<(string Branch, string? Tip), EffectiveConfiguration[]> effectiveConfigurationCache = []; @@ -108,11 +108,6 @@ private IEnumerable GetIncrementsFromCommitHistory( yield break; } - targetIncrement = DetermineIncrementedFieldInternal( - currentCommit, baseVersionSource, shouldIncrement, - targetConfiguration, targetLabel, - history.SelectMany(item => item.TargetCommits).Select(commit => commit.Sha).ToHashSet()); - var targetCommitHistory = GetCommitHistory( targetConfiguration.TagPrefixPattern, targetConfiguration.SemanticVersionFormat, @@ -122,6 +117,8 @@ private IEnumerable GetIncrementsFromCommitHistory( targetConfiguration.Ignore) .Select(commit => commit.Sha) .ToHashSet(); + var defaultTargetIncrement = DetermineIncrementedField( + commitMessageIncrement: null, shouldIncrement, targetConfiguration).Increment; List targetSegment = []; foreach (var entry in history) @@ -157,7 +154,7 @@ private IEnumerable GetIncrementsFromCommitHistory( if (sourceIncrements.Length != 0) { yield return ConsolidateMergedBranchIncrements( - sourceIncrements, targetConfiguration, targetIncrement.Increment); + sourceIncrements, targetConfiguration, defaultTargetIncrement); } } @@ -504,6 +501,9 @@ private readonly record struct MergedBranchIncrement( private readonly record struct CommitHistoryEntry( ICommit Commit, ReferenceName? MergedBranch, IReadOnlyList TargetCommits); + private readonly record struct CommitIncrementCacheKey( + string Commit, Regex Major, Regex Minor, Regex Patch, Regex NoBump, Regex Reset); + private CommitMessageIncrement? GetIncrementForCommits(EffectiveConfiguration configuration, ICommit[] commits) { commits.NotNull(); @@ -672,8 +672,11 @@ private ICommit[] GetHeadCommits(ICommit? headCommit, IIgnoreConfiguration ignor [.. this.repositoryStore.GetCommitsReacheableFromHead(headCommit, ignore)]); private CommitMessageIncrement? GetIncrementFromCommit( - ICommit commit, Regex majorRegex, Regex minorRegex, Regex patchRegex, Regex noBumpRegex, Regex versionBumpResetRegex) => - this.commitIncrementCache.GetOrAdd(commit.Sha, () => + ICommit commit, Regex majorRegex, Regex minorRegex, Regex patchRegex, Regex noBumpRegex, Regex versionBumpResetRegex) + { + var key = new CommitIncrementCacheKey( + commit.Sha, majorRegex, minorRegex, patchRegex, noBumpRegex, versionBumpResetRegex); + return this.commitIncrementCache.GetOrAdd(key, () => { var increment = GetIncrementFromMessage(commit.Message, majorRegex, minorRegex, patchRegex, noBumpRegex); if (!increment.HasValue) @@ -683,6 +686,7 @@ private ICommit[] GetHeadCommits(ICommit? headCommit, IIgnoreConfiguration ignor return new(increment.Value, versionBumpResetRegex.IsMatch(commit.Message)); }); + } private static VersionField? GetIncrementFromMessage(string message, Regex majorRegex, Regex minorRegex, Regex patchRegex, Regex noBumpRegex) { From e9e20ad43b2fa974f1d6ecfb525787e5530a5b9e Mon Sep 17 00:00:00 2001 From: Morax Date: Fri, 21 Aug 2026 21:54:15 +0200 Subject: [PATCH 10/24] fix: preserve inherited main branch status --- ...PreventIncrementOfMergedBranchScenarios.cs | 27 +++++++++++++++++++ .../IncrementStrategyFinder.cs | 26 ++++++++++++++---- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs index 4aaf094db2..f76fe53c12 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs @@ -481,6 +481,33 @@ public void HonorsInheritedMainBranchFlag() fixture.AssertFullSemver("1.1.0-2", configuration); } + [Test] + public void HonorsMainBranchFlagInheritedFromSourceBranch() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Inherit) + .WithSourceBranches("develop") + .WithPreventIncrementOfMergedBranch(true) + .WithIsMainBranch(null) + ).WithBranch("develop", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithIsMainBranch(true) + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Minor) + .WithIsMainBranch(false) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.CreateBranch("develop"); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main", removeBranchAfterMerging: true); + + fixture.AssertFullSemver("1.1.0-2", configuration); + } + [Test] public void RetainsIgnoredCurrentTargetAsHistoricalSource() { diff --git a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs index 079a232f19..6c6080e276 100644 --- a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs @@ -20,7 +20,7 @@ internal class IncrementStrategyFinder( private readonly Dictionary> firstParentHistoryCache = []; private readonly Dictionary> headCommitsMapCache = []; private readonly Dictionary headCommitsCache = []; - private readonly Dictionary linearMainBranchHistoryCache = []; + private readonly Dictionary<(string Commit, EffectiveConfiguration Target), bool> linearMainBranchHistoryCache = []; private readonly Lazy contextLazy = contextLazy.NotNull(); private readonly IRepositoryStore repositoryStore = repositoryStore.NotNull(); @@ -39,7 +39,7 @@ public VersionField DetermineIncrementedField( var targetIncrement = DetermineIncrementedFieldInternal( currentCommit, baseVersionSource, shouldIncrement, configuration, label); - if (!configuration.TrackMergeMessage || !HasLinearMainBranchHistory(currentCommit)) + if (!configuration.TrackMergeMessage || !HasLinearMainBranchHistory(currentCommit, configuration)) { return targetIncrement.Increment; } @@ -439,13 +439,23 @@ private IEnumerable FindClosestSourceBranches( return null; } - private bool HasLinearMainBranchHistory(ICommit commit) => - this.linearMainBranchHistoryCache.GetOrAdd(commit.Sha, () => + private bool HasLinearMainBranchHistory(ICommit commit, EffectiveConfiguration targetConfiguration) + { + if (IsPullRequestBranch(Context.CurrentBranch, Context.Configuration)) + { + return false; + } + + return this.linearMainBranchHistoryCache.GetOrAdd((commit.Sha, targetConfiguration), () => { var closestDistance = int.MaxValue; foreach (var branch in this.repositoryStore.Branches) { - if (!Context.Configuration.GetEffectiveConfiguration(branch.Name).IsMainBranch + var isCurrentBranch = branch.Name.EquivalentTo(Context.CurrentBranch.Name.WithoutOrigin); + var isMainBranch = isCurrentBranch + ? targetConfiguration.IsMainBranch + : GetEffectiveConfigurations(branch).Any(configuration => configuration.IsMainBranch); + if (!isMainBranch || branch.Tip is not { } tip || FindFirstParentSource(commit, tip) is not { } source) { @@ -469,6 +479,12 @@ private bool HasLinearMainBranchHistory(ICommit commit) => } return true; }); + } + + private static bool IsPullRequestBranch(IBranch branch, IGitVersionConfiguration configuration) => + branch.Name.IsPullRequest + || configuration.Branches.TryGetValue(ConfigurationConstants.PullRequestBranchKey, out var pullRequestConfiguration) + && pullRequestConfiguration.IsMatch(branch.Name.WithoutOrigin); private static bool IsConfiguredSourceBranch( IBranch candidate, IBranchConfiguration mergedBranchConfiguration, From 5d66de71173075279f4077eabfaa569dcd4b39cd Mon Sep 17 00:00:00 2001 From: Morax Date: Fri, 21 Aug 2026 22:05:00 +0200 Subject: [PATCH 11/24] refactor: address sonar findings --- .../IncrementStrategyFinderTests.cs | 29 ++++++++-- .../IncrementStrategyFinder.cs | 56 ++++++++++--------- 2 files changed, 54 insertions(+), 31 deletions(-) diff --git a/src/GitVersion.Core.Tests/VersionCalculation/IncrementStrategyFinderTests.cs b/src/GitVersion.Core.Tests/VersionCalculation/IncrementStrategyFinderTests.cs index c236c07db4..c014e7457b 100644 --- a/src/GitVersion.Core.Tests/VersionCalculation/IncrementStrategyFinderTests.cs +++ b/src/GitVersion.Core.Tests/VersionCalculation/IncrementStrategyFinderTests.cs @@ -36,16 +36,33 @@ public void ParsesCachedCommitWithTheRequestedConfiguration() private sealed class StubTaggedSemanticVersionRepository : ITaggedSemanticVersionRepository { - public ILookup GetTaggedSemanticVersionsOfBranch( - IBranch branch, string? tagPrefix, SemanticVersionFormat format, IIgnoreConfiguration ignore) => + ILookup ITaggedSemanticVersionRepository.GetTaggedSemanticVersionsOfBranch( + IBranch branch, string? tagPrefix, SemanticVersionFormat format, IIgnoreConfiguration ignore) + { + _ = branch; + _ = tagPrefix; + _ = format; + _ = ignore; throw new NotSupportedException(); + } - public ILookup GetTaggedSemanticVersionsOfMergeTarget( - IBranch branch, string? tagPrefix, SemanticVersionFormat format, IIgnoreConfiguration ignore) => + ILookup ITaggedSemanticVersionRepository.GetTaggedSemanticVersionsOfMergeTarget( + IBranch branch, string? tagPrefix, SemanticVersionFormat format, IIgnoreConfiguration ignore) + { + _ = branch; + _ = tagPrefix; + _ = format; + _ = ignore; throw new NotSupportedException(); + } - public ILookup GetTaggedSemanticVersions( - string? tagPrefix, SemanticVersionFormat format, IIgnoreConfiguration ignore) => + ILookup ITaggedSemanticVersionRepository.GetTaggedSemanticVersions( + string? tagPrefix, SemanticVersionFormat format, IIgnoreConfiguration ignore) + { + _ = tagPrefix; + _ = format; + _ = ignore; throw new NotSupportedException(); + } } } diff --git a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs index 6c6080e276..ffa2b26b8b 100644 --- a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs @@ -448,37 +448,43 @@ private bool HasLinearMainBranchHistory(ICommit commit, EffectiveConfiguration t return this.linearMainBranchHistoryCache.GetOrAdd((commit.Sha, targetConfiguration), () => { - var closestDistance = int.MaxValue; - foreach (var branch in this.repositoryStore.Branches) - { - var isCurrentBranch = branch.Name.EquivalentTo(Context.CurrentBranch.Name.WithoutOrigin); - var isMainBranch = isCurrentBranch - ? targetConfiguration.IsMainBranch - : GetEffectiveConfigurations(branch).Any(configuration => configuration.IsMainBranch); - if (!isMainBranch - || branch.Tip is not { } tip - || FindFirstParentSource(commit, tip) is not { } source) - { - continue; - } - closestDistance = Math.Min(closestDistance, source.Distance); - } + var closestDistance = GetClosestMainBranchDistance(commit, targetConfiguration); + return closestDistance != int.MaxValue && !ContainsMergeCommit(commit, closestDistance); + }); + } - if (closestDistance == int.MaxValue) + private int GetClosestMainBranchDistance(ICommit commit, EffectiveConfiguration targetConfiguration) + { + var closestDistance = int.MaxValue; + foreach (var branch in this.repositoryStore.Branches) + { + if (!IsMainBranch(branch, targetConfiguration) + || branch.Tip is not { } tip + || FindFirstParentSource(commit, tip) is not { } source) { - return false; + continue; } + closestDistance = Math.Min(closestDistance, source.Distance); + } + return closestDistance; + } + + private bool IsMainBranch(IBranch branch, EffectiveConfiguration targetConfiguration) => + branch.Name.EquivalentTo(Context.CurrentBranch.Name.WithoutOrigin) + ? targetConfiguration.IsMainBranch + : GetEffectiveConfigurations(branch).Any(configuration => configuration.IsMainBranch); - for (ICommit? current = commit; closestDistance > 0; - current = current?.Parents.FirstOrDefault(), closestDistance--) + private static bool ContainsMergeCommit(ICommit commit, int distance) + { + for (ICommit? current = commit; distance > 0; + current = current?.Parents.FirstOrDefault(), distance--) + { + if (current?.IsMergeCommit == true) { - if (current?.IsMergeCommit == true) - { - return false; - } + return true; } - return true; - }); + } + return false; } private static bool IsPullRequestBranch(IBranch branch, IGitVersionConfiguration configuration) => From 0fc1a42dd7c5a7c77292e42e78a4ceb1c1fddc5a Mon Sep 17 00:00:00 2001 From: Morax Date: Fri, 21 Aug 2026 22:14:05 +0200 Subject: [PATCH 12/24] fix: retain ignored main history on descendants --- ...PreventIncrementOfMergedBranchScenarios.cs | 34 +++++++++++++++++++ .../IncrementStrategyFinder.cs | 17 +++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs index f76fe53c12..ada0f0b6de 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs @@ -534,6 +534,40 @@ public void RetainsIgnoredCurrentTargetAsHistoricalSource() fixture.AssertFullSemver("1.0.1-3", configuration); } + [Test] + public void RetainsIgnoredHistoricalMainAsSourceOnLinearDescendant() + { + var configuration = GitFlowConfigurationBuilder.New + .WithIgnoreConfiguration(new IgnoreConfiguration { Branches = ["^main$"] }) + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("develop", builder => builder + .WithIncrement(IncrementStrategy.Minor) + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Inherit) + .WithSourceBranches("main", "develop") + ).WithBranch("child", builder => builder + .WithRegularExpression("^child$") + .WithLabel(string.Empty) + .WithIncrement(IncrementStrategy.Patch) + .WithSourceBranches("main") + .WithPreventIncrementOfMergedBranch(true) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.CreateBranch("develop"); + fixture.MakeACommit(); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main", removeBranchAfterMerging: true); + fixture.BranchTo("child"); + fixture.MakeACommit(); + + fixture.AssertFullSemver("1.0.1-4", configuration); + } + [Test] public void StopsMergedSourceContributionsAtInterveningTargetTag() { diff --git a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs index ffa2b26b8b..3e8f1f4e7b 100644 --- a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs @@ -382,7 +382,7 @@ private IEnumerable FindClosestSourceBranches( { var candidates = repositoryStore.Branches .Where(branch => (!configuration.Ignore.IsBranchIgnored(branch.Name) - || branch.Name.EquivalentTo(Context.CurrentBranch.Name.WithoutOrigin)) + || IsCurrentOrLinearMainBranch(branch)) && IsConfiguredSourceBranch(branch, mergedBranchConfiguration, configuration)); var closestDistance = int.MaxValue; @@ -414,6 +414,21 @@ private IEnumerable FindClosestSourceBranches( .Select(group => group.OrderBy(candidate => candidate.Branch.IsRemote).First()); } + private bool IsCurrentOrLinearMainBranch(IBranch branch) + { + if (branch.Name.EquivalentTo(Context.CurrentBranch.Name.WithoutOrigin)) + { + return true; + } + if (branch.Tip is not { } tip + || !GetEffectiveConfigurations(branch).Any(configuration => configuration.IsMainBranch) + || FindFirstParentSource(Context.CurrentCommit, tip) is not { } source) + { + return false; + } + return !ContainsMergeCommit(Context.CurrentCommit, source.Distance); + } + private (ICommit Commit, int Distance)? FindFirstParentSource( ICommit mergedBranchTip, ICommit sourceBranchTip) { From 4966d59cf1faee0fea1e5c771384600957e5362a Mon Sep 17 00:00:00 2001 From: Morax Date: Fri, 21 Aug 2026 22:23:28 +0200 Subject: [PATCH 13/24] fix: retain side history for ignored merges --- ...PreventIncrementOfMergedBranchScenarios.cs | 26 +++++++++++++ .../IncrementStrategyFinderTests.cs | 38 ++----------------- .../IncrementStrategyFinder.cs | 23 ++++++----- 3 files changed, 43 insertions(+), 44 deletions(-) diff --git a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs index ada0f0b6de..a05a073e19 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs @@ -618,6 +618,32 @@ public void PreservesCommitMessageDirectivesFromUnrecognizedMergedHistory() fixture.AssertFullSemver("2.0.0-4", configuration); } + [Test] + public void PreservesSideHistoryOfIgnoredUnrecognizedMerge() + { + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("topic/foo"); + fixture.MakeACommit("Breaking change +semver: major"); + fixture.Checkout("main"); + fixture.Repository.MergeNoFF("topic/foo", "Integrate topic"); + var ignoredMerge = fixture.Repository.Head.Tip; + fixture.BranchTo("hotfix/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main", removeBranchAfterMerging: true); + + var configuration = GitFlowConfigurationBuilder.New + .WithIgnoreConfiguration(new IgnoreConfiguration { Shas = [ignoredMerge.Sha] }) + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("hotfix", builder => builder + .WithIncrement(IncrementStrategy.Patch) + ).Build(); + + fixture.AssertFullSemver("2.0.0-3", configuration); + } + [Test] public void ResolvesInheritedIncrementFromHistoricalMergedBranchTip() { diff --git a/src/GitVersion.Core.Tests/VersionCalculation/IncrementStrategyFinderTests.cs b/src/GitVersion.Core.Tests/VersionCalculation/IncrementStrategyFinderTests.cs index c014e7457b..903c41e48e 100644 --- a/src/GitVersion.Core.Tests/VersionCalculation/IncrementStrategyFinderTests.cs +++ b/src/GitVersion.Core.Tests/VersionCalculation/IncrementStrategyFinderTests.cs @@ -15,10 +15,12 @@ public void ParsesCachedCommitWithTheRequestedConfiguration() commit.Sha.Returns("0123456789012345678901234567890123456789"); commit.Message.Returns("feature: custom increment"); + var repositoryStore = Substitute.For(); var finder = new IncrementStrategyFinder( new(() => throw new InvalidOperationException()), - Substitute.For(), - new StubTaggedSemanticVersionRepository(), + repositoryStore, + new TaggedSemanticVersionRepository( + NullLogger.Instance, repositoryStore), Substitute.For(), Substitute.For()); var nonMatchingConfiguration = GitFlowConfigurationBuilder.New @@ -33,36 +35,4 @@ public void ParsesCachedCommitWithTheRequestedConfiguration() finder.GetIncrementForcedByCommit(commit, matchingConfiguration).Increment .ShouldBe(VersionField.Minor); } - - private sealed class StubTaggedSemanticVersionRepository : ITaggedSemanticVersionRepository - { - ILookup ITaggedSemanticVersionRepository.GetTaggedSemanticVersionsOfBranch( - IBranch branch, string? tagPrefix, SemanticVersionFormat format, IIgnoreConfiguration ignore) - { - _ = branch; - _ = tagPrefix; - _ = format; - _ = ignore; - throw new NotSupportedException(); - } - - ILookup ITaggedSemanticVersionRepository.GetTaggedSemanticVersionsOfMergeTarget( - IBranch branch, string? tagPrefix, SemanticVersionFormat format, IIgnoreConfiguration ignore) - { - _ = branch; - _ = tagPrefix; - _ = format; - _ = ignore; - throw new NotSupportedException(); - } - - ILookup ITaggedSemanticVersionRepository.GetTaggedSemanticVersions( - string? tagPrefix, SemanticVersionFormat format, IIgnoreConfiguration ignore) - { - _ = tagPrefix; - _ = format; - _ = ignore; - throw new NotSupportedException(); - } - } } diff --git a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs index 3e8f1f4e7b..5b2d202b89 100644 --- a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs @@ -123,14 +123,14 @@ private IEnumerable GetIncrementsFromCommitHistory( foreach (var entry in history) { - if (!targetCommitHistory.Contains(entry.Commit.Sha)) + if (entry.MergedBranch is not { } mergedBranch) { + targetSegment.AddRange(entry.TargetCommits); continue; } - if (entry.MergedBranch is not { } mergedBranch) + if (!targetCommitHistory.Contains(entry.Commit.Sha)) { - targetSegment.AddRange(entry.TargetCommits); continue; } @@ -179,26 +179,24 @@ private IEnumerable GetFirstParentCommitHistory( { yield break; } - if (!commitLog.Contains(commit.Sha)) - { - continue; - } + var isCommitIncluded = commitLog.Contains(commit.Sha); ReferenceName? mergedBranch = null; - if (commit.IsMergeCommit + if (isCommitIncluded + && commit.IsMergeCommit && commit.Parents.Count == 2 && MergeMessage.TryParse(commit, Context.Configuration, out var mergeMessage)) { mergedBranch = mergeMessage.MergedBranch; } - ICommit[] targetCommits = [commit]; + ICommit[] targetCommits = isCommitIncluded ? [commit] : []; if (commit.IsMergeCommit && mergedBranch is null) { var firstParent = commit.Parents[0]; targetCommits = [ - commit, + .. targetCommits, .. commit.Parents.Skip(1) .SelectMany(parent => this.repositoryStore.GetCommitLog( firstParent, parent, targetConfiguration.Ignore)) @@ -206,6 +204,11 @@ .. commit.Parents.Skip(1) ]; } + if (targetCommits.Length == 0) + { + continue; + } + yield return new(commit, mergedBranch, targetCommits); } } From 7e91918e52bd3ca68ae1b15515585ef4b984976f Mon Sep 17 00:00:00 2001 From: Morax Date: Fri, 21 Aug 2026 22:36:39 +0200 Subject: [PATCH 14/24] test: cover selected stable source tags --- ...PreventIncrementOfMergedBranchScenarios.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs index a05a073e19..ca65cd31d4 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs @@ -353,6 +353,27 @@ public void HonorsPreventIncrementForTaggedMergedSourceTip() fixture.AssertFullSemver("2.0.0-1", configuration); } + [Test] + public void TreatsSelectedSourceTipTagAsTaggedAcrossLabels() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Minor) + .WithPreventIncrementWhenCurrentCommitTagged(true) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("feature/foo"); + fixture.MakeATaggedCommit("2.0.0"); + fixture.MergeTo("main", removeBranchAfterMerging: true); + + fixture.AssertFullSemver("2.0.0-1", configuration); + } + [Test] public void IgnoresFutureDatedTagWhenEvaluatingMergedSourceMessages() { From 7985d63bbc0e4c05e8ea31c6c6b15b5eda49434d Mon Sep 17 00:00:00 2001 From: Morax Date: Fri, 21 Aug 2026 22:45:02 +0200 Subject: [PATCH 15/24] fix: preserve commit order across unrecognized merges --- ...PreventIncrementOfMergedBranchScenarios.cs | 25 +++++++++++++++ .../IncrementStrategyFinder.cs | 31 ++++++++++++------- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs index ca65cd31d4..97076376a3 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs @@ -639,6 +639,31 @@ public void PreservesCommitMessageDirectivesFromUnrecognizedMergedHistory() fixture.AssertFullSemver("2.0.0-4", configuration); } + [Test] + public void PreservesCommitOrderAcrossUnrecognizedMergedHistory() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("hotfix", builder => builder + .WithIncrement(IncrementStrategy.Patch) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("topic/foo"); + fixture.MakeACommit("Reset to patch =semver: patch"); + fixture.Checkout("main"); + fixture.MakeACommit("Breaking change +semver: major"); + fixture.Repository.MergeNoFF("topic/foo", "Integrate topic"); + fixture.BranchTo("hotfix/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main", removeBranchAfterMerging: true); + + fixture.AssertFullSemver("2.0.0-5", configuration); + } + [Test] public void PreservesSideHistoryOfIgnoredUnrecognizedMerge() { diff --git a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs index 5b2d202b89..cf4535613b 100644 --- a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs @@ -99,8 +99,11 @@ private IEnumerable GetIncrementsFromCommitHistory( ICommit currentCommit, ICommit? baseVersionSource, bool shouldIncrement, EffectiveConfiguration targetConfiguration, string? targetLabel, CommitMessageIncrement targetIncrement) { + var commitLog = this.repositoryStore + .GetCommitLog(baseVersionSource, currentCommit, targetConfiguration.Ignore); + var includedCommits = commitLog.Select(commit => commit.Sha).ToHashSet(); var history = GetFirstParentCommitHistory( - currentCommit, baseVersionSource, targetConfiguration).ToArray(); + currentCommit, baseVersionSource, targetConfiguration, includedCommits).ToArray(); if (!history.Any(item => item.MergedBranch is not null)) { @@ -117,6 +120,9 @@ private IEnumerable GetIncrementsFromCommitHistory( targetConfiguration.Ignore) .Select(commit => commit.Sha) .ToHashSet(); + var commitOrder = commitLog + .Select((commit, index) => (commit.Sha, Index: index)) + .ToDictionary(item => item.Sha, item => item.Index); var defaultTargetIncrement = DetermineIncrementedField( commitMessageIncrement: null, shouldIncrement, targetConfiguration).Increment; List targetSegment = []; @@ -137,7 +143,7 @@ private IEnumerable GetIncrementsFromCommitHistory( if (targetSegment.Count != 0) { yield return GetTargetIncrement( - targetSegment, targetCommitHistory, shouldIncrement, targetConfiguration); + targetSegment, targetCommitHistory, commitOrder, shouldIncrement, targetConfiguration); targetSegment.Clear(); } @@ -161,18 +167,14 @@ private IEnumerable GetIncrementsFromCommitHistory( if (targetSegment.Count != 0) { yield return GetTargetIncrement( - targetSegment, targetCommitHistory, shouldIncrement, targetConfiguration); + targetSegment, targetCommitHistory, commitOrder, shouldIncrement, targetConfiguration); } } private IEnumerable GetFirstParentCommitHistory( - ICommit currentCommit, ICommit? baseVersionSource, EffectiveConfiguration targetConfiguration) + ICommit currentCommit, ICommit? baseVersionSource, EffectiveConfiguration targetConfiguration, + IReadOnlySet includedCommits) { - var commitLog = this.repositoryStore - .GetCommitLog(baseVersionSource, currentCommit, targetConfiguration.Ignore) - .Select(commit => commit.Sha) - .ToHashSet(); - for (ICommit? commit = currentCommit; commit is not null; commit = commit.Parents.FirstOrDefault()) { if (baseVersionSource?.Equals(commit) == true) @@ -180,7 +182,7 @@ private IEnumerable GetFirstParentCommitHistory( yield break; } - var isCommitIncluded = commitLog.Contains(commit.Sha); + var isCommitIncluded = includedCommits.Contains(commit.Sha); ReferenceName? mergedBranch = null; if (isCommitIncluded && commit.IsMergeCommit @@ -215,9 +217,16 @@ .. commit.Parents.Skip(1) private CommitMessageIncrement GetTargetIncrement( IEnumerable targetCommits, IReadOnlySet targetCommitHistory, + IReadOnlyDictionary commitOrder, bool shouldIncrement, EffectiveConfiguration targetConfiguration) => DetermineIncrementedField( - FindCommitMessageIncrement(targetConfiguration, targetCommits, targetCommitHistory), + FindCommitMessageIncrement( + targetConfiguration, + targetCommits + .Where(commit => targetCommitHistory.Contains(commit.Sha)) + .DistinctBy(commit => commit.Sha) + .OrderBy(commit => commitOrder[commit.Sha]), + targetCommitHistory), shouldIncrement, targetConfiguration); From 467a5d499676bd8b87afbc499c99daf646abb0e9 Mon Sep 17 00:00:00 2001 From: Morax Date: Fri, 21 Aug 2026 22:51:53 +0200 Subject: [PATCH 16/24] fix: stop history traversal at off-parent base --- ...PreventIncrementOfMergedBranchScenarios.cs | 25 ++++++++++++++ .../IncrementStrategyFinder.cs | 34 ++++++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs index 97076376a3..fde89cf5db 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs @@ -374,6 +374,31 @@ public void TreatsSelectedSourceTipTagAsTaggedAcrossLabels() fixture.AssertFullSemver("2.0.0-1", configuration); } + [Test] + public void StopsUnrecognizedSideHistoryAtOffFirstParentBase() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Major) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("hotfix", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementWhenCurrentCommitTagged(true) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("topic/foo"); + fixture.MakeACommit(); + fixture.Checkout("main"); + fixture.Repository.MergeNoFF("topic/foo", "Integrate topic"); + fixture.BranchTo("hotfix/foo"); + fixture.MakeATaggedCommit("2.0.0"); + fixture.MergeTo("main", removeBranchAfterMerging: true); + + fixture.AssertFullSemver("2.0.0-1", configuration); + } + [Test] public void IgnoresFutureDatedTagWhenEvaluatingMergedSourceMessages() { diff --git a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs index cf4535613b..3ffe22cb92 100644 --- a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs @@ -21,6 +21,7 @@ internal class IncrementStrategyFinder( private readonly Dictionary> headCommitsMapCache = []; private readonly Dictionary headCommitsCache = []; private readonly Dictionary<(string Commit, EffectiveConfiguration Target), bool> linearMainBranchHistoryCache = []; + private readonly Dictionary> reachableCommitCache = []; private readonly Lazy contextLazy = contextLazy.NotNull(); private readonly IRepositoryStore repositoryStore = repositoryStore.NotNull(); @@ -175,9 +176,10 @@ private IEnumerable GetFirstParentCommitHistory( ICommit currentCommit, ICommit? baseVersionSource, EffectiveConfiguration targetConfiguration, IReadOnlySet includedCommits) { + var commitsReachableFromBase = GetReachableCommitShas(baseVersionSource); for (ICommit? commit = currentCommit; commit is not null; commit = commit.Parents.FirstOrDefault()) { - if (baseVersionSource?.Equals(commit) == true) + if (commitsReachableFromBase.Contains(commit.Sha)) { yield break; } @@ -215,6 +217,36 @@ .. commit.Parents.Skip(1) } } + private HashSet GetReachableCommitShas(ICommit? commit) + { + if (commit is null) + { + return []; + } + + return this.reachableCommitCache.GetOrAdd(commit.Sha, () => + { + HashSet result = []; + var pending = new Stack(); + pending.Push(commit); + + while (pending.TryPop(out var current)) + { + if (!result.Add(current.Sha)) + { + continue; + } + + foreach (var parent in current.Parents) + { + pending.Push(parent); + } + } + + return result; + }); + } + private CommitMessageIncrement GetTargetIncrement( IEnumerable targetCommits, IReadOnlySet targetCommitHistory, IReadOnlyDictionary commitOrder, From 01e26fc631cdabd4dbdd095d4263865ca84d4200 Mon Sep 17 00:00:00 2001 From: Morax Date: Fri, 21 Aug 2026 23:04:34 +0200 Subject: [PATCH 17/24] fix: skip target segments pruned by tags --- ...PreventIncrementOfMergedBranchScenarios.cs | 22 +++++++++++++++++++ .../IncrementStrategyFinder.cs | 3 ++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs index fde89cf5db..f8e2a06400 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs @@ -640,6 +640,28 @@ public void StopsMergedSourceContributionsAtInterveningTargetTag() fixture.AssertFullSemver("2.0.1-4", configuration); } + [Test] + public void SkipsTargetSegmentsPrunedByInterveningTag() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Major) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("hotfix", builder => builder + .WithIncrement(IncrementStrategy.Patch) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("2.0.0"); + fixture.MakeACommit(); + fixture.ApplyTag("1.0.0"); + fixture.BranchTo("hotfix/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main", removeBranchAfterMerging: true); + + fixture.AssertFullSemver("2.0.1-3", configuration); + } + [Test] public void PreservesCommitMessageDirectivesFromUnrecognizedMergedHistory() { diff --git a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs index 3ffe22cb92..07afd443f9 100644 --- a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs @@ -132,7 +132,8 @@ private IEnumerable GetIncrementsFromCommitHistory( { if (entry.MergedBranch is not { } mergedBranch) { - targetSegment.AddRange(entry.TargetCommits); + targetSegment.AddRange(entry.TargetCommits + .Where(commit => targetCommitHistory.Contains(commit.Sha))); continue; } From e9bdbe3dacde26bbab0bbdf64472c70b0384835b Mon Sep 17 00:00:00 2001 From: Morax Date: Fri, 21 Aug 2026 23:14:43 +0200 Subject: [PATCH 18/24] fix: align merge prevention matrix --- .../PreventIncrementOfMergedBranchScenarios.cs | 5 +++-- .../VersionCalculation/IncrementStrategyFinder.cs | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs index f8e2a06400..1a27349804 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs @@ -8,7 +8,7 @@ namespace GitVersion.Tests.IntegrationTests; [Parallelizable(ParallelScope.All)] public class PreventIncrementOfMergedBranchScenarios { - [TestCase(false, false, "1.1.0-2")] + [TestCase(false, false, "1.0.1-2")] [TestCase(false, true, "1.0.1-2")] [TestCase(false, null, "1.1.0-2")] [TestCase(true, false, "1.1.0-2")] @@ -153,12 +153,13 @@ public void LatestMergedSourceResetDiscardsEarlierMergedIncrement() public void NewerMergedSourceResetDiscardsEarlierTargetDirective() { var configuration = GitFlowConfigurationBuilder.New + .WithPreventIncrementWhenBranchMerged(null) .WithBranch("main", builder => builder .WithIncrement(IncrementStrategy.Patch) .WithPreventIncrementOfMergedBranch(false) ).WithBranch("hotfix", builder => builder .WithIncrement(IncrementStrategy.Patch) - .WithPreventIncrementWhenBranchMerged(false) + .WithPreventIncrementWhenBranchMerged(null) ).Build(); using var fixture = new EmptyRepositoryFixture("main"); diff --git a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs index 07afd443f9..bda3c77f41 100644 --- a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs @@ -570,9 +570,9 @@ private static CommitMessageIncrement SelectIncrement( : sourceIncrement; } - return preventIncrementWhenBranchMerged == true - ? new(targetIncrement, VersionBumpNeedsToBeReset: false) - : new(targetIncrement.Consolidate(sourceIncrement.Increment), sourceIncrement.VersionBumpNeedsToBeReset); + return preventIncrementWhenBranchMerged is null + ? new(targetIncrement.Consolidate(sourceIncrement.Increment), sourceIncrement.VersionBumpNeedsToBeReset) + : new(targetIncrement, VersionBumpNeedsToBeReset: false); } private readonly record struct MergedBranchIncrement( From 0fe3a9a9de3aea30952515f75fbb331f74f2c43e Mon Sep 17 00:00:00 2001 From: Morax Date: Fri, 21 Aug 2026 23:20:46 +0200 Subject: [PATCH 19/24] fix: use global fallback for orphaned sources --- ...PreventIncrementOfMergedBranchScenarios.cs | 24 +++++++++++++++++++ .../IncrementStrategyFinder.cs | 6 ++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs index 1a27349804..9324ff0165 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs @@ -829,6 +829,30 @@ public void ResolvesRetainedInheritedBranchFromHistoricalSource() fixture.AssertFullSemver("1.1.0-3", configuration); } + [TestCase(false)] + [TestCase(true)] + public void UsesGlobalFallbackForOrphanedInheritedSource(bool removeBranchAfterMerging) + { + var configuration = GitFlowConfigurationBuilder.New + .WithIncrement(IncrementStrategy.Minor) + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("topic", builder => builder + .WithRegularExpression("^topics?[/-](?.+)") + .WithIncrement(IncrementStrategy.Inherit) + .WithSourceBranches() + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("topic/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main", removeBranchAfterMerging); + + fixture.AssertFullSemver("1.1.0-2", configuration); + } + [Test] public void ResolvesNestedInheritanceFromHistoricalSourceState() { diff --git a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs index bda3c77f41..f18651524c 100644 --- a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs @@ -274,7 +274,7 @@ private MergedBranchIncrement[] GetMergedBranchIncrements( mergeCommit.Parents[0], mergeCommit.Parents[1]); return [.. GetSourceConfigurations( - mergedBranch, mergeCommit.Parents[1], sourceBranchConfiguration, targetConfiguration) + mergedBranch, mergeCommit.Parents[1], sourceBranchConfiguration) .Select(sourceConfiguration => GetMergedBranchIncrement( mergeCommit.Parents[1], mergedBranch, baseVersionSource, mergeBase, sourceBranchConfiguration, sourceConfiguration))]; @@ -332,7 +332,7 @@ private bool ShouldIncrementTaggedCommit( private IEnumerable GetSourceConfigurations( ReferenceName mergedBranch, ICommit mergedBranchTip, - IBranchConfiguration sourceBranchConfiguration, EffectiveConfiguration targetConfiguration) + IBranchConfiguration sourceBranchConfiguration) { var existingBranch = this.repositoryStore.Branches .Where(candidate => candidate.Name.EquivalentTo(mergedBranch.WithoutOrigin) @@ -364,7 +364,7 @@ private IEnumerable GetSourceConfigurations( return inheritedConfigurations.Length != 0 ? inheritedConfigurations - : [Context.Configuration.GetEffectiveConfiguration(mergedBranch, targetConfiguration)]; + : [Context.Configuration.GetEffectiveConfiguration(mergedBranch)]; } private EffectiveConfiguration[] GetEffectiveConfigurations(IBranch branch, ICommit? tip = null) From 27cecb65aa56cd299697b318ca5ad2b9d553b26f Mon Sep 17 00:00:00 2001 From: Morax Date: Fri, 21 Aug 2026 23:49:32 +0200 Subject: [PATCH 20/24] fix: preserve main merge floor on descendants --- ...PreventIncrementOfMergedBranchScenarios.cs | 58 ++++++++ .../IncrementStrategyFinder.cs | 126 ++++++++++++------ 2 files changed, 143 insertions(+), 41 deletions(-) diff --git a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs index 9324ff0165..870805b34d 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs @@ -615,6 +615,64 @@ public void RetainsIgnoredHistoricalMainAsSourceOnLinearDescendant() fixture.AssertFullSemver("1.0.1-4", configuration); } + [Test] + public void RetainsMergedIncrementAfterMergingMainIntoDescendant() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("release", builder => builder + .WithIncrement(IncrementStrategy.Minor) + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Patch) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit(); + fixture.Checkout("main"); + fixture.BranchTo("release/minor"); + fixture.MakeACommit(); + fixture.MergeTo("main", removeBranchAfterMerging: true); + fixture.AssertFullSemver("1.1.0-2", configuration); + fixture.Checkout("feature/foo"); + fixture.MergeNoFF("main"); + + fixture.AssertFullSemver("1.1.0-foo.1+4", configuration); + } + + [Test] + public void RejectsUnrelatedMergeBeforeMergingMainIntoDescendant() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("release", builder => builder + .WithIncrement(IncrementStrategy.Minor) + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Patch) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit(); + fixture.Checkout("main"); + fixture.BranchTo("release/minor"); + fixture.MakeACommit(); + fixture.MergeTo("main", removeBranchAfterMerging: true); + fixture.Checkout("feature/foo"); + fixture.BranchTo("feature/side"); + fixture.MakeACommit(); + fixture.MergeTo("feature/foo", removeBranchAfterMerging: true); + fixture.MergeNoFF("main"); + + fixture.AssertFullSemver("1.0.1-foo.1+6", configuration); + } + [Test] public void StopsMergedSourceContributionsAtInterveningTargetTag() { diff --git a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs index f18651524c..e1bdea9738 100644 --- a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs @@ -20,7 +20,7 @@ internal class IncrementStrategyFinder( private readonly Dictionary> firstParentHistoryCache = []; private readonly Dictionary> headCommitsMapCache = []; private readonly Dictionary headCommitsCache = []; - private readonly Dictionary<(string Commit, EffectiveConfiguration Target), bool> linearMainBranchHistoryCache = []; + private readonly Dictionary<(string Commit, EffectiveConfiguration Target), bool> compatibleMainBranchHistoryCache = []; private readonly Dictionary> reachableCommitCache = []; private readonly Lazy contextLazy = contextLazy.NotNull(); @@ -40,27 +40,14 @@ public VersionField DetermineIncrementedField( var targetIncrement = DetermineIncrementedFieldInternal( currentCommit, baseVersionSource, shouldIncrement, configuration, label); - if (!configuration.TrackMergeMessage || !HasLinearMainBranchHistory(currentCommit, configuration)) + if (!configuration.TrackMergeMessage || !HasCompatibleMainBranchHistory(currentCommit, configuration)) { return targetIncrement.Increment; } var increments = GetIncrementsFromCommitHistory( currentCommit, baseVersionSource, shouldIncrement, configuration, label, targetIncrement); - - var result = VersionField.None; - var hasIncrement = false; - foreach (var increment in increments) - { - hasIncrement = true; - result = result.Consolidate(increment.Increment); - if (increment.VersionBumpNeedsToBeReset) - { - break; - } - } - - return hasIncrement ? result : targetIncrement.Increment; + return ConsolidateIncrements(increments, targetIncrement).Increment; } private CommitMessageIncrement DetermineIncrementedFieldInternal( @@ -277,31 +264,75 @@ private MergedBranchIncrement[] GetMergedBranchIncrements( mergedBranch, mergeCommit.Parents[1], sourceBranchConfiguration) .Select(sourceConfiguration => GetMergedBranchIncrement( mergeCommit.Parents[1], mergedBranch, baseVersionSource, mergeBase, - sourceBranchConfiguration, sourceConfiguration))]; + sourceBranchConfiguration, sourceConfiguration, targetConfiguration))]; }); private MergedBranchIncrement GetMergedBranchIncrement( ICommit mergedBranchTip, ReferenceName mergedBranch, ICommit? baseVersionSource, ICommit? mergeBase, - IBranchConfiguration sourceBranchConfiguration, EffectiveConfiguration sourceConfiguration) + IBranchConfiguration sourceBranchConfiguration, EffectiveConfiguration sourceConfiguration, + EffectiveConfiguration targetConfiguration) { var sourceLabel = sourceConfiguration.GetBranchSpecificLabel( mergedBranch, null, this.environment); - var preventIncrementWhenBranchMerged = sourceBranchConfiguration.PreventIncrement.WhenBranchMerged - ?? (sourceBranchConfiguration.Increment == IncrementStrategy.Inherit - ? sourceConfiguration.PreventIncrementWhenBranchMerged - : Context.Configuration.PreventIncrement.WhenBranchMerged); + var preventIncrementWhenBranchMerged = ResolvePreventIncrementWhenBranchMerged( + sourceBranchConfiguration, sourceConfiguration, targetConfiguration); + var shouldIncrement = ShouldIncrementTaggedCommit( + mergedBranchTip, baseVersionSource, sourceConfiguration, sourceLabel); var sourceIncrement = DetermineIncrementedFieldInternal( currentCommit: mergedBranchTip, baseVersionSource: mergeBase, - shouldIncrement: ShouldIncrementTaggedCommit( - mergedBranchTip, baseVersionSource, sourceConfiguration, sourceLabel), + shouldIncrement: shouldIncrement, configuration: sourceConfiguration, label: sourceLabel ); + if (sourceConfiguration.TrackMergeMessage + && HasCompatibleMainBranchHistory(mergedBranchTip, sourceConfiguration)) + { + sourceIncrement = ConsolidateIncrements( + GetIncrementsFromCommitHistory( + mergedBranchTip, mergeBase, shouldIncrement, + sourceConfiguration, sourceLabel, sourceIncrement), + sourceIncrement); + } + return new(sourceIncrement, preventIncrementWhenBranchMerged); } + private bool? ResolvePreventIncrementWhenBranchMerged( + IBranchConfiguration sourceBranchConfiguration, EffectiveConfiguration sourceConfiguration, + EffectiveConfiguration targetConfiguration) + { + // Updating a descendant from main carries main's version floor; it does not complete main as a source branch. + if (sourceConfiguration.IsMainBranch && !targetConfiguration.IsMainBranch) + { + return null; + } + + return sourceBranchConfiguration.PreventIncrement.WhenBranchMerged + ?? (sourceBranchConfiguration.Increment == IncrementStrategy.Inherit + ? sourceConfiguration.PreventIncrementWhenBranchMerged + : Context.Configuration.PreventIncrement.WhenBranchMerged); + } + + private static CommitMessageIncrement ConsolidateIncrements( + IEnumerable increments, CommitMessageIncrement fallback) + { + var result = new CommitMessageIncrement(VersionField.None, VersionBumpNeedsToBeReset: false); + var hasIncrement = false; + foreach (var increment in increments) + { + hasIncrement = true; + result = result.Consolidate(increment); + if (increment.VersionBumpNeedsToBeReset) + { + break; + } + } + + return hasIncrement ? result : fallback; + } + private static CommitMessageIncrement ConsolidateMergedBranchIncrements( IEnumerable sourceIncrements, EffectiveConfiguration targetConfiguration, VersionField targetIncrement) @@ -427,7 +458,7 @@ private IEnumerable FindClosestSourceBranches( { var candidates = repositoryStore.Branches .Where(branch => (!configuration.Ignore.IsBranchIgnored(branch.Name) - || IsCurrentOrLinearMainBranch(branch)) + || IsCurrentOrCompatibleMainBranch(branch)) && IsConfiguredSourceBranch(branch, mergedBranchConfiguration, configuration)); var closestDistance = int.MaxValue; @@ -459,7 +490,7 @@ private IEnumerable FindClosestSourceBranches( .Select(group => group.OrderBy(candidate => candidate.Branch.IsRemote).First()); } - private bool IsCurrentOrLinearMainBranch(IBranch branch) + private bool IsCurrentOrCompatibleMainBranch(IBranch branch) { if (branch.Name.EquivalentTo(Context.CurrentBranch.Name.WithoutOrigin)) { @@ -471,21 +502,14 @@ private bool IsCurrentOrLinearMainBranch(IBranch branch) { return false; } - return !ContainsMergeCommit(Context.CurrentCommit, source.Distance); + return !ContainsNonMainMergeCommit( + Context.CurrentCommit, source.Distance, [GetFirstParentHistory(tip)]); } private (ICommit Commit, int Distance)? FindFirstParentSource( ICommit mergedBranchTip, ICommit sourceBranchTip) { - var sourceHistory = this.firstParentHistoryCache.GetOrAdd(sourceBranchTip.Sha, () => - { - HashSet result = []; - for (ICommit? commit = sourceBranchTip; commit is not null; commit = commit.Parents.FirstOrDefault()) - { - result.Add(commit.Sha); - } - return result; - }); + var sourceHistory = GetFirstParentHistory(sourceBranchTip); var distance = 0; for (ICommit? commit = mergedBranchTip; commit is not null; commit = commit.Parents.FirstOrDefault()) @@ -499,17 +523,23 @@ private bool IsCurrentOrLinearMainBranch(IBranch branch) return null; } - private bool HasLinearMainBranchHistory(ICommit commit, EffectiveConfiguration targetConfiguration) + private bool HasCompatibleMainBranchHistory(ICommit commit, EffectiveConfiguration targetConfiguration) { if (IsPullRequestBranch(Context.CurrentBranch, Context.Configuration)) { return false; } - return this.linearMainBranchHistoryCache.GetOrAdd((commit.Sha, targetConfiguration), () => + return this.compatibleMainBranchHistoryCache.GetOrAdd((commit.Sha, targetConfiguration), () => { var closestDistance = GetClosestMainBranchDistance(commit, targetConfiguration); - return closestDistance != int.MaxValue && !ContainsMergeCommit(commit, closestDistance); + var mainBranchHistories = this.repositoryStore.Branches + .Where(branch => IsMainBranch(branch, targetConfiguration) && branch.Tip is not null) + .Select(branch => GetFirstParentHistory(branch.Tip!)) + .ToArray(); + + return closestDistance != int.MaxValue + && !ContainsNonMainMergeCommit(commit, closestDistance, mainBranchHistories); }); } @@ -534,12 +564,26 @@ private bool IsMainBranch(IBranch branch, EffectiveConfiguration targetConfigura ? targetConfiguration.IsMainBranch : GetEffectiveConfigurations(branch).Any(configuration => configuration.IsMainBranch); - private static bool ContainsMergeCommit(ICommit commit, int distance) + private HashSet GetFirstParentHistory(ICommit tip) => + this.firstParentHistoryCache.GetOrAdd(tip.Sha, () => + { + HashSet result = []; + for (ICommit? commit = tip; commit is not null; commit = commit.Parents.FirstOrDefault()) + { + result.Add(commit.Sha); + } + return result; + }); + + private static bool ContainsNonMainMergeCommit( + ICommit commit, int distance, IReadOnlyCollection> mainBranchHistories) { for (ICommit? current = commit; distance > 0; current = current?.Parents.FirstOrDefault(), distance--) { - if (current?.IsMergeCommit == true) + if (current?.IsMergeCommit == true + && current.Parents.Skip(1).Any(parent => + mainBranchHistories.All(history => !history.Contains(parent.Sha)))) { return true; } From 8da3a40e376ed1e175b09dea2be318796659037f Mon Sep 17 00:00:00 2001 From: Morax Date: Sat, 22 Aug 2026 00:05:01 +0200 Subject: [PATCH 21/24] fix: preserve merged history ordering and fallbacks --- ...PreventIncrementOfMergedBranchScenarios.cs | 51 +++++++++++++++++++ .../IncrementStrategyFinder.cs | 32 ++++++++---- 2 files changed, 72 insertions(+), 11 deletions(-) diff --git a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs index 870805b34d..8c7587fb1f 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs @@ -619,13 +619,18 @@ public void RetainsIgnoredHistoricalMainAsSourceOnLinearDescendant() public void RetainsMergedIncrementAfterMergingMainIntoDescendant() { var configuration = GitFlowConfigurationBuilder.New + .WithIsMainBranch(true) .WithBranch("main", builder => builder .WithIncrement(IncrementStrategy.Patch) .WithPreventIncrementOfMergedBranch(true) + .WithPreventIncrementWhenBranchMerged(true) + .WithIsMainBranch(null) ).WithBranch("release", builder => builder .WithIncrement(IncrementStrategy.Minor) + .WithIsMainBranch(false) ).WithBranch("feature", builder => builder .WithIncrement(IncrementStrategy.Patch) + .WithIsMainBranch(false) ).Build(); using var fixture = new EmptyRepositoryFixture("main"); @@ -770,6 +775,30 @@ public void PreservesCommitOrderAcrossUnrecognizedMergedHistory() fixture.AssertFullSemver("2.0.0-5", configuration); } + [Test] + public void PreservesCommitOrderAcrossRecognizedMergeBoundary() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("feature", builder => builder + .WithIncrement(IncrementStrategy.Major) + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("topic/foo"); + fixture.MakeACommit("Reset to patch =semver: patch"); + fixture.Checkout("main"); + fixture.BranchTo("feature/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main", removeBranchAfterMerging: true); + fixture.Repository.MergeNoFF("topic/foo", "Integrate topic"); + + fixture.AssertFullSemver("2.0.0-4", configuration); + } + [Test] public void PreservesSideHistoryOfIgnoredUnrecognizedMerge() { @@ -911,6 +940,28 @@ public void UsesGlobalFallbackForOrphanedInheritedSource(bool removeBranchAfterM fixture.AssertFullSemver("1.1.0-2", configuration); } + [Test] + public void SkipsUnresolvedFallbackForOrphanedInheritedSource() + { + var configuration = GitFlowConfigurationBuilder.New + .WithBranch("main", builder => builder + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true) + ).WithBranch("topic", builder => builder + .WithRegularExpression("^topics?[/-](?.+)") + .WithIncrement(IncrementStrategy.Inherit) + .WithSourceBranches() + ).Build(); + + using var fixture = new EmptyRepositoryFixture("main"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("topic/foo"); + fixture.MakeACommit(); + fixture.MergeTo("main", removeBranchAfterMerging: true); + + fixture.AssertFullSemver("1.0.1-2", configuration); + } + [Test] public void ResolvesNestedInheritanceFromHistoricalSourceState() { diff --git a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs index e1bdea9738..b300900449 100644 --- a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs @@ -111,21 +111,23 @@ private IEnumerable GetIncrementsFromCommitHistory( var commitOrder = commitLog .Select((commit, index) => (commit.Sha, Index: index)) .ToDictionary(item => item.Sha, item => item.Index); + var orderedHistory = history + .SelectMany(entry => entry.MergedBranch is not null + ? [new IncrementHistoryEntry(entry.Commit, entry.MergedBranch)] + : entry.TargetCommits.Select(commit => new IncrementHistoryEntry(commit, MergedBranch: null))) + .Where(entry => targetCommitHistory.Contains(entry.Commit.Sha)) + .GroupBy(entry => entry.Commit.Sha) + .Select(group => group.OrderByDescending(entry => entry.MergedBranch is not null).First()) + .OrderBy(entry => commitOrder[entry.Commit.Sha]); var defaultTargetIncrement = DetermineIncrementedField( commitMessageIncrement: null, shouldIncrement, targetConfiguration).Increment; List targetSegment = []; - foreach (var entry in history) + foreach (var entry in orderedHistory) { if (entry.MergedBranch is not { } mergedBranch) { - targetSegment.AddRange(entry.TargetCommits - .Where(commit => targetCommitHistory.Contains(commit.Sha))); - continue; - } - - if (!targetCommitHistory.Contains(entry.Commit.Sha)) - { + targetSegment.Add(entry.Commit); continue; } @@ -393,9 +395,15 @@ private IEnumerable GetSourceConfigurations( .Distinct() .ToArray(); - return inheritedConfigurations.Length != 0 - ? inheritedConfigurations - : [Context.Configuration.GetEffectiveConfiguration(mergedBranch)]; + if (inheritedConfigurations.Length != 0) + { + return inheritedConfigurations; + } + + var fallbackConfiguration = Context.Configuration.GetEffectiveConfiguration(mergedBranch); + return fallbackConfiguration.Increment == IncrementStrategy.Inherit + ? [] + : [fallbackConfiguration]; } private EffectiveConfiguration[] GetEffectiveConfigurations(IBranch branch, ICommit? tip = null) @@ -627,6 +635,8 @@ private readonly record struct MergedBranchIncrement( private readonly record struct CommitHistoryEntry( ICommit Commit, ReferenceName? MergedBranch, IReadOnlyList TargetCommits); + private readonly record struct IncrementHistoryEntry(ICommit Commit, ReferenceName? MergedBranch); + private readonly record struct CommitIncrementCacheKey( string Commit, Regex Major, Regex Minor, Regex Patch, Regex NoBump, Regex Reset); From f8fa4a3e5bebc5d83417160734ccf34c85aec2c2 Mon Sep 17 00:00:00 2001 From: Morax Date: Tue, 25 Aug 2026 13:16:09 +0200 Subject: [PATCH 22/24] refactor: resolve merge prevention before selection --- ...PreventIncrementOfMergedBranchScenarios.cs | 6 +-- .../IncrementStrategyFinder.cs | 43 ++++++++----------- 2 files changed, 19 insertions(+), 30 deletions(-) diff --git a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs index 8c7587fb1f..5f91291475 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/PreventIncrementOfMergedBranchScenarios.cs @@ -8,15 +8,13 @@ namespace GitVersion.Tests.IntegrationTests; [Parallelizable(ParallelScope.All)] public class PreventIncrementOfMergedBranchScenarios { - [TestCase(false, false, "1.0.1-2")] + [TestCase(false, false, "1.1.0-2")] [TestCase(false, true, "1.0.1-2")] - [TestCase(false, null, "1.1.0-2")] [TestCase(true, false, "1.1.0-2")] [TestCase(true, true, "1.0.1-2")] - [TestCase(true, null, "1.1.0-2")] public void SelectsIncrementFromTargetAndMergedBranchConfiguration( bool preventIncrementOfMergedBranch, - bool? preventIncrementWhenBranchMerged, + bool preventIncrementWhenBranchMerged, string expectedVersion) { var configuration = GitFlowConfigurationBuilder.New diff --git a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs index b300900449..08a30111c2 100644 --- a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs @@ -266,18 +266,17 @@ private MergedBranchIncrement[] GetMergedBranchIncrements( mergedBranch, mergeCommit.Parents[1], sourceBranchConfiguration) .Select(sourceConfiguration => GetMergedBranchIncrement( mergeCommit.Parents[1], mergedBranch, baseVersionSource, mergeBase, - sourceBranchConfiguration, sourceConfiguration, targetConfiguration))]; + sourceConfiguration, targetConfiguration))]; }); private MergedBranchIncrement GetMergedBranchIncrement( ICommit mergedBranchTip, ReferenceName mergedBranch, ICommit? baseVersionSource, ICommit? mergeBase, - IBranchConfiguration sourceBranchConfiguration, EffectiveConfiguration sourceConfiguration, - EffectiveConfiguration targetConfiguration) + EffectiveConfiguration sourceConfiguration, EffectiveConfiguration targetConfiguration) { var sourceLabel = sourceConfiguration.GetBranchSpecificLabel( mergedBranch, null, this.environment); var preventIncrementWhenBranchMerged = ResolvePreventIncrementWhenBranchMerged( - sourceBranchConfiguration, sourceConfiguration, targetConfiguration); + sourceConfiguration, targetConfiguration); var shouldIncrement = ShouldIncrementTaggedCommit( mergedBranchTip, baseVersionSource, sourceConfiguration, sourceLabel); var sourceIncrement = DetermineIncrementedFieldInternal( @@ -301,20 +300,16 @@ private MergedBranchIncrement GetMergedBranchIncrement( return new(sourceIncrement, preventIncrementWhenBranchMerged); } - private bool? ResolvePreventIncrementWhenBranchMerged( - IBranchConfiguration sourceBranchConfiguration, EffectiveConfiguration sourceConfiguration, - EffectiveConfiguration targetConfiguration) + private static bool ResolvePreventIncrementWhenBranchMerged( + EffectiveConfiguration sourceConfiguration, EffectiveConfiguration targetConfiguration) { // Updating a descendant from main carries main's version floor; it does not complete main as a source branch. if (sourceConfiguration.IsMainBranch && !targetConfiguration.IsMainBranch) { - return null; + return false; } - return sourceBranchConfiguration.PreventIncrement.WhenBranchMerged - ?? (sourceBranchConfiguration.Increment == IncrementStrategy.Inherit - ? sourceConfiguration.PreventIncrementWhenBranchMerged - : Context.Configuration.PreventIncrement.WhenBranchMerged); + return sourceConfiguration.PreventIncrementWhenBranchMerged; } private static CommitMessageIncrement ConsolidateIncrements( @@ -612,23 +607,19 @@ private static bool IsConfiguredSourceBranch( && sourceBranchConfiguration.IsMatch(candidate.Name.WithoutOrigin)); private static CommitMessageIncrement SelectIncrement( - bool preventIncrementOfMergedBranch, bool? preventIncrementWhenBranchMerged, - VersionField targetIncrement, CommitMessageIncrement sourceIncrement) - { - if (preventIncrementOfMergedBranch) + bool preventIncrementOfMergedBranch, bool preventIncrementWhenBranchMerged, + VersionField targetIncrement, CommitMessageIncrement sourceIncrement) => + (preventIncrementOfMergedBranch, preventIncrementWhenBranchMerged) switch { - return preventIncrementWhenBranchMerged == true - ? new(targetIncrement, VersionBumpNeedsToBeReset: false) - : sourceIncrement; - } - - return preventIncrementWhenBranchMerged is null - ? new(targetIncrement.Consolidate(sourceIncrement.Increment), sourceIncrement.VersionBumpNeedsToBeReset) - : new(targetIncrement, VersionBumpNeedsToBeReset: false); - } + (false, false) => new( + targetIncrement.Consolidate(sourceIncrement.Increment), sourceIncrement.VersionBumpNeedsToBeReset), + (false, true) => new(targetIncrement, VersionBumpNeedsToBeReset: false), + (true, false) => sourceIncrement, + (true, true) => new(targetIncrement, VersionBumpNeedsToBeReset: false) + }; private readonly record struct MergedBranchIncrement( - CommitMessageIncrement Increment, bool? PreventIncrementWhenBranchMerged); + CommitMessageIncrement Increment, bool PreventIncrementWhenBranchMerged); private readonly record struct HistoricalSourceBranch(IBranch Branch, ICommit Tip); From 993e93db5f8959a5033176c9c2aef74c8f6dc58d Mon Sep 17 00:00:00 2001 From: Morax Date: Wed, 26 Aug 2026 02:06:11 +0200 Subject: [PATCH 23/24] fix: align mainline merge prevention fallback --- .../NonTrunk/MergeCommitOnNonTrunkBase.cs | 15 ++++-- .../Mainline/Trunk/MergeCommitOnTrunkBase.cs | 51 ++++++++++++------- 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/src/GitVersion.Core/VersionCalculation/Mainline/NonTrunk/MergeCommitOnNonTrunkBase.cs b/src/GitVersion.Core/VersionCalculation/Mainline/NonTrunk/MergeCommitOnNonTrunkBase.cs index b8a197b606..978062cad6 100644 --- a/src/GitVersion.Core/VersionCalculation/Mainline/NonTrunk/MergeCommitOnNonTrunkBase.cs +++ b/src/GitVersion.Core/VersionCalculation/Mainline/NonTrunk/MergeCommitOnNonTrunkBase.cs @@ -40,20 +40,27 @@ IEnumerable GetIncrementsInternal() private static VersionField ConsolidateIncrement(MainlineCommit commit, MainlineContext context, BaseVersion baseVersion) { - var effectiveConfiguration = commit.GetEffectiveConfiguration(context.Configuration); var increment = VersionField.None; - if (!effectiveConfiguration.PreventIncrementOfMergedBranch) + var effectiveConfiguration1 = commit.GetEffectiveConfiguration(context.Configuration); + if (!effectiveConfiguration1.PreventIncrementOfMergedBranch) { increment = increment.Consolidate(context.Increment); } - if (!effectiveConfiguration.PreventIncrementWhenBranchMerged) + var effectiveConfiguration2 = commit.ChildIteration!.GetEffectiveConfiguration(context.Configuration); + if (!effectiveConfiguration2.PreventIncrementWhenBranchMerged) { increment = increment.Consolidate(baseVersion.Operator?.Increment); } - if (effectiveConfiguration.CommitMessageIncrementing != CommitMessageIncrementMode.Disabled) + if (effectiveConfiguration1.PreventIncrementOfMergedBranch + && effectiveConfiguration2.PreventIncrementWhenBranchMerged) + { + increment = increment.Consolidate(context.Increment); + } + + if (effectiveConfiguration1.CommitMessageIncrementing != CommitMessageIncrementMode.Disabled) { increment = increment.Consolidate(commit.Increment); } diff --git a/src/GitVersion.Core/VersionCalculation/Mainline/Trunk/MergeCommitOnTrunkBase.cs b/src/GitVersion.Core/VersionCalculation/Mainline/Trunk/MergeCommitOnTrunkBase.cs index b67abf05ef..71bc5a3171 100644 --- a/src/GitVersion.Core/VersionCalculation/Mainline/Trunk/MergeCommitOnTrunkBase.cs +++ b/src/GitVersion.Core/VersionCalculation/Mainline/Trunk/MergeCommitOnTrunkBase.cs @@ -26,27 +26,10 @@ IEnumerable GetIncrementsInternal() configuration: context.Configuration, environment: context.Environment, currentCommit: context.CurrentCommit - ); + ); context.Label ??= baseVersion.Operator?.Label; - - var increment = VersionField.None; - - if (!commit.GetEffectiveConfiguration(context.Configuration).PreventIncrementOfMergedBranch) - { - increment = increment.Consolidate(context.Increment); - } - - if (!commit.ChildIteration.GetEffectiveConfiguration(context.Configuration).PreventIncrementWhenBranchMerged) - { - increment = increment.Consolidate(baseVersion.Operator?.Increment); - } - - if (commit.GetEffectiveConfiguration(context.Configuration).CommitMessageIncrementing != CommitMessageIncrementMode.Disabled) - { - increment = increment.Consolidate(commit.Increment); - } - context.Increment = increment; + context.Increment = ConsolidateIncrement(commit, context, baseVersion); if (baseVersion.BaseVersionSource is not null) { @@ -82,4 +65,34 @@ IEnumerable GetIncrementsInternal() context.BaseVersionSource = commit.Value; } } + + private static VersionField ConsolidateIncrement(MainlineCommit commit, MainlineContext context, BaseVersion baseVersion) + { + var increment = VersionField.None; + + var effectiveConfiguration1 = commit.GetEffectiveConfiguration(context.Configuration); + if (!effectiveConfiguration1.PreventIncrementOfMergedBranch) + { + increment = increment.Consolidate(context.Increment); + } + + var effectiveConfiguration2 = commit.ChildIteration!.GetEffectiveConfiguration(context.Configuration); + if (!effectiveConfiguration2.PreventIncrementWhenBranchMerged) + { + increment = increment.Consolidate(baseVersion.Operator?.Increment); + } + + if (effectiveConfiguration1.PreventIncrementOfMergedBranch + && effectiveConfiguration2.PreventIncrementWhenBranchMerged) + { + increment = increment.Consolidate(context.Increment); + } + + if (effectiveConfiguration1.CommitMessageIncrementing != CommitMessageIncrementMode.Disabled) + { + increment = increment.Consolidate(commit.Increment); + } + + return increment; + } } From 2d9a2bd280001958055091529d0c62aae33322cd Mon Sep 17 00:00:00 2001 From: Morax Date: Fri, 11 Sep 2026 01:34:27 +0200 Subject: [PATCH 24/24] fix: retain calculation SHA in merged branch labels --- .../IntegrationTests/ShaLabelScenarios.cs | 41 +++++++++++++++++++ .../IncrementStrategyFinder.cs | 2 +- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/GitVersion.Core.Tests/IntegrationTests/ShaLabelScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/ShaLabelScenarios.cs index 85271e08db..e8da3c9f46 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/ShaLabelScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/ShaLabelScenarios.cs @@ -105,6 +105,47 @@ public void TaggedCurrentCommitPreservesStableVersionWithShaLabel(string backend actual.Sha.ShouldBe(fixture.Repository.Head.Tip.Sha); } + [TestCase("managed", "ShortSha")] + [TestCase("libgit2", "ShortSha")] + [TestCase("managed", "Sha")] + [TestCase("libgit2", "Sha")] + public void MergedSourceLabelUsesCalculationCommitForHistoryReset(string backend, string placeholder) + { + using var backendScope = new BackendScope(backend); + using var fixture = new EmptyRepositoryFixture(); + fixture.MakeATaggedCommit("1.0.0"); + fixture.BranchTo("feature/topic"); + fixture.MakeACommit("source change +semver: major"); + var sourceSha = fixture.Repository.Head.Tip.Sha; + fixture.MergeTo("main"); + var currentSha = fixture.Repository.Head.Tip.Sha; + var label = "ci-sha" + (placeholder == "Sha" ? currentSha : currentSha[..7]); + fixture.Repository.Tags.Add($"0.9.0-{label}.1", sourceSha); + + IGitVersionConfiguration Configuration(string sourceLabel) => GitFlowConfigurationBuilder.New + .WithBranch("main", b => b + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementOfMergedBranch(true)) + .WithBranch("feature", b => b + .WithLabel(sourceLabel) + .WithIncrement(IncrementStrategy.Patch) + .WithPreventIncrementWhenBranchMerged(false)) + .Build(); + + var literal = fixture.GetVersion(Configuration(label)); + var actual = fixture.GetVersion(Configuration($"ci-sha{{{placeholder}}}")); + + currentSha.ShouldNotBe(sourceSha); + literal.FullSemVer.ShouldBe("1.0.1-2"); + actual.FullSemVer.ShouldBe(literal.FullSemVer); + + fixture.MakeACommit(); + var historical = fixture.GetVersion( + Configuration($"ci-sha{{{placeholder}}}"), commitId: currentSha); + historical.Sha.ShouldBe(currentSha); + historical.FullSemVer.ShouldBe(actual.FullSemVer); + } + private sealed class BackendScope : IDisposable { private readonly string? original = System.Environment.GetEnvironmentVariable(GitBackendSelector.EnvironmentVariableName); diff --git a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs index 08a30111c2..c641a07b04 100644 --- a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs @@ -274,7 +274,7 @@ private MergedBranchIncrement GetMergedBranchIncrement( EffectiveConfiguration sourceConfiguration, EffectiveConfiguration targetConfiguration) { var sourceLabel = sourceConfiguration.GetBranchSpecificLabel( - mergedBranch, null, this.environment); + mergedBranch, null, this.environment, Context.CurrentCommit); var preventIncrementWhenBranchMerged = ResolvePreventIncrementWhenBranchMerged( sourceConfiguration, targetConfiguration); var shouldIncrement = ShouldIncrementTaggedCommit(