Skip to content

Follow renamed default branch in git sources - #9813

Open
hsbt wants to merge 15 commits into
masterfrom
claude/rubygems-issue-5810-aaa18e
Open

Follow renamed default branch in git sources#9813
hsbt wants to merge 15 commits into
masterfrom
claude/rubygems-issue-5810-aaa18e

Conversation

@hsbt

@hsbt hsbt commented Aug 26, 2026

Copy link
Copy Markdown
Member

A git source that pins no branch, tag or ref follows whatever branch the cached bare clone's HEAD points at. When the remote renames its default branch, that fetch fails with couldn't find remote ref, and Bundler::Source::Git#fetch swallows it as a network problem. The user sees Using cached git data because of network errors while the lockfile stays pinned to the old revision, so bundle update never picks up new commits. Recovering means deleting the cache by hand.

On that specific failure, and only when no branch, tag, ref or locked revision was requested, Bundler now asks the remote for its current default branch with git ls-remote --symref, fetches it, and repoints the cached clone's HEAD. Unaffected sources pay no extra round trip because the lookup runs only after a fetch has already failed.

Fixes #5810.

hsbt and others added 15 commits August 26, 2026 10:03
When a git source pins no branch, Bundler fetches whatever branch the
cached clone's HEAD points at. Renaming the remote's default branch made
that fetch fail, and the failure was swallowed as a network error, so the
lockfile stayed silently pinned to the old revision. Resolve the remote's
current default branch on that failure and repoint the cached clone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rename the branch without naming the old one so the spec does not depend on
what `build_git` picks as its default branch. Drop the comment that repeated
the rename rationale already stated on the recovery helper.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The recovery reassigned the `command` and `command_with_no_credentials`
locals that the enclosing retry block closes over. When `git symbolic-ref`
then failed, the retry re-ran the recovery fetch instead of the original
one, that fetch succeeded because the ref was already there, and the block
returned success with HEAD still on the deleted branch, so a stale revision
was written with nothing reported. The same reassignment made a failed
recovery report the recovery command next to the old branch name.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Repointing the cached clone's HEAD changes which branch every project
sharing that cache tracks, so it should not happen silently.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every other git invocation here puts the Gemfile-controlled URI after
`--` so git cannot read it as an option, and `git ls-remote` accepts
`--upload-pack`, which runs a command.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Dropping the guard on the recovery path left every git spec green, so
nothing stopped an explicit `:branch` from being silently swapped for
whatever branch the remote now points HEAD at.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`git_null` drops stderr, so an unsupported `--symref`, an auth failure and
a network error all collapsed into the same silent nil and the recovery
just did not happen with nothing to explain it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`refspec = refspec()` only reaches the method because of the parentheses.
Dropping them binds the half-defined local instead, and `.compact` then
turns the missing refspec into a fetch that quietly updates nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The `&&` form put a boolean and a branch name in the same local, so any
later `nil?` check would have missed the false case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A failed `git symbolic-ref` raised through the retry block, which then
redid the whole recovery on each attempt: four passes, twelve network
round trips and nine seconds of backoff for a local lock conflict. Treat
it as best effort instead and report the original fetch failure, which
also stops the cache being left with the new branch fetched but HEAD
still on the old one. The recovery fetch now says why it failed too.

The message it prints on success no longer claims the branch was renamed,
which it cannot observe. A deleted branch, a cache seeded by another
project's `branch:`, and a detached cache HEAD all reach the same line and
none of them is a rename. It also warns rather than informs now, since it
changes which commit gets installed and `--quiet` drops info.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deleting the warning or the guard that keeps a pinned branch from
following left both specs green, so neither the message nor the cached
clone's HEAD was actually covered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The added block ran to about 10% comment lines against roughly 4% for the
file, and part of it narrated the method body instead of explaining it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A remote may advertise a ref name that is not valid UTF-8, and matching
that as text raises ArgumentError, which is not a GitError, so it was
retried four times and then escaped as a backtrace. Match the bytes
instead. The rescue around the repoint was narrower than the comment
above it claimed, and the debug lines carried the whole command, which
still holds credentials that the redaction does not reach.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The glob returned nil when it matched nothing and the subprocess helper
skips chdir for a nil directory, so the assertion would have run git
against the rubygems checkout itself. Deriving the path the way the
source does makes a miss an outright failure, and reading the file keeps
the assertion from displacing the bundle command that `err` reports on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Dropping `commit.nil?` from the guard left every spec green, so nothing
stopped a source whose locked revision the remote had dropped from
silently repointing the shared cache at the default branch instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Git source fails to update when default branch is renamed

1 participant