feat: add Remote::default_branch() to learn the default branch of a remote without connecting to it - #3037
Open
Amey Pawar (ameyypawar) wants to merge 1 commit into
Conversation
… remote without connecting to it. `git clone` and `git remote set-head` record the default branch of a remote in the symbolic reference `refs/remotes/<name>/HEAD`, which points to the remote-tracking branch of that branch, like `refs/remotes/origin/main`. Since Git 2.48, `git fetch` also creates it if it's missing. `Remote::default_branch()` maps the target of that reference back to the branch on the remote with the fetch refspecs of the remote, and returns names like `refs/heads/main`. That's the same kind of name that `git2::Remote::default_branch()` returns, but `git2` asks the connected remote for its `HEAD`, while this works offline with what was recorded locally. It's an error if the fetch refspecs map more than one remote reference to the remote-tracking branch. Git would use the first matching refspec in configuration order, but the refspecs of a `Remote` are sorted when they are read from the configuration, so that order isn't known anymore. (see GitoxideLabs#2792)
Amey Pawar (ameyypawar)
force-pushed
the
gix-remote-default-branch
branch
from
October 4, 2026 13:46
63ee63e to
1deed76
Compare
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.
Created by Claude Code on behalf of Amey, who reviewed it before submitting. Everything below this line is the agent's writing, not his.
Summary
Add
Remote::default_branch(), as proposed in Add a Remote::default_branch() function #2792 (originally Add a Remote::default_branch() function #755). It returns the default branch of a remote, likerefs/heads/main, as recorded inrefs/remotes/<name>/HEADbygit clone,git remote set-headand, since Git 2.48,git fetch. It doesn't connect to the remote, which makes it the offline variant mentioned in Add a Remote::default_branch() function #755.The target of
refs/remotes/<name>/HEAD, likerefs/remotes/origin/main, is mapped back to the branch on the remote with the fetch refspecs of the remote, so refspecs that rename branches work too.Noneis returned in these cases:refs/remotes/<name>/HEADis missing or not symbolicIt's an error if more than one remote reference maps to it.
It returns the name on the remote, like
git2::Remote::default_branch(), which takes it from theHEADadvertised by a connected remote (git_remote__default_branch()). It carries agit2alias. The remote-tracking branch, whichgit symbolic-ref refs/remotes/origin/HEADprints, remains available as the target ofrefs/remotes/<name>/HEAD.Git baseline
Git 2.52.0 was run on the new
remote-default-branchfixture. The fixture is agit cloneofbaseplus remotes that changerefs/remotes/<name>/HEADor their fetch refspecs, and none of them is ever fetched.git branch --track probe refs/remotes/<name>/HEADshows Git's own reverse mapping inbranch.probe.merge, which comes fromremote_find_tracking()→refspec_find_match().refs/remotes/<name>/HEAD→branch.probe.mergedefault_branch()originrefs/remotes/origin/main+refs/heads/*:refs/remotes/origin/*refs/heads/mainrefs/heads/mainother-headrefs/remotes/other-head/a+refs/heads/*:refs/remotes/other-head/*refs/heads/arefs/heads/arenamedrefs/remotes/renamed/default+refs/heads/main:refs/remotes/renamed/defaultrefs/heads/mainrefs/heads/mainteam/originrefs/remotes/team/origin/main+refs/heads/*:refs/remotes/team/origin/*refs/heads/mainrefs/heads/mainunmappedrefs/remotes/unmapped/main+refs/heads/a:refs/remotes/unmapped/aNoneambiguousrefs/remotes/ambiguous/main+refs/heads/*:refs/remotes/ambiguous/*+refs/tags/*:refs/remotes/ambiguous/*refs/heads/mainThe fixture has three more remotes:
danglingpoints to a deleted remote-tracking branch.git branchrefuses it, whiledefault_branch()still maps the name torefs/heads/main, much likegit symbolic-refstill prints dangling targets.detached, whoseHEADisn't symbolic, returnsNone.no-headreturnsNone.For
origin, the test also checks the result againstgit ls-remote --symref origin HEAD, which printsref: refs/heads/main HEAD.The
ambiguousrow deviates from Git on purpose. Git uses the first matching refspec in configuration order, butRepository::try_find_remote()sorts and deduplicates refspecs, so that order is gone. With+refs/tags/*:…configured before+refs/heads/*:…, Git answersrefs/tags/main, while a first-match version of this method answeredrefs/heads/main. So ambiguity is an error, as inupstream_branch_and_remote_for_tracking_branch().Validation
cargo test -p gix --testspasses for every target, with 490 tests intests/gixincluding the 3 new ones.GIX_TEST_FIXTURE_HASH=sha256 cargo test -p gix --test gixpasses all 490 tests.cargo test -p gix --doc -- remotepasses all 6 doctests. They sharemake_remote_repos.sh, whose generated archive is git-ignored.cargo fmt --all -- --check,cargo clippy -p gix --all-targets(no findings in the changed files),cargo check -p gix --no-default-features --features sha1, andRUSTDOCFLAGS="-D warnings" cargo doc -p gix --no-deps --features blocking-network-clientall pass.default_branch()makes a new test fail:refs/remotes/<name>/instead of using the refspecsHEADby its own nameoriginfor anonymous remotesremote::Name::Symbol, even though names with a/are classified asremote::Name::UrlNot addressed here
gixclones storerefs/remotes/<remote>/HEADas a direct reference, whilegit clonemakes it symbolic.gix/tests/gix/clone.rsasserts that "remote HEAD is stored as the peeled object id advertised by the remote". Sodefault_branch()returnsNonein repositories cloned bygix, as its docs note. That stays true until clone writes the symbolic reference likegit clonedoes.gixdoesn't create or updaterefs/remotes/<name>/HEADon fetch.git fetchhas done that since Git 2.48, controlled byremote.<name>.followRemoteHEAD.Remote::refspecs()documents "order of occurrence in the configuration", andbranch_remote_tracking_ref_name()documents first-match in configuration order. Buttry_find_remote()sorts the refspecs.MatchGroup::match_rhs()drops only the mappings that a negative refspec excludes, while Git'srefspec_find_negative_match()fails the whole lookup. This already applies toupstream_branch_and_remote_for_tracking_branch().auto_hidden_revisions()ingix-tixderives the same thing withupstream_branch_and_remote_for_tracking_branch()and could use this method instead.Happy to follow up on any of these separately.