diff --git a/GitVersionCore.Tests/Fixtures/BaseGitFlowRepositoryFixture.cs b/GitVersionCore.Tests/Fixtures/BaseGitFlowRepositoryFixture.cs index 8c16dffaee..70b6d501d8 100644 --- a/GitVersionCore.Tests/Fixtures/BaseGitFlowRepositoryFixture.cs +++ b/GitVersionCore.Tests/Fixtures/BaseGitFlowRepositoryFixture.cs @@ -1,8 +1,6 @@ using System; using System.IO; -using System.Text; using GitVersion; -using GitVersion.Helpers; using LibGit2Sharp; /// @@ -41,19 +39,4 @@ void SetupRepo(Action initialMasterAction) Repository.CreateBranch("develop").Checkout(); Repository.MakeACommit(); } - - public void DumpGraph() - { - var output = new StringBuilder(); - - ProcessHelper.Run( - o => output.AppendLine(o), - e => output.AppendLineFormat("ERROR: {0}", e), - null, - "git", - @"log --graph --abbrev-commit --decorate --date=relative --all", - RepositoryPath); - - Console.Write(output.ToString()); - } } \ No newline at end of file diff --git a/GitVersionCore.Tests/Fixtures/EmptyRepositoryFixture.cs b/GitVersionCore.Tests/Fixtures/EmptyRepositoryFixture.cs index dc88f61b55..8726144b78 100644 --- a/GitVersionCore.Tests/Fixtures/EmptyRepositoryFixture.cs +++ b/GitVersionCore.Tests/Fixtures/EmptyRepositoryFixture.cs @@ -1,5 +1,7 @@ using System; +using System.Text; using GitVersion; +using GitVersion.Helpers; using LibGit2Sharp; public class EmptyRepositoryFixture : RepositoryFixtureBase @@ -9,6 +11,21 @@ public EmptyRepositoryFixture(Config configuration) : { } + public void DumpGraph() + { + var output = new StringBuilder(); + + ProcessHelper.Run( + o => output.AppendLine(o), + e => output.AppendLineFormat("ERROR: {0}", e), + null, + "git", + @"log --graph --abbrev-commit --decorate --date=relative --all", + RepositoryPath); + + Console.Write(output.ToString()); + } + static IRepository CreateNewRepository(string path) { LibGit2Sharp.Repository.Init(path); diff --git a/GitVersionCore.Tests/GitVersionContextBuilder.cs b/GitVersionCore.Tests/GitVersionContextBuilder.cs new file mode 100644 index 0000000000..6ff6455a18 --- /dev/null +++ b/GitVersionCore.Tests/GitVersionContextBuilder.cs @@ -0,0 +1,80 @@ +namespace GitVersionCore.Tests +{ + using GitVersion; + using LibGit2Sharp; + + public class GitVersionContextBuilder + { + IRepository repository; + Config config; + + public GitVersionContextBuilder WithRepository(IRepository repository) + { + this.repository = repository; + return this; + } + + public GitVersionContextBuilder WithConfig(Config config) + { + this.config = config; + return this; + } + + public GitVersionContextBuilder WithTaggedMaster() + { + repository = CreateRepository(); + var target = repository.Head.Tip; + ((MockTagCollection)repository.Tags).Add(new MockTag ("1.0.0", target)); + return this; + } + + public GitVersionContextBuilder AddCommit() + { + ((MockBranch)repository.Head).Add(new MockCommit()); + return this; + } + + public GitVersionContextBuilder WithDevelopBranch() + { + return WithBranch("develop"); + } + + public GitVersionContextBuilder WithBranch(string branchName) + { + repository = CreateRepository(); + return AddBranch(branchName); + } + + public GitVersionContextBuilder AddBranch(string branchName) + { + var mockBranch = new MockBranch(branchName) + { + new MockCommit() + }; + ((MockBranchCollection)repository.Branches).Add(mockBranch); + ((MockRepository)repository).Head = mockBranch; + return this; + } + + public GitVersionContext Build() + { + return new GitVersionContext(repository ?? CreateRepository(), config ?? new Config()); + } + + IRepository CreateRepository() + { + var mockBranch = new MockBranch("master") { new MockCommit { CommitterEx = SignatureBuilder.SignatureNow() } }; + var mockRepository = new MockRepository + { + Branches = new MockBranchCollection + { + mockBranch + }, + Tags = new MockTagCollection(), + Head = mockBranch + }; + + return mockRepository; + } + } +} \ No newline at end of file diff --git a/GitVersionCore.Tests/GitVersionContextTests.cs b/GitVersionCore.Tests/GitVersionContextTests.cs index 4bb04108a4..1b31db2850 100644 --- a/GitVersionCore.Tests/GitVersionContextTests.cs +++ b/GitVersionCore.Tests/GitVersionContextTests.cs @@ -1,6 +1,7 @@ namespace GitVersionCore.Tests { using GitVersion; + using LibGit2Sharp; using NUnit.Framework; using Shouldly; @@ -50,5 +51,26 @@ public void UsesBranchSpecificConfigOverTopLevelDefaults() context.Configuration.VersioningMode.ShouldBe(VersioningMode.ContinuousDeployment); context.Configuration.Tag.ShouldBe("alpha"); } + + [Test] + public void CanFindParentBranchForInheritingIncrementStrategy() + { + var config = new Config(); + config.Branches["develop"].Increment = IncrementStrategy.Major; + config.Branches["feature[/-]"].Increment = IncrementStrategy.Inherit; + + using (var repo = new EmptyRepositoryFixture(config)) + { + repo.Repository.MakeACommit(); + repo.Repository.CreateBranch("develop").Checkout(); + repo.Repository.MakeACommit(); + var featureBranch = repo.Repository.CreateBranch("feature/foo"); + featureBranch.Checkout(); + repo.Repository.MakeACommit(); + + var context = new GitVersionContext(repo.Repository, config); + context.Configuration.Increment.ShouldBe(IncrementStrategy.Major); + } + } } } \ No newline at end of file diff --git a/GitVersionCore.Tests/GitVersionCore.Tests.csproj b/GitVersionCore.Tests/GitVersionCore.Tests.csproj index 71ec916ba2..df8af3c75d 100644 --- a/GitVersionCore.Tests/GitVersionCore.Tests.csproj +++ b/GitVersionCore.Tests/GitVersionCore.Tests.csproj @@ -96,6 +96,7 @@ + @@ -114,6 +115,15 @@ + + + + + + + + + diff --git a/GitVersionCore.Tests/IntegrationTests/GitFlow/DevelopScenarios.cs b/GitVersionCore.Tests/IntegrationTests/GitFlow/DevelopScenarios.cs index debfe50352..b082dd493e 100644 --- a/GitVersionCore.Tests/IntegrationTests/GitFlow/DevelopScenarios.cs +++ b/GitVersionCore.Tests/IntegrationTests/GitFlow/DevelopScenarios.cs @@ -6,16 +6,29 @@ public class DevelopScenarios { [Test] - public void WhenDevelopBranchedFromMaster_MinorIsIncreased() + public void WhenDevelopBranchedFromTaggedCommitOnMasterVersionDoesNotChange() { using (var fixture = new EmptyRepositoryFixture(new Config())) { fixture.Repository.MakeATaggedCommit("1.0.0"); fixture.Repository.CreateBranch("develop").Checkout(); + // TODO Should actually be 1.0.0+0 fixture.AssertFullSemver("1.1.0-unstable.0+0"); } } + [Test] + public void WhenDevelopBranchedFromMaster_MinorIsIncreased() + { + using (var fixture = new EmptyRepositoryFixture(new Config())) + { + fixture.Repository.MakeATaggedCommit("1.0.0"); + fixture.Repository.CreateBranch("develop").Checkout(); + fixture.Repository.MakeACommit(); + fixture.AssertFullSemver("1.1.0-unstable.1+1"); + } + } + [Test] public void MergingReleaseBranchBackIntoDevelopWithoutMergingToMaster_DoesNotBumpDevelopVersion() { @@ -23,12 +36,13 @@ public void MergingReleaseBranchBackIntoDevelopWithoutMergingToMaster_DoesNotBum { fixture.Repository.MakeATaggedCommit("1.0.0"); fixture.Repository.CreateBranch("develop").Checkout(); + fixture.Repository.MakeACommit(); fixture.Repository.CreateBranch("release-2.0.0").Checkout(); fixture.AssertFullSemver("2.0.0-beta.1+0"); fixture.Repository.Checkout("develop"); - fixture.AssertFullSemver("1.1.0-unstable.0+0"); + fixture.AssertFullSemver("1.1.0-unstable.1+1"); fixture.Repository.MergeNoFF("release-2.0.0", Constants.SignatureNow()); - fixture.AssertFullSemver("1.1.0-unstable.0+0"); + fixture.AssertFullSemver("1.1.0-unstable.1+1"); } } diff --git a/GitVersionCore.Tests/Mocks/MockCommit.cs b/GitVersionCore.Tests/Mocks/MockCommit.cs index 3f6f852eec..a180cac7f7 100644 --- a/GitVersionCore.Tests/Mocks/MockCommit.cs +++ b/GitVersionCore.Tests/Mocks/MockCommit.cs @@ -6,12 +6,17 @@ [DebuggerDisplay("{DebuggerDisplay}")] public class MockCommit : Commit { + static int commitCount = 1; + static DateTimeOffset when = DateTimeOffset.Now; + public MockCommit(ObjectId id = null) { idEx = id ?? new ObjectId(Guid.NewGuid().ToString().Replace("-", "") + "00000000"); - MessageEx = ""; + MessageEx = "Commit " + commitCount++; ParentsEx = new List { null }; - CommitterEx = new Signature("Joe", "Joe@bloggs.net", DateTimeOffset.Now); + CommitterEx = new Signature("Joe", "Joe@bloggs.net", when); + // Make sure each commit is a different time + when = when.AddSeconds(1); } public string MessageEx; diff --git a/GitVersionCore.Tests/Mocks/MockCommitLog.cs b/GitVersionCore.Tests/Mocks/MockCommitLog.cs index aecb492525..a5e3f1d803 100644 --- a/GitVersionCore.Tests/Mocks/MockCommitLog.cs +++ b/GitVersionCore.Tests/Mocks/MockCommitLog.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Collections.Generic; +using System.Linq; using LibGit2Sharp; public class MockCommitLog : ICommitLog, ICollection @@ -8,7 +9,10 @@ public class MockCommitLog : ICommitLog, ICollection public IEnumerator GetEnumerator() { - return Commits.GetEnumerator(); + if (SortedBy == CommitSortStrategies.Reverse) + return Commits.GetEnumerator(); + + return Enumerable.Reverse(Commits).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() diff --git a/GitVersionCore.Tests/Mocks/MockQueryableCommitLog.cs b/GitVersionCore.Tests/Mocks/MockQueryableCommitLog.cs new file mode 100644 index 0000000000..e1120b8f5d --- /dev/null +++ b/GitVersionCore.Tests/Mocks/MockQueryableCommitLog.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using LibGit2Sharp; + +public class MockQueryableCommitLog : IQueryableCommitLog +{ + readonly ICommitLog commits; + + public MockQueryableCommitLog(ICommitLog commits) + { + this.commits = commits; + } + + public IEnumerator GetEnumerator() + { + return commits.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public CommitSortStrategies SortedBy + { + get { throw new NotImplementedException(); } + } + + public ICommitLog QueryBy(CommitFilter filter) + { + throw new NotImplementedException(); + } + + public Commit FindMergeBase(Commit first, Commit second) + { + throw new NotImplementedException(); + } + + public Commit FindMergeBase(IEnumerable commits, MergeBaseFindingStrategy strategy) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/GitVersionCore.Tests/Mocks/MockRepository.cs b/GitVersionCore.Tests/Mocks/MockRepository.cs index 252f527648..ea832500b2 100644 --- a/GitVersionCore.Tests/Mocks/MockRepository.cs +++ b/GitVersionCore.Tests/Mocks/MockRepository.cs @@ -4,6 +4,8 @@ public class MockRepository : IRepository { + IQueryableCommitLog commits; + public MockRepository() { Tags = new MockTagCollection(); @@ -116,7 +118,13 @@ public BlameHunkCollection Blame(string path, BlameOptions options = null) public Configuration Config { get; set; } public Index Index { get; set; } public ReferenceCollection Refs { get; set; } - public IQueryableCommitLog Commits { get; set; } + + public IQueryableCommitLog Commits + { + get { return commits ?? new MockQueryableCommitLog(Head.Commits); } + set { commits = value; } + } + public BranchCollection Branches { get; set; } public TagCollection Tags { get; set; } public RepositoryInformation Info { get; set; } diff --git a/GitVersionCore.Tests/Mocks/MockTag.cs b/GitVersionCore.Tests/Mocks/MockTag.cs index 4ea1364815..5400fb5ee0 100644 --- a/GitVersionCore.Tests/Mocks/MockTag.cs +++ b/GitVersionCore.Tests/Mocks/MockTag.cs @@ -15,6 +15,15 @@ public override GitObject Target get { return TargetEx; } } public TagAnnotation AnnotationEx; + + public MockTag() { } + + public MockTag(string name, Commit target) + { + NameEx = name; + TargetEx = target; + } + public override TagAnnotation Annotation { get { return AnnotationEx; } diff --git a/GitVersionCore.Tests/TestEffectiveConfiguration.cs b/GitVersionCore.Tests/TestEffectiveConfiguration.cs index 055be59556..e0bb2704a4 100644 --- a/GitVersionCore.Tests/TestEffectiveConfiguration.cs +++ b/GitVersionCore.Tests/TestEffectiveConfiguration.cs @@ -9,8 +9,9 @@ public TestEffectiveConfiguration( VersioningMode versioningMode = VersioningMode.ContinuousDelivery, string gitTagPrefix = "v", string tag = "", - string nextVersion = null) : - base(assemblyVersioningScheme, versioningMode, gitTagPrefix, tag, nextVersion) + string nextVersion = null, + string branchPrefixToTrim = "") : + base(assemblyVersioningScheme, versioningMode, gitTagPrefix, tag, nextVersion, IncrementStrategy.Patch, branchPrefixToTrim) { } } diff --git a/GitVersionCore.Tests/VersionCalculation/BaseVersionCalculatorTests.cs b/GitVersionCore.Tests/VersionCalculation/BaseVersionCalculatorTests.cs new file mode 100644 index 0000000000..5f8c204ad7 --- /dev/null +++ b/GitVersionCore.Tests/VersionCalculation/BaseVersionCalculatorTests.cs @@ -0,0 +1,86 @@ +namespace GitVersionCore.Tests.VersionCalculation +{ + using System; + using GitVersion; + using GitVersion.VersionCalculation; + using GitVersion.VersionCalculation.BaseVersionCalculators; + using LibGit2Sharp; + using NUnit.Framework; + using Shouldly; + + [TestFixture] + public class BaseVersionCalculatorTests + { + [Test] + public void ChoosesHighestVersionReturnedFromStrategies() + { + var context = new GitVersionContextBuilder().Build(); + var dateTimeOffset = DateTimeOffset.Now; + var sut = new BaseVersionCalculator(new V1Strategy(DateTimeOffset.Now), new V2Strategy(dateTimeOffset)); + + var baseVersion = sut.GetBaseVersion(context); + + baseVersion.SemanticVersion.ToString().ShouldBe("2.0.0"); + baseVersion.ShouldIncrement.ShouldBe(true); + baseVersion.BaseVersionSource.When().ShouldBe(dateTimeOffset); + } + + [Test] + public void UsesWhenFromNextBestMatchIfHighestDoesntHaveWhen() + { + var context = new GitVersionContextBuilder().Build(); + var when = DateTimeOffset.Now; + var sut = new BaseVersionCalculator(new V1Strategy(when), new V2Strategy(null)); + + var baseVersion = sut.GetBaseVersion(context); + + baseVersion.SemanticVersion.ToString().ShouldBe("2.0.0"); + baseVersion.ShouldIncrement.ShouldBe(true); + baseVersion.BaseVersionSource.When().ShouldBe(when); + } + + [Test] + public void UsesWhenFromNextBestMatchIfHighestDoesntHaveWhenReversedOrder() + { + var context = new GitVersionContextBuilder().Build(); + var when = DateTimeOffset.Now; + var sut = new BaseVersionCalculator(new V1Strategy(null), new V2Strategy(when)); + + var baseVersion = sut.GetBaseVersion(context); + + baseVersion.SemanticVersion.ToString().ShouldBe("2.0.0"); + baseVersion.ShouldIncrement.ShouldBe(true); + baseVersion.BaseVersionSource.When().ShouldBe(when); + } + + class V1Strategy : BaseVersionStrategy + { + readonly Commit when; + + public V1Strategy(DateTimeOffset? when) + { + this.when = when == null ? null : new MockCommit { CommitterEx = when.Value.ToSignature() }; + } + + public override BaseVersion GetVersion(GitVersionContext context) + { + return new BaseVersion(false, true, new SemanticVersion(1), when); + } + } + + class V2Strategy : BaseVersionStrategy + { + Commit when; + + public V2Strategy(DateTimeOffset? when) + { + this.when = when == null ? null : new MockCommit { CommitterEx = when.Value.ToSignature() }; + } + + public override BaseVersion GetVersion(GitVersionContext context) + { + return new BaseVersion(true, true, new SemanticVersion(2), when); + } + } + } +} \ No newline at end of file diff --git a/GitVersionCore.Tests/VersionCalculation/NewNextVersionCalculatorTests.cs b/GitVersionCore.Tests/VersionCalculation/NewNextVersionCalculatorTests.cs new file mode 100644 index 0000000000..dc61b7a4e1 --- /dev/null +++ b/GitVersionCore.Tests/VersionCalculation/NewNextVersionCalculatorTests.cs @@ -0,0 +1,99 @@ +namespace GitVersionCore.Tests.VersionCalculation +{ + using System; + using GitVersion; + using GitVersion.VersionCalculation; + using NUnit.Framework; + using Shouldly; + + public class NewNextVersionCalculatorTests + { + [Test] + public void ShouldIncrementVersionBasedOnConfig() + { + var baseCalculator = new TestBaseVersionCalculator(true, true, new SemanticVersion(1), new MockCommit()); + var semanticVersionBuildMetaData = new SemanticVersionBuildMetaData(1, "master", "b1a34e", DateTimeOffset.Now); + var sut = new NewNextVersionCalculator(baseCalculator, new TestMetaDataCalculator(semanticVersionBuildMetaData)); + var config = new Config(); + config.Branches.Add("master", new BranchConfig + { + Increment = IncrementStrategy.Major + }); + var context = new GitVersionContextBuilder().WithConfig(config).Build(); + + var version = sut.FindVersion(context); + + version.ToString().ShouldBe("2.0.0"); + } + + [Test] + public void DoesNotIncrementWhenBaseVersionSaysNotTo() + { + var baseCalculator = new TestBaseVersionCalculator(false, true, new SemanticVersion(1), new MockCommit()); + var semanticVersionBuildMetaData = new SemanticVersionBuildMetaData(1, "master", "b1a34e", DateTimeOffset.Now); + var sut = new NewNextVersionCalculator(baseCalculator, new TestMetaDataCalculator(semanticVersionBuildMetaData)); + var config = new Config(); + config.Branches.Add("master", new BranchConfig + { + Increment = IncrementStrategy.Major + }); + var context = new GitVersionContextBuilder().WithConfig(config).Build(); + + var version = sut.FindVersion(context); + + version.ToString().ShouldBe("1.0.0"); + } + + [Test] + public void AppliesBranchPreReleaseTag() + { + var baseCalculator = new TestBaseVersionCalculator(false, true, new SemanticVersion(1), new MockCommit()); + var semanticVersionBuildMetaData = new SemanticVersionBuildMetaData(2, "develop", "b1a34e", DateTimeOffset.Now); + var sut = new NewNextVersionCalculator(baseCalculator, new TestMetaDataCalculator(semanticVersionBuildMetaData)); + var context = new GitVersionContextBuilder() + .WithDevelopBranch() + .Build(); + + var version = sut.FindVersion(context); + + version.ToString("f").ShouldBe("1.0.0-unstable.1+2"); + } + + [Test] + public void DoesNotApplyPreReleaseTagWhenBaseVersionSaysNotTo() + { + var baseCalculator = new TestBaseVersionCalculator(false, false, new SemanticVersion(1), new MockCommit()); + var semanticVersionBuildMetaData = new SemanticVersionBuildMetaData(1, "develop", "b1a34e", DateTimeOffset.Now); + var sut = new NewNextVersionCalculator(baseCalculator, new TestMetaDataCalculator(semanticVersionBuildMetaData)); + var context = new GitVersionContextBuilder() + .WithDevelopBranch() + .Build(); + + var version = sut.FindVersion(context); + + version.ToString("f").ShouldBe("1.0.0+1"); + } + + [Test] + public void PreReleaseTagCanUseBranchName() + { + var baseCalculator = new TestBaseVersionCalculator(false, true, new SemanticVersion(1), new MockCommit()); + var semanticVersionBuildMetaData = new SemanticVersionBuildMetaData(2, "develop", "b1a34e", DateTimeOffset.Now); + var sut = new NewNextVersionCalculator(baseCalculator, new TestMetaDataCalculator(semanticVersionBuildMetaData)); + var config = new Config(); + config.Branches.Add("custom/", new BranchConfig + { + Tag = "useBranchName" + }); + var context = new GitVersionContextBuilder() + .WithConfig(config) + .WithDevelopBranch() + .AddBranch("custom/foo") + .Build(); + + var version = sut.FindVersion(context); + + version.ToString("f").ShouldBe("1.0.0-foo.1+2"); + } + } +} \ No newline at end of file diff --git a/GitVersionCore.Tests/VersionCalculation/Strategies/ConfigNextVersionBaseVersionStrategyTests.cs b/GitVersionCore.Tests/VersionCalculation/Strategies/ConfigNextVersionBaseVersionStrategyTests.cs new file mode 100644 index 0000000000..d68469ccfd --- /dev/null +++ b/GitVersionCore.Tests/VersionCalculation/Strategies/ConfigNextVersionBaseVersionStrategyTests.cs @@ -0,0 +1,38 @@ +namespace GitVersionCore.Tests.VersionCalculation.Strategies +{ + using GitVersion; + using GitVersion.VersionCalculation.BaseVersionCalculators; + using NUnit.Framework; + using Shouldly; + + [TestFixture] + public class ConfigNextVersionBaseVersionStrategyTests + { + [Test] + public void ShouldNotBeIncremented() + { + var contextBuilder = new GitVersionContextBuilder() + .WithConfig(new Config + { + NextVersion = "1.0.0" + }); + var sut = new ConfigNextVersionBaseVersionStrategy(); + + var baseVersion = sut.GetVersion(contextBuilder.Build()); + + baseVersion.ShouldIncrement.ShouldBe(false); + baseVersion.SemanticVersion.ToString().ShouldBe("1.0.0"); + } + + [Test] + public void ReturnsNullWhenNoNextVersionIsInConfig() + { + var contextBuilder = new GitVersionContextBuilder(); + var sut = new ConfigNextVersionBaseVersionStrategy(); + + var baseVersion = sut.GetVersion(contextBuilder.Build()); + + baseVersion.ShouldBe(null); + } + } +} \ No newline at end of file diff --git a/GitVersionCore.Tests/VersionCalculation/Strategies/LastTagBaseVersionStrategyTests.cs b/GitVersionCore.Tests/VersionCalculation/Strategies/LastTagBaseVersionStrategyTests.cs new file mode 100644 index 0000000000..7341233a86 --- /dev/null +++ b/GitVersionCore.Tests/VersionCalculation/Strategies/LastTagBaseVersionStrategyTests.cs @@ -0,0 +1,39 @@ +namespace GitVersionCore.Tests.VersionCalculation.Strategies +{ + using GitVersion.VersionCalculation.BaseVersionCalculators; + using NUnit.Framework; + using Shouldly; + + [TestFixture] + public class LastTagBaseVersionStrategyTests + { + [Test] + public void ShouldAllowVersionIncrement() + { + // TODO Looks like our MockRepostory stuff doesn't work properly. commits are added to end of list, but Tip is first. + // Changing behaviour breaks a bunch of tests + var context = new GitVersionContextBuilder() + .WithTaggedMaster() + .AddCommit() + .Build(); + var sut = new LastTagBaseVersionStrategy(); + + var baseVersion = sut.GetVersion(context); + + baseVersion.ShouldIncrement.ShouldBe(true); + } + + [Test] + public void ShouldNotAllowVersionIncrementWhenTagComesFromCurrentCommit() + { + var context = new GitVersionContextBuilder() + .WithTaggedMaster() + .Build(); + var sut = new LastTagBaseVersionStrategy(); + + var baseVersion = sut.GetVersion(context); + + baseVersion.ShouldIncrement.ShouldBe(false); + } + } +} \ No newline at end of file diff --git a/GitVersionCore.Tests/VersionCalculation/Strategies/MergeMessageBaseVersionStrategyTests.cs b/GitVersionCore.Tests/VersionCalculation/Strategies/MergeMessageBaseVersionStrategyTests.cs new file mode 100644 index 0000000000..ac22497e58 --- /dev/null +++ b/GitVersionCore.Tests/VersionCalculation/Strategies/MergeMessageBaseVersionStrategyTests.cs @@ -0,0 +1,110 @@ +namespace GitVersionCore.Tests.VersionCalculation.Strategies +{ + using System.Collections.Generic; + using GitVersion.VersionCalculation.BaseVersionCalculators; + using LibGit2Sharp; + using NUnit.Framework; + using Shouldly; + + [TestFixture] + public class MergeMessageBaseVersionStrategyTests + { + [Test] + public void ShouldAllowIncrementOfVersion() + { + var context = new GitVersionContextBuilder().WithRepository(new MockRepository + { + Head = new MockBranch("master") { new MockCommit + { + MessageEx = "Merge branch 'hotfix-0.1.5'", + ParentsEx = GetParents(true) + } } + }).Build(); + var sut = new MergeMessageBaseVersionStrategy(); + + var baseVersion = sut.GetVersion(context); + + baseVersion.ShouldIncrement.ShouldBe(true); + } + + [TestCase("Merge branch 'hotfix-0.1.5'", false, null)] + [TestCase("Merge branch 'develop' of github.com:Particular/NServiceBus into develop", true, null)] + [TestCase("Merge branch '4.0.3'", true, "4.0.3")] //TODO: possible make it a config option to support this + [TestCase("Merge branch 'release-10.10.50'", true, "10.10.50")] + [TestCase("Merge branch 's'", true, null)] // Must start with a number + [TestCase("Merge branch 'release-0.2.0'", true, "0.2.0")] + [TestCase("Merge branch 'hotfix-4.6.6' into support-4.6", true, "4.6.6")] + [TestCase("Merge branch 'hotfix-10.10.50'", true, "10.10.50")] + [TestCase("Merge branch 'hotfix-0.1.5'", true, "0.1.5")] + [TestCase("Merge branch 'hotfix-0.1.5'\n\nRelates to: TicketId", true, "0.1.5")] + [TestCase("Merge branch 'alpha-0.1.5'", true, "0.1.5")] + [TestCase("Merge pull request #165 from Particular/release-1.0.0", true, "1.0.0")] + [TestCase("Merge pull request #95 from Particular/issue-94", false, null)] + [TestCase("Merge pull request #165 in Particular/release-1.0.0", true, "1.0.0")] + [TestCase("Merge pull request #95 in Particular/issue-94", true, null)] + [TestCase("Merge pull request #95 in Particular/issue-94", false, null)] + [TestCase("Merge pull request #64 from arledesma/feature-VS2013_3rd_party_test_framework_support", true, null)] + [TestCase("Finish Release-0.12.0", true, "0.12.0")] //Support Syntevo SmartGit/Hg's Gitflow merge commit messages for finishing a 'Release' branch + [TestCase("Finish 0.14.1", true, "0.14.1")] //Support Syntevo SmartGit/Hg's Gitflow merge commit messages for finishing a 'Hotfix' branch + public void AssertMergeMessage(string message, bool isMergeCommit, string expectedVersion) + { + var parents = GetParents(isMergeCommit); + AssertMergeMessage(message, expectedVersion, parents); + AssertMergeMessage(message + " ", expectedVersion, parents); + AssertMergeMessage(message + "\r ", expectedVersion, parents); + AssertMergeMessage(message + "\r", expectedVersion, parents); + AssertMergeMessage(message + "\r\n", expectedVersion, parents); + AssertMergeMessage(message + "\r\n ", expectedVersion, parents); + AssertMergeMessage(message + "\n", expectedVersion, parents); + AssertMergeMessage(message + "\n ", expectedVersion, parents); + } + + static void AssertMergeMessage(string message, string expectedVersion, List parents) + { + var commit = new MockCommit + { + MessageEx = message, + ParentsEx = parents + }; + + var context = new GitVersionContextBuilder() + .WithRepository(new MockRepository + { + Head = new MockBranch("master") + { + commit, + new MockCommit() + } + }) + .Build(); + var sut = new MergeMessageBaseVersionStrategy(); + + var baseVersion = sut.GetVersion(context); + + if (expectedVersion == null) + { + baseVersion.ShouldBe(null); + } + else + { + baseVersion.SemanticVersion.ToString().ShouldBe(expectedVersion); + } + } + + static List GetParents(bool isMergeCommit) + { + if (isMergeCommit) + { + return new List + { + null, + null + }; + } + return new List + { + null + }; + } + } +} \ No newline at end of file diff --git a/GitVersionCore.Tests/VersionCalculation/Strategies/VersionInBranchBaseVersionStrategyTests.cs b/GitVersionCore.Tests/VersionCalculation/Strategies/VersionInBranchBaseVersionStrategyTests.cs new file mode 100644 index 0000000000..9900c795a5 --- /dev/null +++ b/GitVersionCore.Tests/VersionCalculation/Strategies/VersionInBranchBaseVersionStrategyTests.cs @@ -0,0 +1,34 @@ +namespace GitVersionCore.Tests.VersionCalculation.Strategies +{ + using GitVersion.VersionCalculation.BaseVersionCalculators; + using NUnit.Framework; + using Shouldly; + + [TestFixture] + public class VersionInBranchBaseVersionStrategyTests + { + [Test] + [TestCase("release-2.0.0", "2.0.0")] + [TestCase("release/2.0.0", "2.0.0")] + [TestCase("hotfix-2.0.0", "2.0.0")] + [TestCase("hotfix/2.0.0", "2.0.0")] + [TestCase("hotfix/2.0.0", "2.0.0")] + [TestCase("custom/JIRA-123", null)] + public void CanTakeVersionFromBranchName(string branchName, string expectedBaseVersion) + { + var context = new GitVersionContextBuilder() + .WithBranch(branchName) + .AddCommit() + .Build(); + + var sut = new VersionInBranchBaseVersionStrategy(); + + var baseVersion = sut.GetVersion(context); + + if (expectedBaseVersion == null) + baseVersion.ShouldBe(null); + else + baseVersion.SemanticVersion.ToString().ShouldBe(expectedBaseVersion); + } + } +} diff --git a/GitVersionCore.Tests/VersionCalculation/TestBaseVersionCalculator.cs b/GitVersionCore.Tests/VersionCalculation/TestBaseVersionCalculator.cs new file mode 100644 index 0000000000..f38dae098d --- /dev/null +++ b/GitVersionCore.Tests/VersionCalculation/TestBaseVersionCalculator.cs @@ -0,0 +1,28 @@ +namespace GitVersionCore.Tests.VersionCalculation +{ + using GitVersion; + using GitVersion.VersionCalculation; + using GitVersion.VersionCalculation.BaseVersionCalculators; + using LibGit2Sharp; + + public class TestBaseVersionCalculator : IBaseVersionCalculator + { + readonly SemanticVersion semanticVersion; + bool shouldIncrement; + bool shouldUpdateTag; + Commit source; + + public TestBaseVersionCalculator(bool shouldIncrement, bool shouldUpdateTag, SemanticVersion semanticVersion, Commit source) + { + this.semanticVersion = semanticVersion; + this.source = source; + this.shouldUpdateTag = shouldUpdateTag; + this.shouldIncrement = shouldIncrement; + } + + public BaseVersion GetBaseVersion(GitVersionContext context) + { + return new BaseVersion(shouldIncrement, shouldUpdateTag, semanticVersion, source); + } + } +} \ No newline at end of file diff --git a/GitVersionCore.Tests/VersionCalculation/TestMetaDataCalculator.cs b/GitVersionCore.Tests/VersionCalculation/TestMetaDataCalculator.cs new file mode 100644 index 0000000000..51f7a95b22 --- /dev/null +++ b/GitVersionCore.Tests/VersionCalculation/TestMetaDataCalculator.cs @@ -0,0 +1,21 @@ +namespace GitVersionCore.Tests.VersionCalculation +{ + using GitVersion; + using GitVersion.VersionCalculation; + using LibGit2Sharp; + + public class TestMetaDataCalculator : IMetaDataCalculator + { + SemanticVersionBuildMetaData metaData; + + public TestMetaDataCalculator(SemanticVersionBuildMetaData metaData) + { + this.metaData = metaData; + } + + public SemanticVersionBuildMetaData Create(Commit baseVersionSource, GitVersionContext context) + { + return metaData; + } + } +} \ No newline at end of file diff --git a/GitVersionCore/Configuration/BranchConfig.cs b/GitVersionCore/Configuration/BranchConfig.cs index 1b7494e0f0..2f09fd4f90 100644 --- a/GitVersionCore/Configuration/BranchConfig.cs +++ b/GitVersionCore/Configuration/BranchConfig.cs @@ -4,10 +4,27 @@ public class BranchConfig { + public BranchConfig() + { + } + + public BranchConfig(BranchConfig branchConfiguration) + { + VersioningMode = branchConfiguration.VersioningMode; + Tag = branchConfiguration.Tag; + Increment = branchConfiguration.Increment; + } + [YamlAlias("mode")] public VersioningMode? VersioningMode { get; set; } + /// + /// Special value 'useBranchName' will extract the tag from the branch name + /// [YamlAlias("tag")] public string Tag { get; set; } + + [YamlAlias("increment")] + public IncrementStrategy? Increment { get; set; } } } diff --git a/GitVersionCore/Configuration/Config.cs b/GitVersionCore/Configuration/Config.cs index ed487c57db..2d602b338f 100644 --- a/GitVersionCore/Configuration/Config.cs +++ b/GitVersionCore/Configuration/Config.cs @@ -14,10 +14,16 @@ public Config() TagPrefix = "[vV]"; VersioningMode = GitVersion.VersioningMode.ContinuousDelivery; Branches["release[/-]"] = new BranchConfig { Tag = "beta" }; + Branches["feature[/-]"] = new BranchConfig + { + Increment = IncrementStrategy.Inherit, + Tag = "useBranchName" + }; Branches["hotfix[/-]"] = new BranchConfig { Tag = "beta" }; Branches["develop"] = new BranchConfig { Tag = "unstable", + Increment = IncrementStrategy.Minor, VersioningMode = GitVersion.VersioningMode.ContinuousDeployment }; } diff --git a/GitVersionCore/Configuration/IncrementStrategy.cs b/GitVersionCore/Configuration/IncrementStrategy.cs new file mode 100644 index 0000000000..cd3b8de0b7 --- /dev/null +++ b/GitVersionCore/Configuration/IncrementStrategy.cs @@ -0,0 +1,14 @@ +namespace GitVersion +{ + public enum IncrementStrategy + { + None, + Major, + Minor, + Patch, + /// + /// Uses the increment strategy from the branch the current branch was branched from + /// + Inherit + } +} \ No newline at end of file diff --git a/GitVersionCore/EffectiveConfiguration.cs b/GitVersionCore/EffectiveConfiguration.cs index 9daed63748..0c8a66c3ae 100644 --- a/GitVersionCore/EffectiveConfiguration.cs +++ b/GitVersionCore/EffectiveConfiguration.cs @@ -5,13 +5,15 @@ /// public class EffectiveConfiguration { - public EffectiveConfiguration(AssemblyVersioningScheme assemblyVersioningScheme, VersioningMode versioningMode, string gitTagPrefix, string tag, string nextVersion) + public EffectiveConfiguration(AssemblyVersioningScheme assemblyVersioningScheme, VersioningMode versioningMode, string gitTagPrefix, string tag, string nextVersion, IncrementStrategy increment, string branchPrefixToTrim) { AssemblyVersioningScheme = assemblyVersioningScheme; VersioningMode = versioningMode; GitTagPrefix = gitTagPrefix; Tag = tag; NextVersion = nextVersion; + Increment = increment; + BranchPrefixToTrim = branchPrefixToTrim; } public VersioningMode VersioningMode { get; private set; } @@ -29,5 +31,9 @@ public EffectiveConfiguration(AssemblyVersioningScheme assemblyVersioningScheme, public string Tag { get; private set; } public string NextVersion { get; private set; } + + public IncrementStrategy Increment { get; private set; } + + public string BranchPrefixToTrim { get; private set; } } } \ No newline at end of file diff --git a/GitVersionCore/GitVersionContext.cs b/GitVersionCore/GitVersionContext.cs index 3bd8a0ef7e..0b8cbf2c67 100644 --- a/GitVersionCore/GitVersionContext.cs +++ b/GitVersionCore/GitVersionContext.cs @@ -81,30 +81,82 @@ IEnumerable GetBranchesContainingCommit(string commitSha) void CalculateEffectiveConfiguration() { - var matchingBranches = configuration.Branches.Where(b => Regex.IsMatch("^" + CurrentBranch.Name, b.Key)).ToArray(); + var currentBranchConfig = GetBranchConfiguration(CurrentBranch); - var currentBranchConfig = GetBranchConfiguration(matchingBranches); - - var versioningMode = currentBranchConfig.VersioningMode ?? configuration.VersioningMode ?? VersioningMode.ContinuousDelivery; - var tag = currentBranchConfig.Tag; + var versioningMode = currentBranchConfig.Value.VersioningMode ?? configuration.VersioningMode ?? VersioningMode.ContinuousDelivery; + var tag = currentBranchConfig.Value.Tag; var nextVersion = configuration.NextVersion; - - Configuration = new EffectiveConfiguration(configuration.AssemblyVersioningScheme, versioningMode, configuration.TagPrefix, tag, nextVersion); + var incrementStrategy = currentBranchConfig.Value.Increment ?? IncrementStrategy.Patch; + var assemblyVersioningScheme = configuration.AssemblyVersioningScheme; + var gitTagPrefix = configuration.TagPrefix; + Configuration = new EffectiveConfiguration(assemblyVersioningScheme, versioningMode, gitTagPrefix, tag, nextVersion, incrementStrategy, currentBranchConfig.Key); } - BranchConfig GetBranchConfiguration(KeyValuePair[] matchingBranches) + KeyValuePair GetBranchConfiguration(Branch currentBranch) { + var matchingBranches = configuration.Branches.Where(b => Regex.IsMatch("^" + currentBranch.Name, b.Key)).ToArray(); + if (matchingBranches.Length == 0) { - return new BranchConfig(); + return new KeyValuePair(string.Empty, new BranchConfig()); } if (matchingBranches.Length == 1) { - return matchingBranches[0].Value; + var keyValuePair = matchingBranches[0]; + var branchConfiguration = keyValuePair.Value; + + if (branchConfiguration.Increment == IncrementStrategy.Inherit) + { + var branchPoint = currentBranch.FindCommitBranchWasBranchedFrom(Repository); + + List possibleParents; + if (branchPoint.Sha == CurrentCommit.Sha) + { + possibleParents = ListBranchesContaininingCommit(Repository, CurrentCommit.Sha).Except(new[] + { + currentBranch + }).ToList(); + } + else + { + var branches = ListBranchesContaininingCommit(Repository, branchPoint.Sha).ToArray(); + var currentTipBranches = ListBranchesContaininingCommit(Repository, CurrentCommit.Sha).ToArray(); + possibleParents = branches + .Except(currentTipBranches) + .ToList(); + } + + // If it comes down to master and something, master is always first so we pick other branch + if (possibleParents.Count == 2 && possibleParents.Any(p => p.Name == "master")) + possibleParents.Remove(possibleParents.Single(p => p.Name == "master")); + + if (possibleParents.Count == 1) + { + return new KeyValuePair( + keyValuePair.Key, + new BranchConfig(branchConfiguration) + { + Increment = GetBranchConfiguration(possibleParents[0]).Value.Increment + }); + } + + throw new Exception("Failed to inherit Increment branch configuration"); + } + + return keyValuePair; } - const string format = "Multiple branch configurations match the current branch name of '{0}'. Matching configurations: '{1}'"; - throw new Exception(string.Format(format, CurrentBranch.Name, string.Join(", ", matchingBranches.Select(b => b.Key)))); + const string format = "Multiple branch configurations match the current branch branchName of '{0}'. Matching configurations: '{1}'"; + throw new Exception(string.Format(format, currentBranch.Name, string.Join(", ", matchingBranches.Select(b => b.Key)))); + } + + static IEnumerable ListBranchesContaininingCommit(IRepository repo, string commitSha) + { + return from branch in repo.Branches + where !branch.IsRemote + let commits = repo.Commits.QueryBy(new CommitFilter { Since = branch }).Where(c => c.Sha == commitSha) + where commits.Any() + select branch; } } } \ No newline at end of file diff --git a/GitVersionCore/GitVersionCore.csproj b/GitVersionCore/GitVersionCore.csproj index 1505d8477c..c67c96743e 100644 --- a/GitVersionCore/GitVersionCore.csproj +++ b/GitVersionCore/GitVersionCore.csproj @@ -74,6 +74,7 @@ + @@ -87,6 +88,18 @@ + + + + + + + + + + + + @@ -143,6 +156,7 @@ + diff --git a/GitVersionCore/GitVersionFinder.cs b/GitVersionCore/GitVersionFinder.cs index c9faa91e15..05bda271d2 100644 --- a/GitVersionCore/GitVersionFinder.cs +++ b/GitVersionCore/GitVersionFinder.cs @@ -3,6 +3,7 @@ namespace GitVersion using System; using System.IO; using System.Linq; + using GitVersion.VersionCalculation; using LibGit2Sharp; public class GitVersionFinder @@ -18,20 +19,21 @@ public SemanticVersion FindVersion(GitVersionContext context) throw new Exception("NextVersion.txt has been depreciated. See https://github.com/ParticularLabs/GitVersion/wiki/GitVersionConfig.yaml-Configuration-File for replacement"); } - if (ShouldGitHubFlowVersioningSchemeApply(context.Repository)) - { - Logger.WriteInfo("GitHubFlow version strategy will be used"); - return new GitHubFlowVersionFinder().FindVersion(context); - } + return new NewNextVersionCalculator().FindVersion(context); + //if (ShouldGitHubFlowVersioningSchemeApply(context.Repository)) + //{ + // Logger.WriteInfo("GitHubFlow version strategy will be used"); + // return new GitHubFlowVersionFinder().FindVersion(context); + //} - Logger.WriteInfo("GitFlow version strategy will be used"); - return new GitFlowVersionFinder().FindVersion(context); + //Logger.WriteInfo("GitFlow version strategy will be used"); + //return new GitFlowVersionFinder().FindVersion(context); } - static bool ShouldGitHubFlowVersioningSchemeApply(IRepository repo) - { - return repo.FindBranch("develop") == null; - } + //static bool ShouldGitHubFlowVersioningSchemeApply(IRepository repo) + //{ + // return repo.FindBranch("develop") == null; + //} void EnsureMainTopologyConstraints(GitVersionContext context) { diff --git a/GitVersionCore/LibGitExtensions.cs b/GitVersionCore/LibGitExtensions.cs index bbea6117b3..e7ad5e5b19 100644 --- a/GitVersionCore/LibGitExtensions.cs +++ b/GitVersionCore/LibGitExtensions.cs @@ -24,6 +24,12 @@ public static Branch FindBranch(this IRepository repository, string branchName) return repository.Branches.FirstOrDefault(x => x.Name == "origin/" + branchName); } + public static Commit FindCommitBranchWasBranchedFrom(this Branch branch, IRepository repository) + { + var tips = repository.Branches.Select(b => b.Tip).Where(c => c.Sha != branch.Tip.Sha).ToList(); + return repository.Commits.FirstOrDefault(c => tips.Contains(c) || c.Parents.Count() > 1) ?? branch.Tip; + } + public static IEnumerable TagsByDate(this IRepository repository, Commit commit) { return repository.Tags diff --git a/GitVersionCore/MergeMessageParser.cs b/GitVersionCore/MergeMessageParser.cs index e97b741335..a477a4da27 100644 --- a/GitVersionCore/MergeMessageParser.cs +++ b/GitVersionCore/MergeMessageParser.cs @@ -6,14 +6,14 @@ namespace GitVersion static class MergeMessageParser { - public static bool TryParse(Commit mergeCommit, EffectiveConfiguration configuration, out SemanticVersion shortVersion) + public static bool TryParse(Commit mergeCommit, EffectiveConfiguration configuration, out SemanticVersion semanticVersion) { string versionPart; if (Inner(mergeCommit, out versionPart)) { - return SemanticVersion.TryParse(versionPart, configuration.GitTagPrefix, out shortVersion); + return SemanticVersion.TryParse(versionPart, configuration.GitTagPrefix, out semanticVersion); } - shortVersion = null; + semanticVersion = null; return false; } diff --git a/GitVersionCore/SemanticVersion.cs b/GitVersionCore/SemanticVersion.cs index 77e628ce14..e5a4778854 100644 --- a/GitVersionCore/SemanticVersion.cs +++ b/GitVersionCore/SemanticVersion.cs @@ -15,8 +15,11 @@ public class SemanticVersion : IFormattable, IComparable public SemanticVersionPreReleaseTag PreReleaseTag; public SemanticVersionBuildMetaData BuildMetaData; - public SemanticVersion() + public SemanticVersion(int major = 0, int minor = 0, int patch = 0) { + Major = major; + Minor = minor; + Patch = patch; PreReleaseTag = new SemanticVersionPreReleaseTag(); BuildMetaData = new SemanticVersionBuildMetaData(); } diff --git a/GitVersionCore/VersionCalculation/BaseVersionCalculator.cs b/GitVersionCore/VersionCalculation/BaseVersionCalculator.cs new file mode 100644 index 0000000000..a87e55fd8c --- /dev/null +++ b/GitVersionCore/VersionCalculation/BaseVersionCalculator.cs @@ -0,0 +1,31 @@ +namespace GitVersion.VersionCalculation +{ + using System.Linq; + using BaseVersionCalculators; + + public class BaseVersionCalculator : IBaseVersionCalculator + { + readonly BaseVersionStrategy[] strategies; + + public BaseVersionCalculator(params BaseVersionStrategy[] strategies) + { + this.strategies = strategies; + } + + public BaseVersion GetBaseVersion(GitVersionContext context) + { + return strategies + .Select(s => s.GetVersion(context)) + .Where(v => v != null) + .Aggregate((v1, v2) => + { + if (v1.SemanticVersion > v2.SemanticVersion) + { + return new BaseVersion(v1.ShouldIncrement, v1.ShouldUpdateTag, v1.SemanticVersion, v1.BaseVersionSource ?? v2.BaseVersionSource); + } + + return new BaseVersion(v2.ShouldIncrement, v2.ShouldUpdateTag, v2.SemanticVersion, v2.BaseVersionSource ?? v1.BaseVersionSource); + }); + } + } +} \ No newline at end of file diff --git a/GitVersionCore/VersionCalculation/BaseVersionCalculators/BaseVersion.cs b/GitVersionCore/VersionCalculation/BaseVersionCalculators/BaseVersion.cs new file mode 100644 index 0000000000..f566090ce1 --- /dev/null +++ b/GitVersionCore/VersionCalculation/BaseVersionCalculators/BaseVersion.cs @@ -0,0 +1,23 @@ +namespace GitVersion.VersionCalculation.BaseVersionCalculators +{ + using LibGit2Sharp; + + public class BaseVersion + { + public BaseVersion(bool shouldIncrement, bool shouldUpdateTag, SemanticVersion semanticVersion, Commit baseVersionSource) + { + ShouldIncrement = shouldIncrement; + ShouldUpdateTag = shouldUpdateTag; + SemanticVersion = semanticVersion; + BaseVersionSource = baseVersionSource; + } + + public bool ShouldIncrement { get; private set; } + + public bool ShouldUpdateTag { get; private set; } + + public SemanticVersion SemanticVersion { get; private set; } + + public Commit BaseVersionSource { get; private set; } + } +} \ No newline at end of file diff --git a/GitVersionCore/VersionCalculation/BaseVersionCalculators/ConfigNextVersionBaseVersionStrategy.cs b/GitVersionCore/VersionCalculation/BaseVersionCalculators/ConfigNextVersionBaseVersionStrategy.cs new file mode 100644 index 0000000000..3bccf10ad9 --- /dev/null +++ b/GitVersionCore/VersionCalculation/BaseVersionCalculators/ConfigNextVersionBaseVersionStrategy.cs @@ -0,0 +1,12 @@ +namespace GitVersion.VersionCalculation.BaseVersionCalculators +{ + public class ConfigNextVersionBaseVersionStrategy : BaseVersionStrategy + { + public override BaseVersion GetVersion(GitVersionContext context) + { + if (string.IsNullOrEmpty(context.Configuration.NextVersion)) + return null; + return new BaseVersion(false, true, SemanticVersion.Parse(context.Configuration.NextVersion, context.Configuration.GitTagPrefix), null); + } + } +} \ No newline at end of file diff --git a/GitVersionCore/VersionCalculation/BaseVersionCalculators/LastTagBaseVersionStrategy.cs b/GitVersionCore/VersionCalculation/BaseVersionCalculators/LastTagBaseVersionStrategy.cs new file mode 100644 index 0000000000..ce621a87af --- /dev/null +++ b/GitVersionCore/VersionCalculation/BaseVersionCalculators/LastTagBaseVersionStrategy.cs @@ -0,0 +1,17 @@ +namespace GitVersion.VersionCalculation.BaseVersionCalculators +{ + public class LastTagBaseVersionStrategy : BaseVersionStrategy + { + public override BaseVersion GetVersion(GitVersionContext context) + { + VersionTaggedCommit version; + if (new LastTaggedReleaseFinder(context).GetVersion(out version)) + { + var shouldUpdateVersion = version.Commit.Sha != context.CurrentCommit.Sha; + return new BaseVersion(shouldUpdateVersion, shouldUpdateVersion, version.SemVer, version.Commit); + } + + return null; + } + } +} \ No newline at end of file diff --git a/GitVersionCore/VersionCalculation/BaseVersionCalculators/MergeMessageBaseVersionStrategy.cs b/GitVersionCore/VersionCalculation/BaseVersionCalculators/MergeMessageBaseVersionStrategy.cs new file mode 100644 index 0000000000..dea7b0736f --- /dev/null +++ b/GitVersionCore/VersionCalculation/BaseVersionCalculators/MergeMessageBaseVersionStrategy.cs @@ -0,0 +1,28 @@ +namespace GitVersion.VersionCalculation.BaseVersionCalculators +{ + using System.Linq; + + public class MergeMessageBaseVersionStrategy : BaseVersionStrategy + { + public override BaseVersion GetVersion(GitVersionContext context) + { + var commitsPriorToThan = context.CurrentBranch + .CommitsPriorToThan(context.CurrentCommit.When()); + var baseVersions = commitsPriorToThan + .SelectMany(c => + { + SemanticVersion semanticVersion; + // TODO when this approach works, inline the other class into here + if (MergeMessageParser.TryParse(c, context.Configuration, out semanticVersion)) + return new[] + { + new BaseVersion(true, true, semanticVersion, c) + }; + return Enumerable.Empty(); + }) + .ToArray(); + + return baseVersions.Length > 1 ? baseVersions.Aggregate((x, y) => x.SemanticVersion > y.SemanticVersion ? x : y) : baseVersions.SingleOrDefault(); + } + } +} \ No newline at end of file diff --git a/GitVersionCore/VersionCalculation/BaseVersionCalculators/VersionInBranchBaseVersionStrategy.cs b/GitVersionCore/VersionCalculation/BaseVersionCalculators/VersionInBranchBaseVersionStrategy.cs new file mode 100644 index 0000000000..7531c2a989 --- /dev/null +++ b/GitVersionCore/VersionCalculation/BaseVersionCalculators/VersionInBranchBaseVersionStrategy.cs @@ -0,0 +1,36 @@ +namespace GitVersion.VersionCalculation.BaseVersionCalculators +{ + using System; + using System.Linq; + + public class VersionInBranchBaseVersionStrategy : BaseVersionStrategy + { + public override BaseVersion GetVersion(GitVersionContext context) + { + var versionInBranch = GetVersionInBranch(context); + if (versionInBranch != null) + { + var commitBranchWasBranchedFrom = context.CurrentBranch.FindCommitBranchWasBranchedFrom(context.Repository); + var baseVersionSource = context.CurrentBranch.Commits.First(c => c.Sha != commitBranchWasBranchedFrom.Sha); + return new BaseVersion(false, true, versionInBranch.Item2, baseVersionSource); + } + + return null; + } + + Tuple GetVersionInBranch(GitVersionContext context) + { + var branchParts = context.CurrentBranch.Name.Split('/', '-'); + foreach (var part in branchParts) + { + SemanticVersion semanticVersion; + if (SemanticVersion.TryParse(part, context.Configuration.GitTagPrefix, out semanticVersion)) + { + return Tuple.Create(part, semanticVersion); + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/GitVersionCore/VersionCalculation/BaseVersionStrategy.cs b/GitVersionCore/VersionCalculation/BaseVersionStrategy.cs new file mode 100644 index 0000000000..855d4cbc3a --- /dev/null +++ b/GitVersionCore/VersionCalculation/BaseVersionStrategy.cs @@ -0,0 +1,9 @@ +namespace GitVersion.VersionCalculation +{ + using GitVersion.VersionCalculation.BaseVersionCalculators; + + public abstract class BaseVersionStrategy + { + public abstract BaseVersion GetVersion(GitVersionContext context); + } +} \ No newline at end of file diff --git a/GitVersionCore/VersionCalculation/FallbackBaseVersionStrategy.cs b/GitVersionCore/VersionCalculation/FallbackBaseVersionStrategy.cs new file mode 100644 index 0000000000..e0943ff2e4 --- /dev/null +++ b/GitVersionCore/VersionCalculation/FallbackBaseVersionStrategy.cs @@ -0,0 +1,13 @@ +namespace GitVersion.VersionCalculation +{ + using System.Linq; + using GitVersion.VersionCalculation.BaseVersionCalculators; + + public class FallbackBaseVersionStrategy : BaseVersionStrategy + { + public override BaseVersion GetVersion(GitVersionContext context) + { + return new BaseVersion(false, true, new SemanticVersion(minor: 1), context.CurrentBranch.Commits.Last()); + } + } +} \ No newline at end of file diff --git a/GitVersionCore/VersionCalculation/IBaseVersionCalculator.cs b/GitVersionCore/VersionCalculation/IBaseVersionCalculator.cs new file mode 100644 index 0000000000..b6e8f3d67a --- /dev/null +++ b/GitVersionCore/VersionCalculation/IBaseVersionCalculator.cs @@ -0,0 +1,9 @@ +namespace GitVersion.VersionCalculation +{ + using GitVersion.VersionCalculation.BaseVersionCalculators; + + public interface IBaseVersionCalculator + { + BaseVersion GetBaseVersion(GitVersionContext context); + } +} \ No newline at end of file diff --git a/GitVersionCore/VersionCalculation/IMetaDataCalculator.cs b/GitVersionCore/VersionCalculation/IMetaDataCalculator.cs new file mode 100644 index 0000000000..967baf8528 --- /dev/null +++ b/GitVersionCore/VersionCalculation/IMetaDataCalculator.cs @@ -0,0 +1,9 @@ +namespace GitVersion.VersionCalculation +{ + using LibGit2Sharp; + + public interface IMetaDataCalculator + { + SemanticVersionBuildMetaData Create(Commit baseVersionSource, GitVersionContext context); + } +} \ No newline at end of file diff --git a/GitVersionCore/VersionCalculation/MetaDataCalculator.cs b/GitVersionCore/VersionCalculation/MetaDataCalculator.cs new file mode 100644 index 0000000000..9afb84787f --- /dev/null +++ b/GitVersionCore/VersionCalculation/MetaDataCalculator.cs @@ -0,0 +1,26 @@ +namespace GitVersion.VersionCalculation +{ + using System.Linq; + using LibGit2Sharp; + + public class MetaDataCalculator : IMetaDataCalculator + { + public SemanticVersionBuildMetaData Create(Commit baseVersionSource, GitVersionContext context) + { + var qf = new CommitFilter + { + Since = context.CurrentCommit, + Until = baseVersionSource, + SortBy = CommitSortStrategies.Topological | CommitSortStrategies.Time + }; + + var commitsSinceTag = context.Repository.Commits.QueryBy(qf).Count(); + + return new SemanticVersionBuildMetaData( + commitsSinceTag, + context.CurrentBranch.Name, + context.CurrentCommit.Sha, + context.CurrentCommit.When()); + } + } +} \ No newline at end of file diff --git a/GitVersionCore/VersionCalculation/NewNextVersionCalculator.cs b/GitVersionCore/VersionCalculation/NewNextVersionCalculator.cs new file mode 100644 index 0000000000..91762f9d4a --- /dev/null +++ b/GitVersionCore/VersionCalculation/NewNextVersionCalculator.cs @@ -0,0 +1,74 @@ +namespace GitVersion.VersionCalculation +{ + using System; + using System.Text.RegularExpressions; + using BaseVersionCalculators; + + public class NewNextVersionCalculator + { + IBaseVersionCalculator baseVersionFinder; + IMetaDataCalculator metaDataCalculator; + + public NewNextVersionCalculator(IBaseVersionCalculator baseVersionCalculator = null, IMetaDataCalculator metaDataCalculator = null) + { + this.metaDataCalculator = metaDataCalculator ?? new MetaDataCalculator(); + baseVersionFinder = baseVersionCalculator ?? + new BaseVersionCalculator( + new FallbackBaseVersionStrategy(), + new ConfigNextVersionBaseVersionStrategy(), + new LastTagBaseVersionStrategy(), + new MergeMessageBaseVersionStrategy(), + new VersionInBranchBaseVersionStrategy()); + } + + public SemanticVersion FindVersion(GitVersionContext context) + { + var baseVersion = baseVersionFinder.GetBaseVersion(context); + + if (baseVersion.ShouldIncrement) IncrementVersion(context, baseVersion); + + if (baseVersion.ShouldUpdateTag && !baseVersion.SemanticVersion.PreReleaseTag.HasTag() && !string.IsNullOrEmpty(context.Configuration.Tag)) + { + var tagToUse = context.Configuration.Tag; + if (tagToUse == "useBranchName") + tagToUse = context.CurrentBranch.Name.RegexReplace(context.Configuration.BranchPrefixToTrim, string.Empty, RegexOptions.IgnoreCase); + baseVersion.SemanticVersion.PreReleaseTag = new SemanticVersionPreReleaseTag(tagToUse, 1); + } + + baseVersion.SemanticVersion.BuildMetaData = metaDataCalculator.Create(baseVersion.BaseVersionSource, context); + + return baseVersion.SemanticVersion; + } + + static void IncrementVersion(GitVersionContext context, BaseVersion baseVersion) + { + if (!baseVersion.SemanticVersion.PreReleaseTag.HasTag()) + { + switch (context.Configuration.Increment) + { + case IncrementStrategy.None: + break; + case IncrementStrategy.Major: + baseVersion.SemanticVersion.Major++; + break; + case IncrementStrategy.Minor: + baseVersion.SemanticVersion.Minor++; + break; + case IncrementStrategy.Patch: + baseVersion.SemanticVersion.Patch++; + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + else + { + if (baseVersion.SemanticVersion.PreReleaseTag.Number != null) + { + baseVersion.SemanticVersion.PreReleaseTag.Number = baseVersion.SemanticVersion.PreReleaseTag.Number; + baseVersion.SemanticVersion.PreReleaseTag.Number++; + } + } + } + } +} \ No newline at end of file diff --git a/GitVersionTask.Tests/GitFlow/GitFlowVersionFinderTests.cs b/GitVersionTask.Tests/GitFlow/GitFlowVersionFinderTests.cs index b935ee3d94..16d89a4547 100644 --- a/GitVersionTask.Tests/GitFlow/GitFlowVersionFinderTests.cs +++ b/GitVersionTask.Tests/GitFlow/GitFlowVersionFinderTests.cs @@ -39,27 +39,6 @@ public void RequiresALocalDevelopBranch() } } - [Test] - public void AFeatureBranchIsRequiredToBranchOffOfDevelopBranch() - { - var repoPath = Clone(ASBMTestRepoWorkingDirPath); - using (var repo = new Repository(repoPath)) - { - const string branchName = "feature/unborn"; - - // Create a new unborn feature branch sharing no history with "develop" - repo.Refs.UpdateTarget(repo.Refs.Head.CanonicalName, "refs/heads/" + branchName); - - AddOneCommitToHead(repo, "feature"); - - var feature = repo.Branches[branchName]; - - var finder = new GitVersionFinder(); - - Assert.Throws(() => finder.FindVersion(new GitVersionContext(repo, feature, new Config()))); - } - } - [Test] public void AHotfixBranchIsRequiredToBranchOffOfMasterBranch() { diff --git a/GitVersionTask.Tests/VersionOnMasterFinderTests.cs b/GitVersionTask.Tests/VersionOnMasterFinderTests.cs index 1b5570d470..80bc5e2c95 100644 --- a/GitVersionTask.Tests/VersionOnMasterFinderTests.cs +++ b/GitVersionTask.Tests/VersionOnMasterFinderTests.cs @@ -21,9 +21,9 @@ public void Should_find_previous_commit_that_was_at_least_a_minor_bump() var mockBranch = new MockBranch("master") { - new MockMergeCommit(new ObjectId(sha)) + new MockMergeCommit { - MessageEx = "Merge branch 'hotfix-0.3.0'", + MessageEx = "Merge branch 'hotfix-0.2.0'", CommitterEx = signature }, new MockMergeCommit @@ -31,9 +31,9 @@ public void Should_find_previous_commit_that_was_at_least_a_minor_bump() MessageEx = "Merge branch 'hotfix-0.3.1'", CommitterEx = signature, }, - new MockMergeCommit + new MockMergeCommit(new ObjectId(sha)) { - MessageEx = "Merge branch 'hotfix-0.2.0'", + MessageEx = "Merge branch 'hotfix-0.3.0'", CommitterEx = signature }, };