Skip to content

Version calculation performs one revision walk per version tag (O(tags x commits)) #5245

Description

@mk185147

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

  1. Create a repository where most commits carry a version tag (for example CI tags every push).
  2. Check out a detached pull request merge head with all remote branches present.
  3. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions