From 60535dc2c3d38adc92d71f30683f4d5cb630f806 Mon Sep 17 00:00:00 2001 From: Sam Nelson Date: Fri, 30 Jan 2026 11:46:01 -0700 Subject: [PATCH 1/4] Fix memory leak in MainlineVersionStrategy - Remove ToArray() loop to prevent exponential array growth - Add caching to GetCommitsWasBranchedFrom to avoid redundant calculations - Fixes OOM kills on ADO agents with large repos --- .../MainlineVersionStrategy.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/MainlineVersionStrategy.cs b/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/MainlineVersionStrategy.cs index a7cd9a342d..5aa19e8470 100644 --- a/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/MainlineVersionStrategy.cs +++ b/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/MainlineVersionStrategy.cs @@ -21,6 +21,7 @@ internal sealed class MainlineVersionStrategy( private readonly ITaggedSemanticVersionService taggedSemanticVersionService = taggedSemanticVersionService.NotNull(); private readonly IRepositoryStore repositoryStore = repositoryStore.NotNull(); private readonly IIncrementStrategyFinder incrementStrategyFinder = incrementStrategyFinder.NotNull(); + private readonly Dictionary>> commitsWasBranchedFromCache = new(); private GitVersionContext Context => contextLazy.Value; @@ -277,6 +278,15 @@ private bool IterateOverCommitsRecursive( private Dictionary> GetCommitsWasBranchedFrom( IBranch branch, params IBranch[] excludedBranches) { + // Create cache key from branch name and excluded branches + var cacheKey = $"{branch.Name}|{string.Join(",", excludedBranches.Select(b => b.Name).OrderBy(n => n))}"; + + // Return cached result if available + if (this.commitsWasBranchedFromCache.TryGetValue(cacheKey, out var cachedResult)) + { + return cachedResult; + } + Dictionary> result = []; var branchCommits = repositoryStore.FindCommitBranchesBranchedFrom( @@ -298,8 +308,9 @@ private bool IterateOverCommitsRecursive( throw new InvalidOperationException(); } - if ((branchConfiguration.IsMainBranch ?? Context.Configuration.IsMainBranch) != true) continue; - foreach (var _ in value.ToArray()) + // Fix: Just add the item once instead of duplicating for each existing item + // The original logic caused exponential growth: 1→2→4→8→16 with multiple branches + if ((branchConfiguration.IsMainBranch ?? Context.Configuration.IsMainBranch) == true) { value.Add(new(item, branchConfiguration)); } @@ -315,6 +326,10 @@ private bool IterateOverCommitsRecursive( { result[item.Key] = [.. item.Value.OrderByDescending(element => (element.Configuration.IsMainBranch ?? Context.Configuration.IsMainBranch) == true)]; } + + // Cache the result for future calls + this.commitsWasBranchedFromCache[cacheKey] = result; + return result; } From 8fae7d8e2b2e4e019ab1e1c655933fbeffc2d9b8 Mon Sep 17 00:00:00 2001 From: Sam Nelson Date: Fri, 30 Jan 2026 13:50:45 -0700 Subject: [PATCH 2/4] Use canonical identifier for cache key to avoid name collisions Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../VersionSearchStrategies/MainlineVersionStrategy.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/MainlineVersionStrategy.cs b/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/MainlineVersionStrategy.cs index 5aa19e8470..c1c7bdca45 100644 --- a/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/MainlineVersionStrategy.cs +++ b/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/MainlineVersionStrategy.cs @@ -278,8 +278,8 @@ private bool IterateOverCommitsRecursive( private Dictionary> GetCommitsWasBranchedFrom( IBranch branch, params IBranch[] excludedBranches) { - // Create cache key from branch name and excluded branches - var cacheKey = $"{branch.Name}|{string.Join(",", excludedBranches.Select(b => b.Name).OrderBy(n => n))}"; + // Create cache key from canonical branch name and canonical excluded branch names + var cacheKey = $"{branch.Name.Canonical}|{string.Join(",", excludedBranches.Select(b => b.Name.Canonical).OrderBy(n => n))}"; // Return cached result if available if (this.commitsWasBranchedFromCache.TryGetValue(cacheKey, out var cachedResult)) From e18539bc2ed986b549c52b1b09b293217583558b Mon Sep 17 00:00:00 2001 From: Sam Nelson Date: Fri, 30 Jan 2026 15:50:12 -0700 Subject: [PATCH 3/4] Remove redundant boolean literal --- .../VersionSearchStrategies/MainlineVersionStrategy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/MainlineVersionStrategy.cs b/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/MainlineVersionStrategy.cs index c1c7bdca45..d103dbc0de 100644 --- a/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/MainlineVersionStrategy.cs +++ b/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/MainlineVersionStrategy.cs @@ -310,7 +310,7 @@ private bool IterateOverCommitsRecursive( // Fix: Just add the item once instead of duplicating for each existing item // The original logic caused exponential growth: 1→2→4→8→16 with multiple branches - if ((branchConfiguration.IsMainBranch ?? Context.Configuration.IsMainBranch) == true) + if (branchConfiguration.IsMainBranch ?? Context.Configuration.IsMainBranch) { value.Add(new(item, branchConfiguration)); } From 5d62b74751d273fdc6f582a5983b5efad5845f43 Mon Sep 17 00:00:00 2001 From: Sam Nelson Date: Fri, 30 Jan 2026 15:58:48 -0700 Subject: [PATCH 4/4] Properly handle nullable bool --- .../VersionSearchStrategies/MainlineVersionStrategy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/MainlineVersionStrategy.cs b/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/MainlineVersionStrategy.cs index d103dbc0de..805dfae9f8 100644 --- a/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/MainlineVersionStrategy.cs +++ b/src/GitVersion.Core/VersionCalculation/VersionSearchStrategies/MainlineVersionStrategy.cs @@ -310,7 +310,7 @@ private bool IterateOverCommitsRecursive( // Fix: Just add the item once instead of duplicating for each existing item // The original logic caused exponential growth: 1→2→4→8→16 with multiple branches - if (branchConfiguration.IsMainBranch ?? Context.Configuration.IsMainBranch) + if ((branchConfiguration.IsMainBranch ?? Context.Configuration.IsMainBranch).GetValueOrDefault()) { value.Add(new(item, branchConfiguration)); }