From 789226ff04e3b49d9c26bdaea1ad44fb06b9060d Mon Sep 17 00:00:00 2001 From: roeil Date: Wed, 21 May 2025 08:16:46 +0300 Subject: [PATCH 1/7] feat: introduce paths-filter based on commit filtering --- docs/input/docs/workflows/GitFlow/v1.yml | 1 + docs/input/docs/workflows/GitHubFlow/v1.yml | 1 + .../docs/workflows/TrunkBased/preview1.yml | 1 + ...riteOutEffectiveConfiguration.approved.txt | 1 + .../Workflows/approved/GitFlow/v1.yml | 1 + .../Workflows/approved/GitHubFlow/v1.yml | 1 + .../approved/TrunkBased/preview1.yml | 1 + .../IgnoreConfiguration.cs | 9 +++- .../MinDateVersionFilterTests.cs | 2 +- .../VersionCalculation/PathFilterTests.cs | 11 ++++ .../ShaVersionFilterTests.cs | 2 +- .../MergeMessageBaseVersionStrategyTests.cs | 1 + .../Configuration/EffectiveConfiguration.cs | 3 -- .../Configuration/IIgnoreConfiguration.cs | 4 +- .../IgnoreConfigurationExtensions.cs | 2 +- .../Extensions/ConfigurationExtensions.cs | 3 +- src/GitVersion.Core/Git/ICommit.cs | 1 + src/GitVersion.Core/Git/ITreeChanges.cs | 6 +++ src/GitVersion.Core/PublicAPI.Shipped.txt | 1 - src/GitVersion.Core/PublicAPI.Unshipped.txt | 5 ++ .../Abstractions/IVersionFilter.cs | 3 ++ .../IncrementStrategyFinder.cs | 4 +- .../MinDateVersionFilter.cs | 12 +++++ .../VersionCalculation/PathFilter.cs | 51 +++++++++++++++++++ .../VersionCalculation/ShaVersionFilter.cs | 13 +++++ .../ConfiguredNextVersionVersionStrategy.cs | 2 +- src/GitVersion.LibGit2Sharp/Git/Branch.cs | 6 +-- .../Git/BranchCollection.cs | 8 +-- src/GitVersion.LibGit2Sharp/Git/Commit.cs | 20 +++++++- .../Git/CommitCollection.cs | 8 +-- .../Git/GitRepository.cs | 15 +++--- src/GitVersion.LibGit2Sharp/Git/Tag.cs | 6 ++- .../Git/TagCollection.cs | 4 +- .../Git/TreeChanges.cs | 8 +++ 34 files changed, 183 insertions(+), 34 deletions(-) create mode 100644 src/GitVersion.Core.Tests/VersionCalculation/PathFilterTests.cs create mode 100644 src/GitVersion.Core/Git/ITreeChanges.cs create mode 100644 src/GitVersion.Core/VersionCalculation/PathFilter.cs create mode 100644 src/GitVersion.LibGit2Sharp/Git/TreeChanges.cs 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/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..636571e2a8 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; @@ -24,5 +25,11 @@ public string? BeforeString public HashSet Shas { get; init; } = []; [JsonIgnore] - public bool IsEmpty => Before == null && Shas.Count == 0; + public bool IsEmpty => Before == null && Shas.Count == 0 && Paths.Count == 0; + + IReadOnlyCollection IIgnoreConfiguration.Paths => Paths; + + [JsonPropertyName("paths")] + [JsonPropertyDescription("A sequence of file paths to be excluded from the version calculations.")] + public Collection Paths { get; init; } = []; } 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..8c8286458c --- /dev/null +++ b/src/GitVersion.Core.Tests/VersionCalculation/PathFilterTests.cs @@ -0,0 +1,11 @@ +using GitVersion.Core.Tests.Helpers; +using GitVersion.VersionCalculation; + +namespace GitVersion.Core.Tests; + +[TestFixture] +public class PathFilterTests : TestBase +{ + [Test] + public void VerifyNullGuard() => Should.Throw(() => new PathFilter(null!)); +} 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..bc67a4e604 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 IEnumerable 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 4d72d3109a..8d09e6ca4a 100644 --- a/src/GitVersion.Core/Configuration/EffectiveConfiguration.cs +++ b/src/GitVersion.Core/Configuration/EffectiveConfiguration.cs @@ -68,7 +68,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; @@ -124,8 +123,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..d94c778fb1 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; } - bool IsEmpty { get; } + IReadOnlyCollection Paths { get; } + + bool IsEmpty => Before == null && Shas.Count == 0 && Paths.Count == 0; } 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..c8c0cb4fd1 100644 --- a/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs +++ b/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs @@ -25,7 +25,7 @@ public static EffectiveConfiguration GetEffectiveConfiguration( { fallbackConfiguration = parentConfiguration; } - return new EffectiveConfiguration(configuration, branchConfiguration, fallbackConfiguration); + return new EffectiveConfiguration(configuration, branchConfiguration, fallbackConfiguration: fallbackConfiguration); } public static IBranchConfiguration GetBranchConfiguration(this IGitVersionConfiguration configuration, IBranch branch) @@ -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); } 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..bf16c9cc9a 100644 --- a/src/GitVersion.Core/Git/ICommit.cs +++ b/src/GitVersion.Core/Git/ICommit.cs @@ -7,4 +7,5 @@ public interface ICommit : IEquatable, IComparable, IGitObjec DateTimeOffset When { get; } string Message { get; } + IEnumerable DiffPaths { get; } } diff --git a/src/GitVersion.Core/Git/ITreeChanges.cs b/src/GitVersion.Core/Git/ITreeChanges.cs new file mode 100644 index 0000000000..469caf9db8 --- /dev/null +++ b/src/GitVersion.Core/Git/ITreeChanges.cs @@ -0,0 +1,6 @@ +namespace GitVersion.Git; + +public interface ITreeChanges +{ + IEnumerable Paths { get; } +} diff --git a/src/GitVersion.Core/PublicAPI.Shipped.txt b/src/GitVersion.Core/PublicAPI.Shipped.txt index ef124b7b64..2b3133a9e8 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..dfceb7c7e7 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.IEnumerable! +GitVersion.Git.ITreeChanges +GitVersion.Git.ITreeChanges.Paths.get -> System.Collections.Generic.IEnumerable! +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 8b08f0869b..7a941b13d6 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..8a0543a3ea --- /dev/null +++ b/src/GitVersion.Core/VersionCalculation/PathFilter.cs @@ -0,0 +1,51 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.RegularExpressions; +using GitVersion.Git; + +namespace GitVersion.VersionCalculation; + +internal class PathFilter( IEnumerable paths) : IVersionFilter +{ + private readonly List pathsRegexes = paths.Select(path => new Regex(path, RegexOptions.IgnoreCase | RegexOptions.Compiled)).ToList(); + private readonly Dictionary pathMatchCache = []; + + public bool Exclude(IBaseVersion baseVersion, [NotNullWhen(true)] out string? reason) + { + ArgumentNullException.ThrowIfNull(baseVersion); + + reason = null; + if (baseVersion.Source.StartsWith("Fallback") || baseVersion.Source.StartsWith("Git tag") || baseVersion.Source.StartsWith("NextVersion")) return false; + + return Exclude(baseVersion.BaseVersionSource, out reason); + } + + public bool Exclude(ICommit? commit, [NotNullWhen(true)] out string? reason) + { + reason = null; + + if (commit != null) + { + var patchPaths = commit.DiffPaths; + + if (patchPaths != null) + { + foreach (var path in patchPaths) + { + if (!pathMatchCache.TryGetValue(path, out var isMatch)) + { + isMatch = this.pathsRegexes.Any(regex => regex.IsMatch(path)); + pathMatchCache[path] = isMatch; + } + + if (isMatch) + { + reason = "Source was ignored due to commit path matching ignore regex"; + return true; + } + } + } + } + + 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..e453f4bf98 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,20 @@ internal Commit(LibGit2Sharp.Commit innerCommit) : base(innerCommit) public IReadOnlyList Parents => this.parentsLazy.Value; public DateTimeOffset When { get; } public string Message => this.innerCommit.Message; + // TODO implement tag prefix filtering before returning the paths. + public IEnumerable DiffPaths + { + get + { + if (pathsCache.TryGetValue(this.Sha, out var paths)) + return paths; + else + return this.CommitChanges?.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 ITreeChanges CommitChanges => new TreeChanges(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..231fef0e85 100644 --- a/src/GitVersion.LibGit2Sharp/Git/GitRepository.cs +++ b/src/GitVersion.LibGit2Sharp/Git/GitRepository.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using GitVersion.Extensions; using GitVersion.Helpers; using LibGit2Sharp; @@ -7,6 +8,7 @@ namespace GitVersion.Git; internal sealed partial class GitRepository { private Lazy? repositoryLazy; + private readonly ConcurrentDictionary?> patchPathsCache = new(); private IRepository RepositoryInstance { @@ -16,17 +18,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 +50,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 +89,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..48089a6cb5 --- /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 IEnumerable Paths => this.innerTreeChanges.Select(element => element.Path); +} From f767603586bba70d90b73ca462ad1ef7d2bc0202 Mon Sep 17 00:00:00 2001 From: roeil Date: Wed, 21 May 2025 08:33:58 +0300 Subject: [PATCH 2/7] refactor: follow pr comments about data types --- .../IgnoreConfiguration.cs | 6 ++-- .../MergeMessageBaseVersionStrategyTests.cs | 2 +- .../Configuration/IIgnoreConfiguration.cs | 2 +- .../Extensions/ConfigurationExtensions.cs | 4 +-- src/GitVersion.Core/Git/ICommit.cs | 2 +- src/GitVersion.Core/Git/ITreeChanges.cs | 2 +- src/GitVersion.Core/PublicAPI.Unshipped.txt | 4 +-- .../VersionCalculation/PathFilter.cs | 30 +++++++++---------- src/GitVersion.LibGit2Sharp/Git/Commit.cs | 14 +++++---- .../Git/GitRepository.cs | 2 -- .../Git/TreeChanges.cs | 2 +- 11 files changed, 34 insertions(+), 36 deletions(-) diff --git a/src/GitVersion.Configuration/IgnoreConfiguration.cs b/src/GitVersion.Configuration/IgnoreConfiguration.cs index 636571e2a8..4ea9d6d86e 100644 --- a/src/GitVersion.Configuration/IgnoreConfiguration.cs +++ b/src/GitVersion.Configuration/IgnoreConfiguration.cs @@ -24,12 +24,12 @@ public string? BeforeString [JsonPropertyDescription("A sequence of SHAs to be excluded from the version calculations.")] public HashSet Shas { get; init; } = []; - [JsonIgnore] - public bool IsEmpty => Before == null && Shas.Count == 0 && Paths.Count == 0; - 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 && Paths.Count == 0; } diff --git a/src/GitVersion.Core.Tests/VersionCalculation/Strategies/MergeMessageBaseVersionStrategyTests.cs b/src/GitVersion.Core.Tests/VersionCalculation/Strategies/MergeMessageBaseVersionStrategyTests.cs index bc67a4e604..1f03f3a9ea 100644 --- a/src/GitVersion.Core.Tests/VersionCalculation/Strategies/MergeMessageBaseVersionStrategyTests.cs +++ b/src/GitVersion.Core.Tests/VersionCalculation/Strategies/MergeMessageBaseVersionStrategyTests.cs @@ -204,7 +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 IEnumerable DiffPaths => 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/IIgnoreConfiguration.cs b/src/GitVersion.Core/Configuration/IIgnoreConfiguration.cs index d94c778fb1..ea19f6d746 100644 --- a/src/GitVersion.Core/Configuration/IIgnoreConfiguration.cs +++ b/src/GitVersion.Core/Configuration/IIgnoreConfiguration.cs @@ -8,5 +8,5 @@ public interface IIgnoreConfiguration IReadOnlyCollection Paths { get; } - bool IsEmpty => Before == null && Shas.Count == 0 && Paths.Count == 0; + bool IsEmpty { get; } } diff --git a/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs b/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs index c8c0cb4fd1..6f794c91d1 100644 --- a/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs +++ b/src/GitVersion.Core/Extensions/ConfigurationExtensions.cs @@ -25,7 +25,7 @@ public static EffectiveConfiguration GetEffectiveConfiguration( { fallbackConfiguration = parentConfiguration; } - return new EffectiveConfiguration(configuration, branchConfiguration, fallbackConfiguration: fallbackConfiguration); + return new EffectiveConfiguration(configuration, branchConfiguration, fallbackConfiguration); } public static IBranchConfiguration GetBranchConfiguration(this IGitVersionConfiguration configuration, IBranch branch) @@ -44,7 +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); + 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 bf16c9cc9a..d0c5d666e2 100644 --- a/src/GitVersion.Core/Git/ICommit.cs +++ b/src/GitVersion.Core/Git/ICommit.cs @@ -7,5 +7,5 @@ public interface ICommit : IEquatable, IComparable, IGitObjec DateTimeOffset When { get; } string Message { get; } - IEnumerable DiffPaths { get; } + IReadOnlyList DiffPaths { get; } } diff --git a/src/GitVersion.Core/Git/ITreeChanges.cs b/src/GitVersion.Core/Git/ITreeChanges.cs index 469caf9db8..28a4600d68 100644 --- a/src/GitVersion.Core/Git/ITreeChanges.cs +++ b/src/GitVersion.Core/Git/ITreeChanges.cs @@ -2,5 +2,5 @@ namespace GitVersion.Git; public interface ITreeChanges { - IEnumerable Paths { get; } + IReadOnlyList Paths { get; } } diff --git a/src/GitVersion.Core/PublicAPI.Unshipped.txt b/src/GitVersion.Core/PublicAPI.Unshipped.txt index dfceb7c7e7..505df687a4 100644 --- a/src/GitVersion.Core/PublicAPI.Unshipped.txt +++ b/src/GitVersion.Core/PublicAPI.Unshipped.txt @@ -147,7 +147,7 @@ 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.IEnumerable! +GitVersion.Git.ICommit.DiffPaths.get -> System.Collections.Generic.IReadOnlyList! GitVersion.Git.ITreeChanges -GitVersion.Git.ITreeChanges.Paths.get -> System.Collections.Generic.IEnumerable! +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/PathFilter.cs b/src/GitVersion.Core/VersionCalculation/PathFilter.cs index 8a0543a3ea..a10bc22c5c 100644 --- a/src/GitVersion.Core/VersionCalculation/PathFilter.cs +++ b/src/GitVersion.Core/VersionCalculation/PathFilter.cs @@ -1,13 +1,14 @@ +using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; using GitVersion.Git; namespace GitVersion.VersionCalculation; -internal class PathFilter( IEnumerable paths) : IVersionFilter +internal class PathFilter(IReadOnlyList paths) : IVersionFilter { - private readonly List pathsRegexes = paths.Select(path => new Regex(path, RegexOptions.IgnoreCase | RegexOptions.Compiled)).ToList(); - private readonly Dictionary pathMatchCache = []; + private readonly List pathsRegexes = [.. paths.Select(path => new Regex(path, RegexOptions.IgnoreCase | RegexOptions.Compiled))]; + private readonly ConcurrentDictionary pathMatchCache = []; public bool Exclude(IBaseVersion baseVersion, [NotNullWhen(true)] out string? reason) { @@ -27,21 +28,18 @@ public bool Exclude(ICommit? commit, [NotNullWhen(true)] out string? reason) { var patchPaths = commit.DiffPaths; - if (patchPaths != null) + foreach (var path in patchPaths) { - foreach (var path in patchPaths) + if (!pathMatchCache.TryGetValue(path, out var isMatch)) { - if (!pathMatchCache.TryGetValue(path, out var isMatch)) - { - isMatch = this.pathsRegexes.Any(regex => regex.IsMatch(path)); - pathMatchCache[path] = isMatch; - } - - if (isMatch) - { - reason = "Source was ignored due to commit path matching ignore regex"; - return true; - } + isMatch = this.pathsRegexes.Any(regex => regex.IsMatch(path)); + pathMatchCache[path] = isMatch; + } + + if (isMatch) + { + reason = "Source was ignored due to commit path matching ignore regex"; + return true; } } } diff --git a/src/GitVersion.LibGit2Sharp/Git/Commit.cs b/src/GitVersion.LibGit2Sharp/Git/Commit.cs index e453f4bf98..91e08df0a7 100644 --- a/src/GitVersion.LibGit2Sharp/Git/Commit.cs +++ b/src/GitVersion.LibGit2Sharp/Git/Commit.cs @@ -28,19 +28,21 @@ internal Commit(LibGit2Sharp.Commit innerCommit, LibGit2Sharp.Diff repoDiff) : b public DateTimeOffset When { get; } public string Message => this.innerCommit.Message; // TODO implement tag prefix filtering before returning the paths. - public IEnumerable DiffPaths + public IReadOnlyList DiffPaths { get { - if (pathsCache.TryGetValue(this.Sha, out var paths)) - return paths; - else - return this.CommitChanges?.Paths ?? []; + 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 ITreeChanges CommitChanges => new TreeChanges(this.repoDiff.Compare(this.innerCommit.Tree, this.innerCommit.Parents.FirstOrDefault()?.Tree)); + private TreeChanges CommitChanges => new TreeChanges(this.repoDiff.Compare(this.innerCommit.Tree, this.innerCommit.Parents.FirstOrDefault()?.Tree)); } diff --git a/src/GitVersion.LibGit2Sharp/Git/GitRepository.cs b/src/GitVersion.LibGit2Sharp/Git/GitRepository.cs index 231fef0e85..3d2fd756ed 100644 --- a/src/GitVersion.LibGit2Sharp/Git/GitRepository.cs +++ b/src/GitVersion.LibGit2Sharp/Git/GitRepository.cs @@ -1,4 +1,3 @@ -using System.Collections.Concurrent; using GitVersion.Extensions; using GitVersion.Helpers; using LibGit2Sharp; @@ -8,7 +7,6 @@ namespace GitVersion.Git; internal sealed partial class GitRepository { private Lazy? repositoryLazy; - private readonly ConcurrentDictionary?> patchPathsCache = new(); private IRepository RepositoryInstance { diff --git a/src/GitVersion.LibGit2Sharp/Git/TreeChanges.cs b/src/GitVersion.LibGit2Sharp/Git/TreeChanges.cs index 48089a6cb5..9ee9385a7e 100644 --- a/src/GitVersion.LibGit2Sharp/Git/TreeChanges.cs +++ b/src/GitVersion.LibGit2Sharp/Git/TreeChanges.cs @@ -4,5 +4,5 @@ internal sealed class TreeChanges(LibGit2Sharp.TreeChanges innerTreeChanges) : I { private readonly LibGit2Sharp.TreeChanges innerTreeChanges = innerTreeChanges ?? throw new ArgumentNullException(nameof(innerTreeChanges)); - public IEnumerable Paths => this.innerTreeChanges.Select(element => element.Path); + public IReadOnlyList Paths => [.. this.innerTreeChanges.Select(element => element.Path)]; } From f924b855bfab86e664d18995c7e5e5f2c754d01a Mon Sep 17 00:00:00 2001 From: roeil Date: Sun, 1 Jun 2025 18:51:51 +0300 Subject: [PATCH 3/7] test: paths filter --- .../GitRepositoryTestingExtensions.cs | 7 ++ .../IntegrationTests/IgnoreCommitScenarios.cs | 106 +++++++++++++++++- .../VersionCalculation/PathFilterTests.cs | 39 ++++++- .../VersionCalculation/PathFilter.cs | 10 +- src/GitVersion.LibGit2Sharp/Git/Commit.cs | 2 +- 5 files changed, 152 insertions(+), 12 deletions(-) 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/PathFilterTests.cs b/src/GitVersion.Core.Tests/VersionCalculation/PathFilterTests.cs index 8c8286458c..efffb84e18 100644 --- a/src/GitVersion.Core.Tests/VersionCalculation/PathFilterTests.cs +++ b/src/GitVersion.Core.Tests/VersionCalculation/PathFilterTests.cs @@ -7,5 +7,42 @@ namespace GitVersion.Core.Tests; public class PathFilterTests : TestBase { [Test] - public void VerifyNullGuard() => Should.Throw(() => new PathFilter(null!)); + 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/VersionCalculation/PathFilter.cs b/src/GitVersion.Core/VersionCalculation/PathFilter.cs index a10bc22c5c..6201ebf478 100644 --- a/src/GitVersion.Core/VersionCalculation/PathFilter.cs +++ b/src/GitVersion.Core/VersionCalculation/PathFilter.cs @@ -7,16 +7,12 @@ namespace GitVersion.VersionCalculation; internal class PathFilter(IReadOnlyList paths) : IVersionFilter { - private readonly List pathsRegexes = [.. paths.Select(path => new Regex(path, RegexOptions.IgnoreCase | RegexOptions.Compiled))]; + private readonly List 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); - - reason = null; - if (baseVersion.Source.StartsWith("Fallback") || baseVersion.Source.StartsWith("Git tag") || baseVersion.Source.StartsWith("NextVersion")) return false; - return Exclude(baseVersion.BaseVersionSource, out reason); } @@ -26,9 +22,7 @@ public bool Exclude(ICommit? commit, [NotNullWhen(true)] out string? reason) if (commit != null) { - var patchPaths = commit.DiffPaths; - - foreach (var path in patchPaths) + foreach (var path in commit.DiffPaths) { if (!pathMatchCache.TryGetValue(path, out var isMatch)) { diff --git a/src/GitVersion.LibGit2Sharp/Git/Commit.cs b/src/GitVersion.LibGit2Sharp/Git/Commit.cs index 91e08df0a7..3984244f58 100644 --- a/src/GitVersion.LibGit2Sharp/Git/Commit.cs +++ b/src/GitVersion.LibGit2Sharp/Git/Commit.cs @@ -44,5 +44,5 @@ public IReadOnlyList DiffPaths 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 TreeChanges(this.repoDiff.Compare(this.innerCommit.Tree, this.innerCommit.Parents.FirstOrDefault()?.Tree)); + private TreeChanges CommitChanges => new(this.repoDiff.Compare(this.innerCommit.Tree, this.innerCommit.Parents.FirstOrDefault()?.Tree)); } From 66ac29ec9f08e881b5c64d3dad4cf37b0e0127a7 Mon Sep 17 00:00:00 2001 From: roeil Date: Sat, 28 Jun 2025 20:27:11 +0300 Subject: [PATCH 4/7] refactor: use PathFilterMode to indicate explicitly the mode of path-based filtering --- .../GitVersion.Core.Libgit2Sharp.csproj | 3 ++ src/GitVersion.Core/Git/ICommit.cs | 1 + .../VersionCalculation/PathFilter.cs | 42 ++++++++++++------- .../Git/BranchCollection.cs | 2 +- src/GitVersion.LibGit2Sharp/Git/Commit.cs | 1 - 5 files changed, 33 insertions(+), 16 deletions(-) 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.Core/Git/ICommit.cs b/src/GitVersion.Core/Git/ICommit.cs index d0c5d666e2..8d3b225655 100644 --- a/src/GitVersion.Core/Git/ICommit.cs +++ b/src/GitVersion.Core/Git/ICommit.cs @@ -7,5 +7,6 @@ public interface ICommit : IEquatable, IComparable, IGitObjec DateTimeOffset When { get; } string Message { get; } + IReadOnlyList DiffPaths { get; } } diff --git a/src/GitVersion.Core/VersionCalculation/PathFilter.cs b/src/GitVersion.Core/VersionCalculation/PathFilter.cs index 6201ebf478..cb801b6351 100644 --- a/src/GitVersion.Core/VersionCalculation/PathFilter.cs +++ b/src/GitVersion.Core/VersionCalculation/PathFilter.cs @@ -5,9 +5,15 @@ namespace GitVersion.VersionCalculation; -internal class PathFilter(IReadOnlyList paths) : IVersionFilter +internal enum PathFilterMode { - private readonly List pathsRegexes = [.. paths.Select(path => new Regex(path, RegexOptions.Compiled))]; + 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) @@ -16,25 +22,33 @@ public bool Exclude(IBaseVersion baseVersion, [NotNullWhen(true)] out string? re 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) { - foreach (var path in commit.DiffPaths) + switch (mode) { - if (!pathMatchCache.TryGetValue(path, out var isMatch)) - { - isMatch = this.pathsRegexes.Any(regex => regex.IsMatch(path)); - pathMatchCache[path] = isMatch; - } - - if (isMatch) - { - reason = "Source was ignored due to commit path matching ignore regex"; - return true; - } + case PathFilterMode.Inclusive: + { + if (commit.DiffPaths.All(this.IsMatch)) + { + reason = "Source was ignored due to all commit paths matching ignore regex"; + return true; + } + break; + } } } diff --git a/src/GitVersion.LibGit2Sharp/Git/BranchCollection.cs b/src/GitVersion.LibGit2Sharp/Git/BranchCollection.cs index f5edcd38cd..11304240c2 100644 --- a/src/GitVersion.LibGit2Sharp/Git/BranchCollection.cs +++ b/src/GitVersion.LibGit2Sharp/Git/BranchCollection.cs @@ -12,7 +12,7 @@ internal sealed class BranchCollection : IBranchCollection internal BranchCollection(LibGit2Sharp.BranchCollection collection, LibGit2Sharp.Diff diff) { this.innerCollection = collection.NotNull(); -this.branches = new Lazy>(() => [.. this.innerCollection.Select(branch => new Branch(branch, diff))]); + this.branches = new Lazy>(() => [.. this.innerCollection.Select(branch => new Branch(branch, diff))]); this.diff = diff.NotNull(); } diff --git a/src/GitVersion.LibGit2Sharp/Git/Commit.cs b/src/GitVersion.LibGit2Sharp/Git/Commit.cs index 3984244f58..95f30e81cc 100644 --- a/src/GitVersion.LibGit2Sharp/Git/Commit.cs +++ b/src/GitVersion.LibGit2Sharp/Git/Commit.cs @@ -27,7 +27,6 @@ internal Commit(LibGit2Sharp.Commit innerCommit, LibGit2Sharp.Diff repoDiff) : b public IReadOnlyList Parents => this.parentsLazy.Value; public DateTimeOffset When { get; } public string Message => this.innerCommit.Message; - // TODO implement tag prefix filtering before returning the paths. public IReadOnlyList DiffPaths { get From 89b1e958c525e17e83b163887ebb116e3b0aea2e Mon Sep 17 00:00:00 2001 From: roeil Date: Sat, 28 Jun 2025 20:28:33 +0300 Subject: [PATCH 5/7] docs: add paths filter to configuration docs --- .../mdsource/configuration.source.md | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/input/docs/reference/mdsource/configuration.source.md b/docs/input/docs/reference/mdsource/configuration.source.md index 5ba2cabcad..1b29b89523 100644 --- a/docs/input/docs/reference/mdsource/configuration.source.md +++ b/docs/input/docs/reference/mdsource/configuration.source.md @@ -225,6 +225,44 @@ Date and time in the format `yyyy-MM-ddTHH:mm:ss` (eg `commits-before: 2015-10-23T12:23:15`) to setup an exclusion range. Effectively any commit before `commits-before` will be ignored. +#### paths +A sequence of regular expressions that represent paths in the repository. Commits that modify these paths will be excluded from version calculations. For example, to filter out commits that belong to `docs`: +```yaml +ignore: + paths: + - ^docs\/ +``` +##### *Monorepo* +This ignore config can be used to filter only those commits that belong to a specific project in a monorepo. +As an example, consider a monorepo consisting of subdirectories for `ProjectA`, `ProjectB` and a shared `LibraryC`. For GitVersion to consider only commits that are part of `projectA` and shared library `LibraryC`, a regex that matches all paths except those starting with `ProjectA` or `LibraryC` can be used. Either one of the following configs would filter out `ProjectB`. +* Specific match on `/ProjectB/*`: +```yaml +ignore: + paths: + - `^\/ProductB\/.*` +``` +* Negative lookahead on anything other than `/ProjectA/*` and `/LibraryC/*`: +```yaml +ignore: + paths: + - `^(?!\/ProductA\/|\/LibraryC\/).*` +``` +A commit having changes only in `/ProjectB/*` path would be ignored. A commit having changes in the following paths wouldn't be ignored: +* `/ProductA/*` +* `/LibraryC/*` +* `/ProductA/*` and `/LibraryC/*` +* `/ProductA/*` and `/ProductB/*` +* `/LibraryC/*` and `/ProductB/*` +* `/ProductA/*` and `/ProductB/*` and `/LibraryC/*` + +::: +Note: The `ignore.paths` configuration is case-sensitive. This can lead to unexpected behavior on case-insensitive file systems, such as Windows. To ensure consistent matching regardless of case, you can prefix your regular expressions with the case-insensitive flag `(?i)`. For example, `(?i)^docs\/` will match both `docs/` and `Docs/`. +::: + +::: {.alert .alert-warning} +A commit is ignored by the `ignore.paths` configuration only if **all paths** changed in that commit match one or more of the specified regular expressions. If a path in a commit does not match any one of the ignore patterns, that commit will be included in version calculations. +::: + ### merge-message-formats Custom merge message formats to enable identification of merge messages that do not From 3ce7d156e808f76b63c3d5acd636b3808f511465 Mon Sep 17 00:00:00 2001 From: roeil Date: Sat, 28 Jun 2025 20:34:16 +0300 Subject: [PATCH 6/7] chore: minor pr comments --- .../VersionCalculation/PathFilter.cs | 23 ++++++++++--------- .../ConfiguredNextVersionVersionStrategy.cs | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/GitVersion.Core/VersionCalculation/PathFilter.cs b/src/GitVersion.Core/VersionCalculation/PathFilter.cs index cb801b6351..75c93fbf13 100644 --- a/src/GitVersion.Core/VersionCalculation/PathFilter.cs +++ b/src/GitVersion.Core/VersionCalculation/PathFilter.cs @@ -35,21 +35,22 @@ private bool IsMatch(string path) public bool Exclude(ICommit? commit, [NotNullWhen(true)] out string? reason) { reason = null; + if (commit == null) + { + return false; + } - if (commit != null) + switch (mode) { - switch (mode) - { - case PathFilterMode.Inclusive: + case PathFilterMode.Inclusive: + { + if (commit.DiffPaths.All(this.IsMatch)) { - if (commit.DiffPaths.All(this.IsMatch)) - { - reason = "Source was ignored due to all commit paths matching ignore regex"; - return true; - } - break; + reason = "Source was ignored due to all commit paths matching ignore regex"; + return true; } - } + break; + } } return false; diff --git a/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/ConfiguredNextVersionVersionStrategy.cs b/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/ConfiguredNextVersionVersionStrategy.cs index 129f655733..275aee185a 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 (!this.Context.Configuration.VersionStrategy.HasFlag(VersionStrategies.ConfiguredNextVersion)) + if (!Context.Configuration.VersionStrategy.HasFlag(VersionStrategies.ConfiguredNextVersion)) yield break; var nextVersion = Context.Configuration.NextVersion; From 212fe9f464a650c16f54b49e42ca096ca66a1b0d Mon Sep 17 00:00:00 2001 From: Artur Stolear Date: Sat, 28 Jun 2025 17:45:25 +0000 Subject: [PATCH 7/7] Docs changes --- docs/input/docs/reference/configuration.md | 47 ++++++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/docs/input/docs/reference/configuration.md b/docs/input/docs/reference/configuration.md index f6ac35c566..78f90204b8 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: @@ -621,6 +624,44 @@ Date and time in the format `yyyy-MM-ddTHH:mm:ss` (eg `commits-before: 2015-10-23T12:23:15`) to setup an exclusion range. Effectively any commit before `commits-before` will be ignored. +#### paths +A sequence of regular expressions that represent paths in the repository. Commits that modify these paths will be excluded from version calculations. For example, to filter out commits that belong to `docs`: +```yaml +ignore: + paths: + - ^docs\/ +``` +##### *Monorepo* +This ignore config can be used to filter only those commits that belong to a specific project in a monorepo. +As an example, consider a monorepo consisting of subdirectories for `ProjectA`, `ProjectB` and a shared `LibraryC`. For GitVersion to consider only commits that are part of `projectA` and shared library `LibraryC`, a regex that matches all paths except those starting with `ProjectA` or `LibraryC` can be used. Either one of the following configs would filter out `ProjectB`. +* Specific match on `/ProjectB/*`: +```yaml +ignore: + paths: + - `^\/ProductB\/.*` +``` +* Negative lookahead on anything other than `/ProjectA/*` and `/LibraryC/*`: +```yaml +ignore: + paths: + - `^(?!\/ProductA\/|\/LibraryC\/).*` +``` +A commit having changes only in `/ProjectB/*` path would be ignored. A commit having changes in the following paths wouldn't be ignored: +* `/ProductA/*` +* `/LibraryC/*` +* `/ProductA/*` and `/LibraryC/*` +* `/ProductA/*` and `/ProductB/*` +* `/LibraryC/*` and `/ProductB/*` +* `/ProductA/*` and `/ProductB/*` and `/LibraryC/*` + +::: +Note: The `ignore.paths` configuration is case-sensitive. This can lead to unexpected behavior on case-insensitive file systems, such as Windows. To ensure consistent matching regardless of case, you can prefix your regular expressions with the case-insensitive flag `(?i)`. For example, `(?i)^docs\/` will match both `docs/` and `Docs/`. +::: + +::: {.alert .alert-warning} +A commit is ignored by the `ignore.paths` configuration only if **all paths** changed in that commit match one or more of the specified regular expressions. If a path in a commit does not match any one of the ignore patterns, that commit will be included in version calculations. +::: + ### merge-message-formats Custom merge message formats to enable identification of merge messages that do not