diff --git a/docs/input/docs/reference/configuration.md b/docs/input/docs/reference/configuration.md index f6ac35c566..eb6302ab7b 100644 --- a/docs/input/docs/reference/configuration.md +++ b/docs/input/docs/reference/configuration.md @@ -191,6 +191,7 @@ branches: is-main-branch: false ignore: sha: [] + paths: [] mode: ContinuousDelivery label: '{BranchName}' increment: Inherit @@ -208,7 +209,7 @@ tracks-release-branches: false is-release-branch: false is-main-branch: false ``` -snippet source | anchor +snippet source | anchor The supported built-in configuration for the `GitHubFlow` workflow (`workflow: GitHubFlow/v1`) looks like: @@ -315,6 +316,7 @@ branches: is-main-branch: false ignore: sha: [] + paths: [] mode: ContinuousDelivery label: '{BranchName}' increment: Inherit @@ -332,7 +334,7 @@ tracks-release-branches: false is-release-branch: false is-main-branch: false ``` -snippet source | anchor +snippet source | anchor The preview built-in configuration (experimental usage only) for the `TrunkBased` workflow (`workflow: TrunkBased/preview1`) looks like: @@ -424,6 +426,7 @@ branches: pre-release-weight: 30000 ignore: sha: [] + paths: [] mode: ContinuousDelivery label: '{BranchName}' increment: Inherit @@ -441,7 +444,7 @@ tracks-release-branches: false is-release-branch: false is-main-branch: false ``` -snippet source | anchor +snippet source | anchor The details of the available options are as follows: diff --git a/docs/input/docs/workflows/GitFlow/v1.yml b/docs/input/docs/workflows/GitFlow/v1.yml index 114ecf8e4f..dd44250ac5 100644 --- a/docs/input/docs/workflows/GitFlow/v1.yml +++ b/docs/input/docs/workflows/GitFlow/v1.yml @@ -148,6 +148,7 @@ branches: is-main-branch: false ignore: sha: [] + paths: [] mode: ContinuousDelivery label: '{BranchName}' increment: Inherit diff --git a/docs/input/docs/workflows/GitHubFlow/v1.yml b/docs/input/docs/workflows/GitHubFlow/v1.yml index bc0452231a..be7da3a729 100644 --- a/docs/input/docs/workflows/GitHubFlow/v1.yml +++ b/docs/input/docs/workflows/GitHubFlow/v1.yml @@ -97,6 +97,7 @@ branches: is-main-branch: false ignore: sha: [] + paths: [] mode: ContinuousDelivery label: '{BranchName}' increment: Inherit diff --git a/docs/input/docs/workflows/TrunkBased/preview1.yml b/docs/input/docs/workflows/TrunkBased/preview1.yml index 83d231527f..c261444d9f 100644 --- a/docs/input/docs/workflows/TrunkBased/preview1.yml +++ b/docs/input/docs/workflows/TrunkBased/preview1.yml @@ -82,6 +82,7 @@ branches: pre-release-weight: 30000 ignore: sha: [] + paths: [] mode: ContinuousDelivery label: '{BranchName}' increment: Inherit diff --git a/new-cli/GitVersion.Core.Libgit2Sharp/GitVersion.Core.Libgit2Sharp.csproj b/new-cli/GitVersion.Core.Libgit2Sharp/GitVersion.Core.Libgit2Sharp.csproj index 20c3f4bedf..4e8f95ddc5 100644 --- a/new-cli/GitVersion.Core.Libgit2Sharp/GitVersion.Core.Libgit2Sharp.csproj +++ b/new-cli/GitVersion.Core.Libgit2Sharp/GitVersion.Core.Libgit2Sharp.csproj @@ -56,6 +56,9 @@ Git\TagCollection.cs + + + Git\TreeChanges.cs diff --git a/src/GitVersion.Configuration.Tests/Configuration/ConfigurationProviderTests.CanWriteOutEffectiveConfiguration.approved.txt b/src/GitVersion.Configuration.Tests/Configuration/ConfigurationProviderTests.CanWriteOutEffectiveConfiguration.approved.txt index 114ecf8e4f..dd44250ac5 100644 --- a/src/GitVersion.Configuration.Tests/Configuration/ConfigurationProviderTests.CanWriteOutEffectiveConfiguration.approved.txt +++ b/src/GitVersion.Configuration.Tests/Configuration/ConfigurationProviderTests.CanWriteOutEffectiveConfiguration.approved.txt @@ -148,6 +148,7 @@ branches: is-main-branch: false ignore: sha: [] + paths: [] mode: ContinuousDelivery label: '{BranchName}' increment: Inherit diff --git a/src/GitVersion.Configuration.Tests/Workflows/approved/GitFlow/v1.yml b/src/GitVersion.Configuration.Tests/Workflows/approved/GitFlow/v1.yml index 114ecf8e4f..dd44250ac5 100644 --- a/src/GitVersion.Configuration.Tests/Workflows/approved/GitFlow/v1.yml +++ b/src/GitVersion.Configuration.Tests/Workflows/approved/GitFlow/v1.yml @@ -148,6 +148,7 @@ branches: is-main-branch: false ignore: sha: [] + paths: [] mode: ContinuousDelivery label: '{BranchName}' increment: Inherit diff --git a/src/GitVersion.Configuration.Tests/Workflows/approved/GitHubFlow/v1.yml b/src/GitVersion.Configuration.Tests/Workflows/approved/GitHubFlow/v1.yml index bc0452231a..be7da3a729 100644 --- a/src/GitVersion.Configuration.Tests/Workflows/approved/GitHubFlow/v1.yml +++ b/src/GitVersion.Configuration.Tests/Workflows/approved/GitHubFlow/v1.yml @@ -97,6 +97,7 @@ branches: is-main-branch: false ignore: sha: [] + paths: [] mode: ContinuousDelivery label: '{BranchName}' increment: Inherit diff --git a/src/GitVersion.Configuration.Tests/Workflows/approved/TrunkBased/preview1.yml b/src/GitVersion.Configuration.Tests/Workflows/approved/TrunkBased/preview1.yml index 83d231527f..c261444d9f 100644 --- a/src/GitVersion.Configuration.Tests/Workflows/approved/TrunkBased/preview1.yml +++ b/src/GitVersion.Configuration.Tests/Workflows/approved/TrunkBased/preview1.yml @@ -82,6 +82,7 @@ branches: pre-release-weight: 30000 ignore: sha: [] + paths: [] mode: ContinuousDelivery label: '{BranchName}' increment: Inherit diff --git a/src/GitVersion.Configuration/IgnoreConfiguration.cs b/src/GitVersion.Configuration/IgnoreConfiguration.cs index cb1e81e679..4ea9d6d86e 100644 --- a/src/GitVersion.Configuration/IgnoreConfiguration.cs +++ b/src/GitVersion.Configuration/IgnoreConfiguration.cs @@ -1,3 +1,4 @@ +using System.Collections.ObjectModel; using GitVersion.Configuration.Attributes; namespace GitVersion.Configuration; @@ -23,6 +24,12 @@ public string? BeforeString [JsonPropertyDescription("A sequence of SHAs to be excluded from the version calculations.")] public HashSet Shas { get; init; } = []; + IReadOnlyCollection IIgnoreConfiguration.Paths => Paths; + + [JsonPropertyName("paths")] + [JsonPropertyDescription("A sequence of file paths to be excluded from the version calculations.")] + public Collection Paths { get; init; } = []; + [JsonIgnore] - public bool IsEmpty => Before == null && Shas.Count == 0; + public bool IsEmpty => Before == null && Shas.Count == 0 && Paths.Count == 0; } diff --git a/src/GitVersion.Core.Tests/Extensions/GitRepositoryTestingExtensions.cs b/src/GitVersion.Core.Tests/Extensions/GitRepositoryTestingExtensions.cs index 1570e7bf58..1dd6e242c8 100644 --- a/src/GitVersion.Core.Tests/Extensions/GitRepositoryTestingExtensions.cs +++ b/src/GitVersion.Core.Tests/Extensions/GitRepositoryTestingExtensions.cs @@ -32,6 +32,13 @@ public static ICommit CreateMockCommit() return commit; } + public static ICommit CreateMockCommit(List diffPaths) + { + var commit = CreateMockCommit(); + commit.DiffPaths.Returns(diffPaths); + return commit; + } + public static IBranch CreateMockBranch(string name, params ICommit[] commits) { var branch = Substitute.For(); diff --git a/src/GitVersion.Core.Tests/IntegrationTests/IgnoreCommitScenarios.cs b/src/GitVersion.Core.Tests/IntegrationTests/IgnoreCommitScenarios.cs index e1f567e92f..a789867705 100644 --- a/src/GitVersion.Core.Tests/IntegrationTests/IgnoreCommitScenarios.cs +++ b/src/GitVersion.Core.Tests/IntegrationTests/IgnoreCommitScenarios.cs @@ -135,8 +135,9 @@ public void GivenTrunkBasedWorkflowWithIgnoreConfigurationBeforeCommitWithTagThe fixture.ApplyTag("1.0.0"); fixture.MakeACommit("D"); + var before = commitC.Committer.When.AddSeconds(1); var configuration = TrunkBasedConfigurationBuilder.New - .WithIgnoreConfiguration(new IgnoreConfiguration { Before = commitC.Committer.When }) + .WithIgnoreConfiguration(new IgnoreConfiguration { Before = before }) .Build(); // ✅ succeeds as expected @@ -285,8 +286,9 @@ public void GivenGitHubFlowWorkflowWithIgnoreConfigurationBeforeCommitWithTagThe fixture.ApplyTag("1.0.0"); fixture.MakeACommit("D"); + var before = commitC.Committer.When.AddSeconds(1); var configuration = GitHubFlowConfigurationBuilder.New - .WithIgnoreConfiguration(new IgnoreConfiguration { Before = commitC.Committer.When }) + .WithIgnoreConfiguration(new IgnoreConfiguration { Before = before }) .Build(); // ✅ succeeds as expected @@ -331,4 +333,104 @@ public void GivenGitHubFlowWorkflowWithCommitParameterBThenTagShouldBeConsidered // ✅ succeeds as expected fixture.AssertFullSemver(semanticVersion, configuration, commitId: commitA.Sha); } + + [Test] + public void GivenTrunkBasedWorkflowWithIgnoreConfigurationForPathThenVersionShouldBeCorrect() + { + using var fixture = new EmptyRepositoryFixture(); + + var commitA = fixture.Repository.MakeACommit("A"); + var commitB = fixture.Repository.MakeACommit("B"); + fixture.MakeACommit("C"); + fixture.MakeACommit("D"); + + var ignoredPath = fixture.Repository.Diff.Compare(commitA.Tree, commitB.Tree).Select(element => element.Path).First(); + + var configuration = TrunkBasedConfigurationBuilder.New + .WithIgnoreConfiguration(new IgnoreConfiguration { Paths = { ignoredPath } }) + .Build(); + + // commitB should be ignored, so version should be as if B didn't exist + fixture.AssertFullSemver("0.0.3", configuration); + } + + [Test] + public void GivenTrunkBasedWorkflowWithIgnoreConfigurationForPathAndCommitParameterCThenVersionShouldBeCorrect() + { + using var fixture = new EmptyRepositoryFixture(); + + var commitA = fixture.Repository.MakeACommit("A"); + fixture.MakeACommit("B"); + var commitC = fixture.Repository.MakeACommit("C"); + fixture.MakeACommit("D"); + + var ignoredPath = fixture.Repository.Diff.Compare(commitA.Tree, commitC.Tree).Select(element => element.Path).First(); + + var configuration = TrunkBasedConfigurationBuilder.New + .WithIgnoreConfiguration(new IgnoreConfiguration { Paths = { ignoredPath } }) + .Build(); + + // commitC should be ignored, so version should be as if C didn't exist + fixture.AssertFullSemver("0.0.2", configuration, commitId: commitC.Sha); + } + + [Test] + public void GivenGitHubFlowWorkflowWithIgnoreConfigurationForPathThenVersionShouldBeCorrect() + { + using var fixture = new EmptyRepositoryFixture(); + + var commitA = fixture.Repository.MakeACommit("A"); + var commitB = fixture.Repository.MakeACommit("B"); + fixture.MakeACommit("C"); + fixture.MakeACommit("D"); + + var ignoredPath = fixture.Repository.Diff.Compare(commitA.Tree, commitB.Tree).Select(element => element.Path).First(); + + var configuration = GitHubFlowConfigurationBuilder.New + .WithIgnoreConfiguration(new IgnoreConfiguration { Paths = { ignoredPath } }) + .Build(); + + // commitB should be ignored, so version should be as if B didn't exist + fixture.AssertFullSemver("0.0.1-3", configuration); + } + + [Test] + public void GivenTrunkBasedWorkflowWithIgnoreConfigurationForTaggedCommitPathThenTagShouldBeIgnored() + { + using var fixture = new EmptyRepositoryFixture(); + + var commitA = fixture.Repository.MakeACommit("A"); + var commitB = fixture.Repository.MakeACommit("B"); + fixture.ApplyTag("1.0.0"); + fixture.MakeACommit("C"); + + var ignoredPath = fixture.Repository.Diff.Compare(commitA.Tree, commitB.Tree).Select(element => element.Path).First(); + + var configuration = TrunkBasedConfigurationBuilder.New + .WithIgnoreConfiguration(new IgnoreConfiguration { Paths = { ignoredPath } }) + .Build(); + + // commitB should be ignored, so version should be as if B didn't exist + fixture.AssertFullSemver("0.0.2", configuration); + } + + [Test] + public void GivenGitHubFlowWorkflowWithIgnoreConfigurationForTaggedCommitPathThenTagShouldBeIgnored() + { + using var fixture = new EmptyRepositoryFixture(); + + var commitA = fixture.Repository.MakeACommit("A"); + var commitB = fixture.Repository.MakeACommit("B"); + fixture.ApplyTag("1.0.0"); + fixture.MakeACommit("C"); + + var ignoredPath = fixture.Repository.Diff.Compare(commitA.Tree, commitB.Tree).Select(element => element.Path).First(); + + var configuration = GitHubFlowConfigurationBuilder.New + .WithIgnoreConfiguration(new IgnoreConfiguration { Paths = { ignoredPath } }) + .Build(); + + // commitB should be ignored, so version should be as if B didn't exist + fixture.AssertFullSemver("0.0.1-2", configuration); + } } diff --git a/src/GitVersion.Core.Tests/VersionCalculation/MinDateVersionFilterTests.cs b/src/GitVersion.Core.Tests/VersionCalculation/MinDateVersionFilterTests.cs index 86f41c787c..909c77e094 100644 --- a/src/GitVersion.Core.Tests/VersionCalculation/MinDateVersionFilterTests.cs +++ b/src/GitVersion.Core.Tests/VersionCalculation/MinDateVersionFilterTests.cs @@ -12,7 +12,7 @@ public void VerifyNullGuard() var dummy = DateTimeOffset.UtcNow.AddSeconds(1.0); var sut = new MinDateVersionFilter(dummy); - Should.Throw(() => sut.Exclude(null!, out _)); + Should.Throw(() => sut.Exclude((IBaseVersion)null!, out _)); } [Test] diff --git a/src/GitVersion.Core.Tests/VersionCalculation/PathFilterTests.cs b/src/GitVersion.Core.Tests/VersionCalculation/PathFilterTests.cs new file mode 100644 index 0000000000..efffb84e18 --- /dev/null +++ b/src/GitVersion.Core.Tests/VersionCalculation/PathFilterTests.cs @@ -0,0 +1,48 @@ +using GitVersion.Core.Tests.Helpers; +using GitVersion.VersionCalculation; + +namespace GitVersion.Core.Tests; + +[TestFixture] +public class PathFilterTests : TestBase +{ + [Test] + public void VerifyNullGuard() + { + var sut = new PathFilter([]); + + Should.Throw(() => sut.Exclude((IBaseVersion)null!, out _)); + } + + [Test] + public void WhenPathMatchShouldExcludeWithReason() + { + var commit = GitRepositoryTestingExtensions.CreateMockCommit(["/path"]); + BaseVersion version = new("dummy", new SemanticVersion(1), commit); + var sut = new PathFilter(commit.DiffPaths); + + sut.Exclude(version, out var reason).ShouldBeTrue(); + reason.ShouldNotBeNullOrWhiteSpace(); + } + + [Test] + public void WhenPathMismatchShouldNotExclude() + { + var commit = GitRepositoryTestingExtensions.CreateMockCommit(["/path"]); + BaseVersion version = new("dummy", new SemanticVersion(1), commit); + var sut = new PathFilter(["/another_path"]); + + sut.Exclude(version, out var reason).ShouldBeFalse(); + reason.ShouldBeNull(); + } + + [Test] + public void ExcludeShouldAcceptVersionWithNullCommit() + { + BaseVersion version = new("dummy", new SemanticVersion(1)); + var sut = new PathFilter(["/path"]); + + sut.Exclude(version, out var reason).ShouldBeFalse(); + reason.ShouldBeNull(); + } +} diff --git a/src/GitVersion.Core.Tests/VersionCalculation/ShaVersionFilterTests.cs b/src/GitVersion.Core.Tests/VersionCalculation/ShaVersionFilterTests.cs index 69b95b9f20..e4d4b219f1 100644 --- a/src/GitVersion.Core.Tests/VersionCalculation/ShaVersionFilterTests.cs +++ b/src/GitVersion.Core.Tests/VersionCalculation/ShaVersionFilterTests.cs @@ -12,7 +12,7 @@ public void VerifyNullGuard() var commit = GitRepositoryTestingExtensions.CreateMockCommit(); var sut = new ShaVersionFilter([commit.Sha]); - Should.Throw(() => sut.Exclude(null!, out _)); + Should.Throw(() => sut.Exclude((IBaseVersion)null!, out _)); } [Test] diff --git a/src/GitVersion.Core.Tests/VersionCalculation/Strategies/MergeMessageBaseVersionStrategyTests.cs b/src/GitVersion.Core.Tests/VersionCalculation/Strategies/MergeMessageBaseVersionStrategyTests.cs index eb80a02e3a..1f03f3a9ea 100644 --- a/src/GitVersion.Core.Tests/VersionCalculation/Strategies/MergeMessageBaseVersionStrategyTests.cs +++ b/src/GitVersion.Core.Tests/VersionCalculation/Strategies/MergeMessageBaseVersionStrategyTests.cs @@ -204,6 +204,7 @@ private class MockCommit : ICommit public IObjectId Id => throw new NotImplementedException(); public string Sha => throw new NotImplementedException(); public IReadOnlyList Parents => throw new NotImplementedException(); + public IReadOnlyList DiffPaths => throw new NotImplementedException(); public DateTimeOffset When => throw new NotImplementedException(); public string Message => throw new NotImplementedException(); } diff --git a/src/GitVersion.Core/Configuration/EffectiveConfiguration.cs b/src/GitVersion.Core/Configuration/EffectiveConfiguration.cs index bd16f1f2ba..29f7280f74 100644 --- a/src/GitVersion.Core/Configuration/EffectiveConfiguration.cs +++ b/src/GitVersion.Core/Configuration/EffectiveConfiguration.cs @@ -67,7 +67,6 @@ public EffectiveConfiguration( PatchVersionBumpMessage = configuration.PatchVersionBumpMessage; NoBumpMessage = configuration.NoBumpMessage; CommitMessageIncrementing = branchConfiguration.CommitMessageIncrementing.Value; - VersionFilters = configuration.Ignore.ToFilters(); Ignore = configuration.Ignore; TracksReleaseBranches = branchConfiguration.TracksReleaseBranches ?? false; IsReleaseBranch = branchConfiguration.IsReleaseBranch ?? false; @@ -122,8 +121,6 @@ public EffectiveConfiguration( public CommitMessageIncrementMode CommitMessageIncrementing { get; } - public IEnumerable VersionFilters { get; } - public IIgnoreConfiguration Ignore { get; } public string? CommitDateFormat { get; } diff --git a/src/GitVersion.Core/Configuration/IIgnoreConfiguration.cs b/src/GitVersion.Core/Configuration/IIgnoreConfiguration.cs index 482b6a6c5f..ea19f6d746 100644 --- a/src/GitVersion.Core/Configuration/IIgnoreConfiguration.cs +++ b/src/GitVersion.Core/Configuration/IIgnoreConfiguration.cs @@ -6,5 +6,7 @@ public interface IIgnoreConfiguration IReadOnlySet Shas { get; } + IReadOnlyCollection Paths { get; } + bool IsEmpty { get; } } diff --git a/src/GitVersion.Core/Configuration/IgnoreConfigurationExtensions.cs b/src/GitVersion.Core/Configuration/IgnoreConfigurationExtensions.cs index eeaf3a0ded..d4c7b19c07 100644 --- a/src/GitVersion.Core/Configuration/IgnoreConfigurationExtensions.cs +++ b/src/GitVersion.Core/Configuration/IgnoreConfigurationExtensions.cs @@ -22,5 +22,5 @@ public static IEnumerable Filter(this IIgnoreConfiguration ignore, ICom } private static bool ShouldBeIgnored(ICommit commit, IIgnoreConfiguration ignore) - => !(commit.When <= ignore.Before) && !ignore.Shas.Contains(commit.Sha); + => !ignore.ToFilters().Any(filter => filter.Exclude(commit, out var _)); } diff --git a/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs b/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs index 9d87f32726..6f794c91d1 100644 --- a/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs +++ b/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs @@ -44,6 +44,7 @@ public static IEnumerable ToFilters(this IIgnoreConfiguration so if (source.Shas.Count != 0) yield return new ShaVersionFilter(source.Shas); if (source.Before.HasValue) yield return new MinDateVersionFilter(source.Before.Value); + if (source.Paths.Count != 0) yield return new PathFilter(source.Paths.ToList()); } private static IEnumerable GetBranchConfigurations(IGitVersionConfiguration configuration, string branchName) diff --git a/src/GitVersion.Core/Git/ICommit.cs b/src/GitVersion.Core/Git/ICommit.cs index 6d54ecb78d..8d3b225655 100644 --- a/src/GitVersion.Core/Git/ICommit.cs +++ b/src/GitVersion.Core/Git/ICommit.cs @@ -7,4 +7,6 @@ public interface ICommit : IEquatable, IComparable, IGitObjec DateTimeOffset When { get; } string Message { get; } + + IReadOnlyList DiffPaths { get; } } diff --git a/src/GitVersion.Core/Git/ITreeChanges.cs b/src/GitVersion.Core/Git/ITreeChanges.cs new file mode 100644 index 0000000000..28a4600d68 --- /dev/null +++ b/src/GitVersion.Core/Git/ITreeChanges.cs @@ -0,0 +1,6 @@ +namespace GitVersion.Git; + +public interface ITreeChanges +{ + IReadOnlyList Paths { get; } +} diff --git a/src/GitVersion.Core/PublicAPI.Shipped.txt b/src/GitVersion.Core/PublicAPI.Shipped.txt index 27793f8f08..89e76daf51 100644 --- a/src/GitVersion.Core/PublicAPI.Shipped.txt +++ b/src/GitVersion.Core/PublicAPI.Shipped.txt @@ -87,7 +87,6 @@ GitVersion.Configuration.EffectiveConfiguration.TrackMergeMessage.get -> bool GitVersion.Configuration.EffectiveConfiguration.TrackMergeTarget.get -> bool GitVersion.Configuration.EffectiveConfiguration.TracksReleaseBranches.get -> bool GitVersion.Configuration.EffectiveConfiguration.UpdateBuildNumber.get -> bool -GitVersion.Configuration.EffectiveConfiguration.VersionFilters.get -> System.Collections.Generic.IEnumerable! GitVersion.Configuration.EffectiveConfiguration.VersionInBranchPattern.get -> string? GitVersion.Configuration.EffectiveConfiguration.VersionStrategy.get -> GitVersion.VersionCalculation.VersionStrategies GitVersion.Configuration.IBranchConfiguration diff --git a/src/GitVersion.Core/PublicAPI.Unshipped.txt b/src/GitVersion.Core/PublicAPI.Unshipped.txt index 0bcbcd8281..505df687a4 100644 --- a/src/GitVersion.Core/PublicAPI.Unshipped.txt +++ b/src/GitVersion.Core/PublicAPI.Unshipped.txt @@ -146,3 +146,8 @@ virtual GitVersion.WixInfo.$() -> GitVersion.WixInfo! virtual GitVersion.WixInfo.EqualityContract.get -> System.Type! virtual GitVersion.WixInfo.Equals(GitVersion.WixInfo? other) -> bool virtual GitVersion.WixInfo.PrintMembers(System.Text.StringBuilder! builder) -> bool +GitVersion.Configuration.IIgnoreConfiguration.Paths.get -> System.Collections.Generic.IReadOnlyCollection! +GitVersion.Git.ICommit.DiffPaths.get -> System.Collections.Generic.IReadOnlyList! +GitVersion.Git.ITreeChanges +GitVersion.Git.ITreeChanges.Paths.get -> System.Collections.Generic.IReadOnlyList! +GitVersion.VersionCalculation.IVersionFilter.Exclude(GitVersion.Git.ICommit! commit, out string? reason) -> bool diff --git a/src/GitVersion.Core/VersionCalculation/Abstractions/IVersionFilter.cs b/src/GitVersion.Core/VersionCalculation/Abstractions/IVersionFilter.cs index f088734713..65a8d2a7c4 100644 --- a/src/GitVersion.Core/VersionCalculation/Abstractions/IVersionFilter.cs +++ b/src/GitVersion.Core/VersionCalculation/Abstractions/IVersionFilter.cs @@ -1,6 +1,9 @@ +using GitVersion.Git; + namespace GitVersion.VersionCalculation; public interface IVersionFilter { bool Exclude(IBaseVersion baseVersion, out string? reason); + bool Exclude(ICommit commit, out string? reason); } diff --git a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs index 98c51fe60c..d74f71d4a3 100644 --- a/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs +++ b/src/GitVersion.Core/VersionCalculation/IncrementStrategyFinder.cs @@ -7,7 +7,9 @@ namespace GitVersion.VersionCalculation; -internal class IncrementStrategyFinder(IRepositoryStore repositoryStore, ITaggedSemanticVersionRepository taggedSemanticVersionRepository) +internal class IncrementStrategyFinder( + IRepositoryStore repositoryStore, + ITaggedSemanticVersionRepository taggedSemanticVersionRepository) : IIncrementStrategyFinder { private readonly Dictionary commitIncrementCache = []; diff --git a/src/GitVersion.Core/VersionCalculation/MinDateVersionFilter.cs b/src/GitVersion.Core/VersionCalculation/MinDateVersionFilter.cs index b2fce8b7e6..294c5defb9 100644 --- a/src/GitVersion.Core/VersionCalculation/MinDateVersionFilter.cs +++ b/src/GitVersion.Core/VersionCalculation/MinDateVersionFilter.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using GitVersion.Extensions; +using GitVersion.Git; namespace GitVersion.VersionCalculation; @@ -17,4 +18,15 @@ public bool Exclude(IBaseVersion baseVersion, [NotNullWhen(true)] out string? re reason = "Source was ignored due to commit date being outside of configured range"; return true; } + + public bool Exclude(ICommit commit, [NotNullWhen(true)] out string? reason) + { + reason = null; + + if (commit == null || commit.When >= minimum) + return false; + + reason = "Source was ignored due to commit date being outside of configured range"; + return true; + } } diff --git a/src/GitVersion.Core/VersionCalculation/PathFilter.cs b/src/GitVersion.Core/VersionCalculation/PathFilter.cs new file mode 100644 index 0000000000..cb801b6351 --- /dev/null +++ b/src/GitVersion.Core/VersionCalculation/PathFilter.cs @@ -0,0 +1,57 @@ +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using System.Text.RegularExpressions; +using GitVersion.Git; + +namespace GitVersion.VersionCalculation; + +internal enum PathFilterMode +{ + Inclusive, // All commit paths must match for commit to be excluded + //Exclusive // Any commit path must match for commit to be excluded +} + +internal class PathFilter(IReadOnlyList paths, PathFilterMode mode = PathFilterMode.Inclusive) : IVersionFilter +{ + private readonly IReadOnlyList pathsRegexes = [.. paths.Select(path => new Regex(path, RegexOptions.Compiled))]; + private readonly ConcurrentDictionary pathMatchCache = []; + + public bool Exclude(IBaseVersion baseVersion, [NotNullWhen(true)] out string? reason) + { + ArgumentNullException.ThrowIfNull(baseVersion); + return Exclude(baseVersion.BaseVersionSource, out reason); + } + + private bool IsMatch(string path) + { + if (!pathMatchCache.TryGetValue(path, out var isMatch)) + { + isMatch = this.pathsRegexes.Any(regex => regex.IsMatch(path)); + pathMatchCache[path] = isMatch; + } + return isMatch; + } + + public bool Exclude(ICommit? commit, [NotNullWhen(true)] out string? reason) + { + reason = null; + + if (commit != null) + { + switch (mode) + { + case PathFilterMode.Inclusive: + { + if (commit.DiffPaths.All(this.IsMatch)) + { + reason = "Source was ignored due to all commit paths matching ignore regex"; + return true; + } + break; + } + } + } + + return false; + } +} diff --git a/src/GitVersion.Core/VersionCalculation/ShaVersionFilter.cs b/src/GitVersion.Core/VersionCalculation/ShaVersionFilter.cs index 87d4834d15..1f531342e1 100644 --- a/src/GitVersion.Core/VersionCalculation/ShaVersionFilter.cs +++ b/src/GitVersion.Core/VersionCalculation/ShaVersionFilter.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using GitVersion.Extensions; +using GitVersion.Git; namespace GitVersion.VersionCalculation; @@ -22,4 +23,16 @@ public bool Exclude(IBaseVersion baseVersion, [NotNullWhen(true)] out string? re reason = $"Sha {baseVersion.BaseVersionSource} was ignored due to commit having been excluded by configuration"; return true; } + + public bool Exclude(ICommit commit, [NotNullWhen(true)] out string? reason) + { + reason = null; + + if (commit == null + || !this.shaList.Any(sha => commit.Sha.StartsWith(sha, StringComparison.OrdinalIgnoreCase))) + return false; + + reason = $"Sha {commit} was ignored due to commit having been excluded by configuration"; + return true; + } } diff --git a/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/ConfiguredNextVersionVersionStrategy.cs b/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/ConfiguredNextVersionVersionStrategy.cs index 275aee185a..129f655733 100644 --- a/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/ConfiguredNextVersionVersionStrategy.cs +++ b/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/ConfiguredNextVersionVersionStrategy.cs @@ -18,7 +18,7 @@ public IEnumerable GetBaseVersions(EffectiveBranchConfiguration con { configuration.NotNull(); - if (!Context.Configuration.VersionStrategy.HasFlag(VersionStrategies.ConfiguredNextVersion)) + if (!this.Context.Configuration.VersionStrategy.HasFlag(VersionStrategies.ConfiguredNextVersion)) yield break; var nextVersion = Context.Configuration.NextVersion; diff --git a/src/GitVersion.LibGit2Sharp/Git/Branch.cs b/src/GitVersion.LibGit2Sharp/Git/Branch.cs index 2cbda65c0e..088f4674c7 100644 --- a/src/GitVersion.LibGit2Sharp/Git/Branch.cs +++ b/src/GitVersion.LibGit2Sharp/Git/Branch.cs @@ -10,16 +10,16 @@ internal sealed class Branch : IBranch private readonly LibGit2Sharp.Branch innerBranch; - internal Branch(LibGit2Sharp.Branch branch) + internal Branch(LibGit2Sharp.Branch branch, LibGit2Sharp.Diff diff) { this.innerBranch = branch.NotNull(); Name = new(branch.CanonicalName); var commit = this.innerBranch.Tip; - Tip = commit is null ? null : new Commit(commit); + Tip = commit is null ? null : new Commit(commit, diff); var commits = this.innerBranch.Commits; - Commits = new CommitCollection(commits); + Commits = new CommitCollection(commits, diff); } public ReferenceName Name { get; } diff --git a/src/GitVersion.LibGit2Sharp/Git/BranchCollection.cs b/src/GitVersion.LibGit2Sharp/Git/BranchCollection.cs index a6509d9116..f5edcd38cd 100644 --- a/src/GitVersion.LibGit2Sharp/Git/BranchCollection.cs +++ b/src/GitVersion.LibGit2Sharp/Git/BranchCollection.cs @@ -7,11 +7,13 @@ internal sealed class BranchCollection : IBranchCollection { private readonly LibGit2Sharp.BranchCollection innerCollection; private readonly Lazy> branches; + private readonly LibGit2Sharp.Diff diff; - internal BranchCollection(LibGit2Sharp.BranchCollection collection) + internal BranchCollection(LibGit2Sharp.BranchCollection collection, LibGit2Sharp.Diff diff) { this.innerCollection = collection.NotNull(); - this.branches = new Lazy>(() => [.. this.innerCollection.Select(branch => new Branch(branch))]); +this.branches = new Lazy>(() => [.. this.innerCollection.Select(branch => new Branch(branch, diff))]); + this.diff = diff.NotNull(); } public IEnumerator GetEnumerator() @@ -25,7 +27,7 @@ public IBranch? this[string name] { name = name.NotNull(); var branch = this.innerCollection[name]; - return branch is null ? null : new Branch(branch); + return branch is null ? null : new Branch(branch, this.diff); } } diff --git a/src/GitVersion.LibGit2Sharp/Git/Commit.cs b/src/GitVersion.LibGit2Sharp/Git/Commit.cs index f9dd9cfc06..95f30e81cc 100644 --- a/src/GitVersion.LibGit2Sharp/Git/Commit.cs +++ b/src/GitVersion.LibGit2Sharp/Git/Commit.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using GitVersion.Extensions; using GitVersion.Helpers; @@ -5,17 +6,20 @@ namespace GitVersion.Git; internal sealed class Commit : GitObject, ICommit { + private static readonly ConcurrentDictionary> pathsCache = new(); private static readonly LambdaEqualityHelper equalityHelper = new(x => x.Id); private static readonly LambdaKeyComparer comparerHelper = new(x => x.Sha); private readonly Lazy> parentsLazy; private readonly LibGit2Sharp.Commit innerCommit; + private readonly LibGit2Sharp.Diff repoDiff; - internal Commit(LibGit2Sharp.Commit innerCommit) : base(innerCommit) + internal Commit(LibGit2Sharp.Commit innerCommit, LibGit2Sharp.Diff repoDiff) : base(innerCommit) { this.innerCommit = innerCommit.NotNull(); - this.parentsLazy = new(() => innerCommit.Parents.Select(parent => new Commit(parent)).ToList()); + this.parentsLazy = new(() => innerCommit.Parents.Select(parent => new Commit(parent, repoDiff)).ToList()); When = innerCommit.Committer.When; + this.repoDiff = repoDiff; } public int CompareTo(ICommit? other) => comparerHelper.Compare(this, other); @@ -23,8 +27,21 @@ internal Commit(LibGit2Sharp.Commit innerCommit) : base(innerCommit) public IReadOnlyList Parents => this.parentsLazy.Value; public DateTimeOffset When { get; } public string Message => this.innerCommit.Message; + public IReadOnlyList DiffPaths + { + get + { + if (!pathsCache.TryGetValue(this.Sha, out var paths)) + { + paths = this.CommitChanges?.Paths ?? []; + pathsCache[this.Sha] = paths; + } + return paths; + } + } public override bool Equals(object? obj) => Equals(obj as ICommit); public override int GetHashCode() => equalityHelper.GetHashCode(this); public override string ToString() => $"'{Id.ToString(7)}' - {this.innerCommit.MessageShort}"; public static implicit operator LibGit2Sharp.Commit(Commit d) => d.innerCommit; + private TreeChanges CommitChanges => new(this.repoDiff.Compare(this.innerCommit.Tree, this.innerCommit.Parents.FirstOrDefault()?.Tree)); } diff --git a/src/GitVersion.LibGit2Sharp/Git/CommitCollection.cs b/src/GitVersion.LibGit2Sharp/Git/CommitCollection.cs index c2e33588a4..570c1dd426 100644 --- a/src/GitVersion.LibGit2Sharp/Git/CommitCollection.cs +++ b/src/GitVersion.LibGit2Sharp/Git/CommitCollection.cs @@ -7,11 +7,13 @@ internal sealed class CommitCollection : ICommitCollection { private readonly ICommitLog innerCollection; private readonly Lazy> commits; + private readonly LibGit2Sharp.Diff diff; - internal CommitCollection(ICommitLog collection) + internal CommitCollection(ICommitLog collection, LibGit2Sharp.Diff diff) { this.innerCollection = collection.NotNull(); - this.commits = new Lazy>(() => [.. this.innerCollection.Select(commit => new Commit(commit))]); + this.commits = new Lazy>(() => [.. this.innerCollection.Select(commit => new Commit(commit, diff))]); + this.diff = diff.NotNull(); } public IEnumerator GetEnumerator() @@ -34,7 +36,7 @@ public IEnumerable QueryBy(CommitFilter commitFilter) SortBy = (LibGit2Sharp.CommitSortStrategies)commitFilter.SortBy }; var commitLog = ((IQueryableCommitLog)this.innerCollection).QueryBy(filter); - return new CommitCollection(commitLog); + return new CommitCollection(commitLog, this.diff); static object? GetReacheableFrom(object? item) => item switch diff --git a/src/GitVersion.LibGit2Sharp/Git/GitRepository.cs b/src/GitVersion.LibGit2Sharp/Git/GitRepository.cs index aea0217adc..3d2fd756ed 100644 --- a/src/GitVersion.LibGit2Sharp/Git/GitRepository.cs +++ b/src/GitVersion.LibGit2Sharp/Git/GitRepository.cs @@ -16,17 +16,16 @@ private IRepository RepositoryInstance return lazy.Value; } } - public string Path => RepositoryInstance.Info.Path; public string WorkingDirectory => RepositoryInstance.Info.WorkingDirectory; public bool IsHeadDetached => RepositoryInstance.Info.IsHeadDetached; public bool IsShallow => RepositoryInstance.Info.IsShallow; - public IBranch Head => new Branch(RepositoryInstance.Head); + public IBranch Head => new Branch(RepositoryInstance.Head, RepositoryInstance.Diff); - public ITagCollection Tags => new TagCollection(RepositoryInstance.Tags); + public ITagCollection Tags => new TagCollection(RepositoryInstance.Tags, RepositoryInstance.Diff); public IReferenceCollection Refs => new ReferenceCollection(RepositoryInstance.Refs); - public IBranchCollection Branches => new BranchCollection(RepositoryInstance.Branches); - public ICommitCollection Commits => new CommitCollection(RepositoryInstance.Commits); + public IBranchCollection Branches => new BranchCollection(RepositoryInstance.Branches, RepositoryInstance.Diff); + public ICommitCollection Commits => new CommitCollection(RepositoryInstance.Commits, RepositoryInstance.Diff); public IRemoteCollection Remotes => new RemoteCollection(RepositoryInstance.Network.Remotes); public void DiscoverRepository(string? gitDirectory) @@ -49,7 +48,7 @@ public void DiscoverRepository(string? gitDirectory) var first = (Commit)commit; var second = (Commit)otherCommit; var mergeBase = RepositoryInstance.ObjectDatabase.FindMergeBase(first, second); - return mergeBase == null ? null : new Commit(mergeBase); + return mergeBase == null ? null : new Commit(mergeBase, RepositoryInstance.Diff); }); } @@ -88,7 +87,7 @@ private int GetUncommittedChangesCountInternal() } // gets all changes of the last commit vs Staging area and WT - var changes = RepositoryInstance.Diff.Compare(RepositoryInstance.Head.Tip.Tree, + var changes = RepositoryInstance.Diff.Compare(RepositoryInstance.Head.Tip.Tree, DiffTargets.Index | DiffTargets.WorkingDirectory); return changes.Count; diff --git a/src/GitVersion.LibGit2Sharp/Git/Tag.cs b/src/GitVersion.LibGit2Sharp/Git/Tag.cs index b7af785b56..33302dba3d 100644 --- a/src/GitVersion.LibGit2Sharp/Git/Tag.cs +++ b/src/GitVersion.LibGit2Sharp/Git/Tag.cs @@ -9,12 +9,14 @@ internal sealed class Tag : ITag private static readonly LambdaEqualityHelper equalityHelper = new(x => x.Name.Canonical); private static readonly LambdaKeyComparer comparerHelper = new(x => x.Name.Canonical); private readonly LibGit2Sharp.Tag innerTag; + private readonly LibGit2Sharp.Diff diff; private readonly Lazy commitLazy; - internal Tag(LibGit2Sharp.Tag tag) + internal Tag(LibGit2Sharp.Tag tag, LibGit2Sharp.Diff diff) { this.innerTag = tag.NotNull(); this.commitLazy = new(PeeledTargetCommit); + this.diff = diff.NotNull(); Name = new(this.innerTag.CanonicalName); } @@ -33,7 +35,7 @@ internal Tag(LibGit2Sharp.Tag tag) target = annotation.Target; } - return target is LibGit2Sharp.Commit commit ? new Commit(commit) : null; + return target is LibGit2Sharp.Commit commit ? new Commit(commit, this.diff) : null; } public override bool Equals(object? obj) => Equals(obj as ITag); diff --git a/src/GitVersion.LibGit2Sharp/Git/TagCollection.cs b/src/GitVersion.LibGit2Sharp/Git/TagCollection.cs index bf88136398..bac1b4f790 100644 --- a/src/GitVersion.LibGit2Sharp/Git/TagCollection.cs +++ b/src/GitVersion.LibGit2Sharp/Git/TagCollection.cs @@ -6,10 +6,10 @@ internal sealed class TagCollection : ITagCollection { private readonly Lazy> tags; - internal TagCollection(LibGit2Sharp.TagCollection collection) + internal TagCollection(LibGit2Sharp.TagCollection collection, LibGit2Sharp.Diff diff) { collection = collection.NotNull(); - this.tags = new Lazy>(() => [.. collection.Select(tag => new Tag(tag))]); + this.tags = new Lazy>(() => [.. collection.Select(tag => new Tag(tag, diff))]); } public IEnumerator GetEnumerator() diff --git a/src/GitVersion.LibGit2Sharp/Git/TreeChanges.cs b/src/GitVersion.LibGit2Sharp/Git/TreeChanges.cs new file mode 100644 index 0000000000..9ee9385a7e --- /dev/null +++ b/src/GitVersion.LibGit2Sharp/Git/TreeChanges.cs @@ -0,0 +1,8 @@ +namespace GitVersion.Git; + +internal sealed class TreeChanges(LibGit2Sharp.TreeChanges innerTreeChanges) : ITreeChanges +{ + private readonly LibGit2Sharp.TreeChanges innerTreeChanges = innerTreeChanges ?? throw new ArgumentNullException(nameof(innerTreeChanges)); + + public IReadOnlyList Paths => [.. this.innerTreeChanges.Select(element => element.Path)]; +}