TL;DR: go-tool-base’s VCS support has two halves: forge-API calls for releases and pull requests, and a RepoLike object for local Git operations. The repo half came from another project and is wired in ahead of its main consumer, the code generator. rust-tool-base carries the same capability deliberately. The Rust-specific part is how: its Repo type runs on gix, a pure-Rust Git implementation, so there’s no dependency on a git binary and no libgit2 C library in a default build.

A VCS subsystem with two halves

go-tool-base has a VCS subsystem, and it does two distinct jobs.

The first is forge APIs. GitHub and GitLab, Enterprise and nested group paths included. It authenticates, lists releases, fetches release assets, manages pull requests. The self-update machinery sits on this half, and it’s what a tool uses to ask “what’s the latest release?”

The second is local Git. go-tool-base also carries a RepoLike object, an abstraction over an actual Git repository on disk: clone it, read its commit history, diff two trees, check its status. This half doesn’t talk to a hosting service at all. It talks to the .git directory.

It would be easy to assume the second half grew out of the first. It didn’t, and where it actually came from is the part worth telling.

A capability ahead of its consumer

The RepoLike object wasn’t built for go-tool-base. It came from another project, where it had already proved itself, and it was pulled into go-tool-base on purpose, with a specific future consumer in mind: the code generator.

The plan is for the generator to use Git directly. When it scaffolds a new tool, that tool should start life as a Git repository, with a git init and an initial commit. When you later regenerate, the generator should diff the regenerated template output against your working tree to detect drift, the same idea as respecting your edits. Both of those are local Git operations, not API calls, so the generator needs a repository abstraction to call into.

That wiring isn’t finished yet. The generator doesn’t drive RepoLike today. But the capability is in place, deliberately, ahead of the consumer that will use it, because the alternative is bolting Git support on later under deadline pressure, and that is how you end up with the wrong abstraction.

So when rust-tool-base was built, a repository abstraction was never in question. The Rust port carries the same capability for the same reason: a Repo type with init, open, clone, walk, diff, blame, status, commit, fetch and checkout, present and ready for the generator to wire into. The open question was never whether to have it. It was how to do Git from inside a Rust program, and there are three answers, only one of which is good.

Three ways to do Git, and the one worth picking

Shell out to git. Run the git binary as a subprocess and parse its output. It works until it doesn’t. The binary might not be installed. It might be a different version with different output. Its output is formatted for humans and changes between releases, so parsing it is a standing liability. You’ve made an undeclared dependency on a program you don’t ship.

Link libgit2. libgit2 is the C library that reimplements Git as something you can call from code, and git2 is the Rust binding to it. It’s solid and widely used. But it’s a C dependency, which means a C toolchain in the build, and it is consistently the single biggest source of cross-compilation pain in the Rust Git ecosystem. The musl builds, the Windows builds, the static linking, libgit2 is where they tend to break.

Use gix. gix is a reimplementation of Git in pure Rust. No C library, no subprocess. It’s just Rust code, and it compiles and cross-compiles like any other crate, because that’s all it is. It’s also generally faster, and being pure Rust it fits the no-unsafe-in-first-party-code story far more comfortably than dragging a C library along.

rtb-vcs is gix-first. The Repo type is built on it. There is no git binary dependency, and there is no libgit2 in a default build.

gix is still maturing, and a few write paths, push in particular, aren’t ready in it yet. For those, git2 stays available as an opt-in fallback behind a Cargo feature. Off by default, so the libgit2 C dependency and its cross-compile cost only land in builds that explicitly ask for push support. The common case, a tool that clones, reads history, diffs and commits, pays none of it.

Repo is a foundation, not a façade

One design decision is worth calling out, because it came straight from a go-tool-base lesson.

It would have been easy to build Repo as a narrow façade exposing exactly what the scaffolder and the release-notes feature need today, and nothing else. That was rejected on purpose. go-tool-base’s RepoLike is itself the cautionary tale: it arrived from another project, settled into a sensible abstraction, and is already lined up to carry a consumer, the generator, that wasn’t driving its design when it was first written. A repository abstraction gets used by code that doesn’t exist yet. Build one as a narrow façade around today’s needs and you have guaranteed a rewrite the first time a downstream tool wants something slightly different.

So rtb-vcs’s Repo is built as a foundation: a sensible, reasonably complete vocabulary of Git operations that a tool author can compose richer behaviour on, without re-importing gix directly and re-deriving the framework’s auth and concurrency conventions. The errors back this up. gix’s error types are not leaked through the public API; they’re wrapped in semantic RepoError variants, so the backend could be swapped, gix to git2, or to something else entirely, without breaking a single downstream caller.

Stepping back

go-tool-base’s VCS support has two halves: forge-API calls for releases and pull requests, and a RepoLike object for local Git operations. The repo half arrived from another project and is wired in ahead of its intended consumer, the code generator, which will use it to initialise repositories for scaffolded tools and to diff regenerated output for drift.

rust-tool-base carries the same capability on purpose. Its Repo type is built on gix, a pure-Rust Git implementation, so there’s no dependency on an installed git binary and no libgit2 C library in a default build, which keeps cross-compilation clean. git2 stays an opt-in fallback for the few write paths gix can’t do yet. And Repo is built as a foundation for downstream tools, with the backend wrapped behind its own error type so it can be replaced without breaking callers.