diff --git a/docs/configuration.md b/docs/configuration.md index 8c37a32afa..c720497cd9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -256,4 +256,7 @@ branch. For example `develop` → `release/1.0.0` → merge into `master` and ta Indicates this branch config represents develop in GitFlow. ### is-release-branch -Indicates this branch config represents a release branch in GitFlow. \ No newline at end of file +Indicates this branch config represents a release branch in GitFlow. + +### is-mainline +When using Mainline mode, this indicates that this branch is a mainline. By default support/ and master are mainlines. \ No newline at end of file diff --git a/src/GitVersionCore.Tests/ConfigProviderTests.CanWriteOutEffectiveConfiguration.approved.txt b/src/GitVersionCore.Tests/ConfigProviderTests.CanWriteOutEffectiveConfiguration.approved.txt index 4dd013a10e..8dbda75afd 100644 --- a/src/GitVersionCore.Tests/ConfigProviderTests.CanWriteOutEffectiveConfiguration.approved.txt +++ b/src/GitVersionCore.Tests/ConfigProviderTests.CanWriteOutEffectiveConfiguration.approved.txt @@ -19,6 +19,7 @@ branches: track-merge-target: false is-develop: false is-release-branch: false + is-mainline: true releases?[/-]: mode: ContinuousDelivery tag: beta @@ -27,6 +28,7 @@ branches: track-merge-target: false is-develop: false is-release-branch: true + is-mainline: false features?[/-]: mode: ContinuousDelivery tag: useBranchName @@ -35,6 +37,7 @@ branches: track-merge-target: false is-develop: false is-release-branch: false + is-mainline: false (pull|pull\-requests|pr)[/-]: mode: ContinuousDelivery tag: PullRequest @@ -44,6 +47,7 @@ branches: track-merge-target: false is-develop: false is-release-branch: false + is-mainline: false hotfix(es)?[/-]: mode: ContinuousDelivery tag: beta @@ -52,6 +56,7 @@ branches: track-merge-target: false is-develop: false is-release-branch: false + is-mainline: false support[/-]: mode: ContinuousDelivery tag: '' @@ -60,6 +65,7 @@ branches: track-merge-target: false is-develop: false is-release-branch: false + is-mainline: true dev(elop)?(ment)?$: mode: ContinuousDeployment tag: alpha @@ -68,5 +74,6 @@ branches: track-merge-target: true is-develop: true is-release-branch: false + is-mainline: false ignore: sha: [] diff --git a/src/GitVersionCore.Tests/IntegrationTests/MainlineDevelopmentMode.cs b/src/GitVersionCore.Tests/IntegrationTests/MainlineDevelopmentMode.cs index bf6806a170..e337b20ad5 100644 --- a/src/GitVersionCore.Tests/IntegrationTests/MainlineDevelopmentMode.cs +++ b/src/GitVersionCore.Tests/IntegrationTests/MainlineDevelopmentMode.cs @@ -6,7 +6,6 @@ using GitVersionCore.Tests; using LibGit2Sharp; using NUnit.Framework; -using LibGitExtensions = GitTools.LibGitExtensions; public class MainlineDevelopmentMode { @@ -105,7 +104,41 @@ public void VerifyPullRequestsActLikeContinuousDelivery() } [Test] - [Ignore("Not passing yet")] + public void SupportBranches() + { + using (var fixture = new EmptyRepositoryFixture()) + { + fixture.Repository.MakeACommit("1"); + fixture.MakeATaggedCommit("1.0.0"); + fixture.MakeACommit(); // 1.0.1 + fixture.MakeACommit(); // 1.0.2 + fixture.AssertFullSemver(config, "1.0.2"); + + fixture.BranchTo("support/1.0", "support10"); + fixture.AssertFullSemver(config, "1.0.3"); + + // Move master on + fixture.Checkout("master"); + fixture.MakeACommit("+semver: major"); // 2.0.0 (on master) + fixture.AssertFullSemver(config, "2.0.0"); + + // Continue on support/1.0 + fixture.Checkout("support/1.0"); + fixture.MakeACommit(); // 1.0.4 + fixture.MakeACommit(); // 1.0.5 + fixture.AssertFullSemver(config, "1.0.5"); + fixture.BranchTo("feature/foo", "foo"); + fixture.AssertFullSemver(config, "1.0.5-foo.0"); + fixture.MakeACommit(); + fixture.AssertFullSemver(config, "1.0.5-foo.1"); + fixture.MakeACommit(); + fixture.AssertFullSemver(config, "1.0.5-foo.2"); + fixture.Repository.CreatePullRequestRef("feature/foo", "support/1.0", normalise: true, prNumber: 7); + fixture.AssertFullSemver(config, "1.0.5-PullRequest0007.3"); + } + } + + [Test] public void VerifyForwardMerge() { using (var fixture = new EmptyRepositoryFixture()) @@ -124,7 +157,7 @@ public void VerifyForwardMerge() fixture.AssertFullSemver(config, "1.0.2"); fixture.Checkout("feature/foo"); fixture.MergeNoFF("master"); - fixture.AssertFullSemver(config, "1.0.3-foo.3"); + fixture.AssertFullSemver(config, "1.0.4-foo.3"); } } } diff --git a/src/GitVersionCore/Configuration/BranchConfig.cs b/src/GitVersionCore/Configuration/BranchConfig.cs index 9c52e440b4..a437a5686a 100644 --- a/src/GitVersionCore/Configuration/BranchConfig.cs +++ b/src/GitVersionCore/Configuration/BranchConfig.cs @@ -19,6 +19,7 @@ public BranchConfig(BranchConfig branchConfiguration) CommitMessageIncrementing = branchConfiguration.CommitMessageIncrementing; IsDevelop = branchConfiguration.IsDevelop; IsReleaseBranch = branchConfiguration.IsReleaseBranch; + IsMainline = branchConfiguration.IsMainline; } [YamlMember(Alias = "mode")] @@ -50,5 +51,8 @@ public BranchConfig(BranchConfig branchConfiguration) [YamlMember(Alias = "is-release-branch")] public bool? IsReleaseBranch { get; set; } + + [YamlMember(Alias = "is-mainline")] + public bool? IsMainline { get; set; } } } diff --git a/src/GitVersionCore/Configuration/ConfigurationProvider.cs b/src/GitVersionCore/Configuration/ConfigurationProvider.cs index 9da592bae5..1475aa8ece 100644 --- a/src/GitVersionCore/Configuration/ConfigurationProvider.cs +++ b/src/GitVersionCore/Configuration/ConfigurationProvider.cs @@ -1,5 +1,6 @@ namespace GitVersion { + using System; using GitVersion.Configuration.Init.Wizard; using GitVersion.Helpers; using System.ComponentModel; @@ -43,6 +44,8 @@ public static string SelectConfigFilePath(GitPreparer gitPreparer, IFileSystem f public static Config Provide(string workingDirectory, IFileSystem fileSystem, bool applyDefaults = true, Config overrideConfig = null) { var readConfig = ReadConfig(workingDirectory, fileSystem); + VerifyConfiguration(readConfig); + if (applyDefaults) ApplyDefaultsTo(readConfig); if (null != overrideConfig) @@ -50,6 +53,19 @@ public static Config Provide(string workingDirectory, IFileSystem fileSystem, bo return readConfig; } + static void VerifyConfiguration(Config readConfig) + { + // Verify no branches are set to mainline mode + if (readConfig.Branches.Any(b => b.Value.VersioningMode == VersioningMode.Mainline)) + { + throw new Exception(@"Mainline mode only works at the repository level, a single branch cannot be put into mainline mode + +This is because mainline mode treats your entire git repository as an event source with each merge into the 'mainline' incrementing the version. + +If the docs do not help you decide on the mode open an issue to discuss what you are trying to do."); + } + } + public static void ApplyDefaultsTo(Config config) { MigrateBranches(config); @@ -70,7 +86,10 @@ public static void ApplyDefaultsTo(Config config) var configBranches = config.Branches.ToList(); - ApplyBranchDefaults(config, GetOrCreateBranchDefaults(config, "master"), defaultTag: string.Empty, defaultPreventIncrement: true); + ApplyBranchDefaults(config, GetOrCreateBranchDefaults(config, "master"), + defaultTag: string.Empty, + defaultPreventIncrement: true, + isMainline: true); ApplyBranchDefaults(config, GetOrCreateBranchDefaults(config, "releases?[/-]"), defaultTag: "beta", defaultPreventIncrement: true, isReleaseBranch: true); ApplyBranchDefaults(config, GetOrCreateBranchDefaults(config, "features?[/-]"), defaultIncrementStrategy: IncrementStrategy.Inherit); ApplyBranchDefaults(config, GetOrCreateBranchDefaults(config, @"(pull|pull\-requests|pr)[/-]"), @@ -78,7 +97,10 @@ public static void ApplyDefaultsTo(Config config) defaultTagNumberPattern: @"[/-](?\d+)[-/]", defaultIncrementStrategy: IncrementStrategy.Inherit); ApplyBranchDefaults(config, GetOrCreateBranchDefaults(config, "hotfix(es)?[/-]"), defaultTag: "beta"); - ApplyBranchDefaults(config, GetOrCreateBranchDefaults(config, "support[/-]"), defaultTag: string.Empty, defaultPreventIncrement: true); + ApplyBranchDefaults(config, GetOrCreateBranchDefaults(config, "support[/-]"), + defaultTag: string.Empty, + defaultPreventIncrement: true, + isMainline: true); ApplyBranchDefaults(config, GetOrCreateBranchDefaults(config, "dev(elop)?(ment)?$"), defaultTag: "alpha", defaultIncrementStrategy: IncrementStrategy.Minor, @@ -144,7 +166,8 @@ public static void ApplyBranchDefaults(Config config, bool defaultTrackMergeTarget = false, string defaultTagNumberPattern = null, bool isDevelop = false, - bool isReleaseBranch = false) + bool isReleaseBranch = false, + bool isMainline = false) { branchConfig.Tag = branchConfig.Tag ?? defaultTag; branchConfig.TagNumberPattern = branchConfig.TagNumberPattern ?? defaultTagNumberPattern; @@ -154,6 +177,7 @@ public static void ApplyBranchDefaults(Config config, branchConfig.VersioningMode = branchConfig.VersioningMode ?? defaultVersioningMode ?? config.VersioningMode; branchConfig.IsDevelop = branchConfig.IsDevelop ?? isDevelop; branchConfig.IsReleaseBranch = branchConfig.IsReleaseBranch ?? isReleaseBranch; + branchConfig.IsMainline = branchConfig.IsMainline ?? isMainline; } static Config ReadConfig(string workingDirectory, IFileSystem fileSystem) diff --git a/src/GitVersionCore/Configuration/Init/SetConfig/GlobalModeSetting.cs b/src/GitVersionCore/Configuration/Init/SetConfig/GlobalModeSetting.cs index b89d33dde8..f26b922add 100644 --- a/src/GitVersionCore/Configuration/Init/SetConfig/GlobalModeSetting.cs +++ b/src/GitVersionCore/Configuration/Init/SetConfig/GlobalModeSetting.cs @@ -28,8 +28,12 @@ protected override StepResult HandleResult(string result, Queue c.Sha != baseVersion.BaseVersionSource.Sha).ToList(); var directCommits = new List(); + // Scans commit log in reverse, aggregating merge commits foreach (var commit in commitLog) { directCommits.Add(commit); if (commit.Parents.Count() > 1) { - // Merge commit, process all merged commits as a batch - var mergeCommit = commit; - var mergedHead = GetMergedHead(mergeCommit); - var findMergeBase = context.Repository.ObjectDatabase.FindMergeBase(mergeCommit.Parents.First(), mergedHead); - var findMessageIncrement = FindMessageIncrement(context, mergeCommit, mergedHead, findMergeBase, directCommits); - - // If this collection is not empty there has been some direct commits against master - // Treat each commit as it's own 'release', we need to do this before we increment the branch - mainlineVersion = IncrementForEachCommit(context, directCommits, mainlineVersion); - directCommits.Clear(); - - // Finally increment for the branch - mainlineVersion = mainlineVersion.IncrementVersion(findMessageIncrement); - Logger.WriteInfo(string.Format("Merge commit {0} incremented base versions {1}, now {2}", - mergeCommit.Sha, findMessageIncrement, mainlineVersion)); + mainlineVersion = AggregateMergeCommitIncrement(context, commit, directCommits, mainlineVersion); } } if (context.CurrentBranch.FriendlyName != "master") { var mergedHead = context.CurrentCommit; - var second = context.Repository.FindBranch("master").Tip; - var findMergeBase = context.Repository.ObjectDatabase.FindMergeBase(context.CurrentCommit, second); + var mainlineTip = GetMainlineTip(context); + var findMergeBase = context.Repository.ObjectDatabase.FindMergeBase(context.CurrentCommit, mainlineTip); Logger.WriteInfo(string.Format("Current branch ({0}) was branch from {1}", context.CurrentBranch.FriendlyName, findMergeBase)); var branchIncrement = FindMessageIncrement(context, null, mergedHead, findMergeBase, directCommits); @@ -151,6 +142,53 @@ SemanticVersion FindMainlineModeVersion(BaseVersion baseVersion, GitVersionConte } } + SemanticVersion AggregateMergeCommitIncrement(GitVersionContext context, Commit commit, List directCommits, SemanticVersion mainlineVersion) + { +// Merge commit, process all merged commits as a batch + var mergeCommit = commit; + var mergedHead = GetMergedHead(mergeCommit); + var findMergeBase = context.Repository.ObjectDatabase.FindMergeBase(mergeCommit.Parents.First(), mergedHead); + var findMessageIncrement = FindMessageIncrement(context, mergeCommit, mergedHead, findMergeBase, directCommits); + + // If this collection is not empty there has been some direct commits against master + // Treat each commit as it's own 'release', we need to do this before we increment the branch + mainlineVersion = IncrementForEachCommit(context, directCommits, mainlineVersion); + directCommits.Clear(); + + // Finally increment for the branch + mainlineVersion = mainlineVersion.IncrementVersion(findMessageIncrement); + Logger.WriteInfo(string.Format("Merge commit {0} incremented base versions {1}, now {2}", + mergeCommit.Sha, findMessageIncrement, mainlineVersion)); + return mainlineVersion; + } + + static Commit GetMainlineTip(GitVersionContext context) + { + var mainlineBranchConfigs = context.FullConfiguration.Branches.Where(b => b.Value.IsMainline == true).ToList(); + var seenMainlineTips = new List(); + var mainlineBranches = context.Repository.Branches.Where(b => + { + return mainlineBranchConfigs.Any(c => Regex.IsMatch(b.FriendlyName, c.Key)); + }).Where(b => + { + if (seenMainlineTips.Contains(b.Tip.Sha)) + { + Logger.WriteInfo("Multiple possible mainlines pointing at the same commit, dropping " + b.FriendlyName); + return false; + } + seenMainlineTips.Add(b.Tip.Sha); + return true; + }).ToDictionary(b => context.Repository.ObjectDatabase.FindMergeBase(b.Tip, context.CurrentCommit).Sha, b => b); + Logger.WriteInfo("Found possible mainline branches: " + string.Join(", ", mainlineBranches.Values.Select(b => b.FriendlyName))); + + // Find closest mainline branch + var firstMatchingCommit = context.CurrentBranch.Commits.First(c => mainlineBranches.ContainsKey(c.Sha)); + var mainlineBranch = mainlineBranches[firstMatchingCommit.Sha]; + Logger.WriteInfo("Mainline for current branch is " + mainlineBranch.FriendlyName); + + return mainlineBranch.Tip; + } + private static SemanticVersion IncrementForEachCommit(GitVersionContext context, List directCommits, SemanticVersion mainlineVersion) { foreach (var directCommit in directCommits)