Make rugged optional, default to pure-Ruby git backend#261
Open
stanhu wants to merge 5 commits into
Open
Conversation
Move all direct Rugged usage out of Changeset into a new RuggedAdapter. Changeset now delegates workdir lookup, file/line diff iteration, and emptiness checks to an adapter, keeping filtering and validation logic in Changeset itself. No behavior change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implement a Changeset adapter on top of the pure-Ruby git gem so undercover can read changes without depending on libgit2. Parses unified diff hunks to recover added line numbers, mirroring the RuggedAdapter contract. Parameterize changeset_spec over both adapters to lock the contract. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Remove rugged from undercover.gemspec so installing undercover no longer pulls in the libgit2 native extension. RuggedAdapter is still preferred at runtime when rugged happens to be loadable, so existing users see no change. Keep rugged in the dev Gemfile so both adapters get exercised by the test suite. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Explain that undercover ships with the pure-Ruby git gem by default and opts into rugged automatically when it is in the user's bundle. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Call out in the Git backend section that the git gem shells out to a system git binary, which is universally present on dev and CI machines but may need to be added to minimal production images (distroless, slim Docker bases). Point users at rugged as the escape hatch when installing git is not an option. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Refactor
Undercover::Changesetbehind a small adapter interface so the gem no longer requiresruggedas a hard dependency. Two adapters live side by side:Undercover::Changeset::GitAdapter— pure Ruby, backed by thegitgem (new default)Undercover::Changeset::RuggedAdapter— libgit2-backed, used automatically whenruggedis loadableruggedis dropped fromadd_dependencyin the gemspec. Users who already haveruggedin their bundle (directly or transitively) keep using it —Changeset.default_adapter_classprefersruggedwhenever it can be required, falling back to thegitgem otherwise. No runtime configuration is needed to switch backends; install or removeruggedfrom your bundle.Why avoid native extensions by default
ruggedwraps libgit2, which means everygem install undercovertriggers a C compile and pulls libgit2 along for the ride. That's a real cost for a tool whose job is to read git diffs:brew install libgit2, Debian/Ubuntu users hitapt install libgit2-dev, Alpine/musl users hit notoriously fiddly cross-compilation issues. New contributors and CI cold-cache builds eat that cost on every fresh environment.rugged's upper bound (< 1.10) exists because its native bindings track libgit2 ABI changes. Bumping system libgit2 can silently break the gem until a matchingruggedships. Pure-Ruby code has no such coupling.llhttp_*symbols from its shared object. Loading rugged in the same process asllhttp-ffi(used by the popularhttpgem) causes hard-to-debug ABI collisions — exactly the kind of failure mode pure-Ruby gems can't produce. See rugged#1000 for the in-flight fix; until it ships, any process that loads both rugged andhttp/llhttp-ffiis at risk.bundle installon cold caches, which matters most in containers and CI — exactly where undercover runs.rugged-bundling app whether or not the user benefits from libgit2's features. Thegitgem shells out to the systemgit, which is already managed by the OS package manager.ruggedremains meaningfully faster on large repos because it reads packfiles in-process instead of forkinggit. Users who already rely on it (or who want it for monorepo performance) just keepgem 'rugged'in their Gemfile and undercover picks it up automatically.The net effect: smaller, friction-free default install for the 95% case, with the fast path still one
gem 'rugged'line away.Compatibility caveat
The
gitgem shells out to the systemgitbinary at runtime, so the default backend introduces a soft runtime requirement ongitbeing onPATH. In practice this is almost always already true:gitinstalled by definitiongitruby:3.x, Debian/Ubuntu, Alpine) includegitgitfor any git-sourced gem, so projects with such gems already needgitpresentThe realistic edge cases are minimal images —
distroless,*-slimvariants, scratch-based custom images — wheregitmay have been intentionally stripped. For those users, the README's "Git backend" section points atruggedas the escape hatch (no systemgitneeded, since libgit2 runs in-process). Happy to upgrade this to a more prominent upgrade-note callout if you'd like it called out more loudly than a paragraph in the backend section.Changes
lib/undercover/changeset.rb— slimmed down to delegate to an adapter; no longer requiresruggedlib/undercover/changeset/rugged_adapter.rb— extracted from the oldChangesetbody, behaviorally identicallib/undercover/changeset/git_adapter.rb— new, parses unified diff hunks for added line numbersundercover.gemspec— addsgit ~> 4.0, removesruggedGemfile— addsruggedas a dev dependency so both adapters get exercised in CIspec/changeset_spec.rb— converted to a shared example group run against both adaptersREADME.md— documents the backend choice, runtime requirements, and how to opt back intoruggedSplit into five reviewable commits:
Test plan
bundle exec rspec— all 206 examples pass with both adaptersbundle exec rspec spec/changeset_spec.rb— 18 examples (9 specs × 2 adapters), full contract paritybundle exec rubocop— cleanruggedis unloadable,Changeset.default_adapter_classreturnsGitAdapterand end-to-end behavior matches🤖 Generated with Claude Code