Describe the bug
On repositories with many version tags, GitVersion spends almost all of its time inside repeated revision walks.
TaggedCommitVersionStrategy evaluates every candidate version tag, and for each one calls IIncrementStrategyFinder.DetermineIncrementedField(currentCommit, baseVersionSource: <tag commit>, ...). That calls IRepositoryStore.GetCommitLog(baseVersionSource, currentCommit, ignore), which runs a full Topological | Time revision walk every single time:
var filter = new CommitFilter
{
IncludeReachableFrom = currentCommit,
ExcludeReachableFrom = baseVersionSource,
SortBy = CommitSortStrategies.Topological | CommitSortStrategies.Time
};
var commits = FilterCommits(filter).ToArray();
The cost therefore scales as O(tags x commits). The commits reachable from the current commit do not change while a single version is calculated, so the same history is walked over and over again.
Expected Behavior
Version calculation completes in seconds on a repository with a few thousand tags.
Actual Behavior
On a repository with 1632 tags, 514 branches and ~3.3k commits reachable from the target commit, calculating the version for a detached pull request merge head took over 10 minutes on a GitHub hosted Ubuntu runner, and ~110 s locally.
Instrumenting a local run showed 5945 revision walks:
| stage |
calls |
time |
NextVersionCalculator.FindVersion |
1 |
72.0 s |
TaggedCommitVersionStrategy |
4 |
69.0 s |
DetermineIncrementedField |
5944 |
68.6 s |
RepositoryStore.GetCommitLog |
5945 |
53.2 s |
tagged ancestor pruning in GetCommitHistory |
5944 |
13.5 s |
GetTaggedSemanticVersions (ToLookup rebuilt per call) |
5950 |
3.9 s |
The factor four on the strategy comes from the four effective branch configurations which are evaluated for a pull request merge head.
Two smaller contributors are visible in the same trace:
IncrementStrategyFinder.GetCommitHistory removes every already tagged commit and its ancestors from each commit log, which is redone per base version source even though the result does not depend on it.
TaggedSemanticVersionRepository caches the tag list but rebuilds the ILookup it returns on every call.
Possible Solution
Walk the history once per current commit and derive each commit log from that cached walk, and hoist the tagged ancestor pruning out of the per tag loop.
Steps to Reproduce the Behavior
- Create a repository where most commits carry a version tag (for example CI tags every push).
- Check out a detached pull request merge head with all remote branches present.
- Run
gitversion and observe the runtime grow with the number of tags.
Context
This makes GitVersion impractical as a CI step on repositories which tag every build: the version calculation dominates the pipeline.
Your Environment
- GitVersion 6.7.0, reproduced on
main as well (the hot path is unchanged between the two)
- Ubuntu 26.04 GitHub hosted runner, and Windows locally
- LibGit2Sharp backend
Describe the bug
On repositories with many version tags, GitVersion spends almost all of its time inside repeated revision walks.
TaggedCommitVersionStrategyevaluates every candidate version tag, and for each one callsIIncrementStrategyFinder.DetermineIncrementedField(currentCommit, baseVersionSource: <tag commit>, ...). That callsIRepositoryStore.GetCommitLog(baseVersionSource, currentCommit, ignore), which runs a fullTopological | Timerevision walk every single time:The cost therefore scales as
O(tags x commits). The commits reachable from the current commit do not change while a single version is calculated, so the same history is walked over and over again.Expected Behavior
Version calculation completes in seconds on a repository with a few thousand tags.
Actual Behavior
On a repository with 1632 tags, 514 branches and ~3.3k commits reachable from the target commit, calculating the version for a detached pull request merge head took over 10 minutes on a GitHub hosted Ubuntu runner, and ~110 s locally.
Instrumenting a local run showed 5945 revision walks:
NextVersionCalculator.FindVersionTaggedCommitVersionStrategyDetermineIncrementedFieldRepositoryStore.GetCommitLogGetCommitHistoryGetTaggedSemanticVersions(ToLookuprebuilt per call)The factor four on the strategy comes from the four effective branch configurations which are evaluated for a pull request merge head.
Two smaller contributors are visible in the same trace:
IncrementStrategyFinder.GetCommitHistoryremoves every already tagged commit and its ancestors from each commit log, which is redone per base version source even though the result does not depend on it.TaggedSemanticVersionRepositorycaches the tag list but rebuilds theILookupit returns on every call.Possible Solution
Walk the history once per current commit and derive each commit log from that cached walk, and hoist the tagged ancestor pruning out of the per tag loop.
Steps to Reproduce the Behavior
gitversionand observe the runtime grow with the number of tags.Context
This makes GitVersion impractical as a CI step on repositories which tag every build: the version calculation dominates the pipeline.
Your Environment
mainas well (the hot path is unchanged between the two)