Skip to content

[Feature] Replace LibGit2Sharp with a managed Git implementation (managed reads + git CLI writes) #5031

Description

@gittools-bot

Description

Replace LibGit2Sharp with a hybrid Git backend: a vendored fully-managed reader for all read/history operations plus git CLI shell-out for writes and network. After this, GitVersion ships zero native binaries.

Supersedes the exploration in arturcic#236. Full design document: docs/design/managed-git-migration.md (landing on next/v7).

Status (2026-07, v7.0 prep)

Phases A, B-pre and B are complete and integrating into next/v7 via #5075 (managed backend opt-in behind GITVERSION_GIT_BACKEND=managed, libgit2 the default). C, E and F remain as post-7.0 work; D was dropped.

Phase Sub-issue State
A #5032 ✅ done
B-pre (folded into #5032) ✅ done
B.1 object store #5033 ✅ done
B.2 refs/discovery #5034 ✅ done
B.3 revwalk/merge-base #5035 ✅ done
B.4 diff/status #5036 ✅ done
B.5 adapter + parity #5037 ✅ done
C — v7.1 default flip #5039 ⏳ post-7.0
D — fixture migration #5038 ⛔ not planned (test-only; ~3× slower for zero product benefit — libgit2 stays in the non-shipped GitVersion.Testing)
E — remove libgit2 #5040 ⏳ post-7.0
F — commit-graph (optional) #5041 ⏳ optional

Motivation

Key research findings (2026-07)

  • No maintained managed .NET library implements git writes or fetch/clone. Every managed option — Nerdbank.GitVersioning's internal ManagedGit, GitReader, ManagedGitLib — is read-only. NBGV itself keeps libgit2 for write paths.
  • NBGV's managed engine achieved >10x faster history walks than libgit2 (Consider using a managed Git backend dotnet/Nerdbank.GitVersioning#505); its MIT pack/idx/delta code is the porting source for our vendored reader.
  • The seam is already clean: GitVersion.Core has zero LibGit2Sharp references. Only GitVersion.LibGit2Sharp (adapter), GitVersion.Testing (fixtures), and new-cli's source-linked copy touch it. All write/network operations funnel through a single consumer: GitPreparer (CI normalization: clone/fetch/checkout/ls-remote/ref mutation, username/password HTTPS auth only).
  • git on PATH will be required only for CI-normalization/dynamic-repo scenarios; pure version calculation on a prepared checkout needs no git binary at all.

Proposed architecture

As shipped (Phase B.5): the vendored reader and the git-CLI executor both live in src/GitVersion.Git.Managed — it implements the GitVersion.Core/Git/ interfaces directly (mirroring GitVersion.LibGit2Sharp), with the CLI executor as its CommandLine/ folder. The interim standalone GitVersion.Git.CommandLine and a separate thin GitVersion.Git adapter project described below were not kept as separate projects. Backend selection lives at the composition roots via GITVERSION_GIT_BACKEND (GitBackendSelector in GitVersion.Core).

  • src/GitVersion.Git.Managed — vendored reader (no Core dependency): pack/idx/delta + loose objects ported from NBGV ManagedGit (MIT, attributed); fresh implementations of the parity-critical pieces: revwalk (topo/time/first-parent with libgit2-identical ordering — version output depends on it), merge-base, tree diff (changed paths), .git/index v2–v4 reading for UncommittedChangesCount, discovery/worktrees/shallow. Hash-agnostic object ids (SHA-256-ready); reftable detected with a clear error initially.
  • src/GitVersion.Git.CommandLine — git CLI executor: ArgumentList (no shell), plumbing-only parsing, credentials via per-invocation -c http.<url>.extraHeader, stderr mapped onto existing LockedFileException/401/404 handling, git version probe (≥2.30).
  • src/GitVersion.Git — thin adapter implementing IGitRepository/IMutatingGitRepository; DI surface unchanged; new-cli later source-links the read-only subset.

Phasing (each shippable)

Phase Scope Est.
A git-CLI mutator replaces GitRepository.mutating.cs (reads stay libgit2) — retires the fragile native network/SSL/credential paths first 2–3 wk
B-pre Interface cleanups: Core-owned sort enum, typed CommitFilter, remove public ToGitRepository() leak 3–5 d
B Managed reader behind GITVERSION_GIT_BACKEND=managed|libgit2; dual-backend CI matrix runs the full integration suites both ways + order-sensitive parity tests + real-world corpus JSON diffing 8–12 wk
C Default flip to managed; libgit2 escape hatch for one release 1 wk + soak
D GitVersion.Testing fixtures → pure git-CLI writes (already partially shells out for init/pull/gc) 2–3 wk, parallel
E Delete LibGit2Sharp projects + package; nupkg assertion test: zero runtimes/**/native/* 1–2 wk
F Optional: commit-graph accelerator (generation numbers for topo/merge-base) 2–3 wk

~4–5 months elapsed to a LibGit2Sharp-free GitVersion.

Top risks (mitigations in the design doc)

  • Revwalk ordering parity with libgit2 (port the algorithm precisely; order-sensitive dual-backend tests; corpus diffing gates the default flip)
  • Merge-base candidate selection on criss-cross merges
  • Shallow-clone boundaries (the CI default) and worktree/commondir resolution
  • Fixture suite slowdown from process spawns (batch with git fast-import if needed)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions