Summary
When using PrepareFetch with both .with_shallow() and .with_ref_name() where the ref name is a tag (e.g., v1.26.0), the clone fails with:
None of the refspec(s) +refs/heads/v1.26.0:refs/remotes/origin/v1.26.0, HEAD:refs/remotes/origin/HEAD, v1.26.0, refs/tags/*:refs/tags/* matched any of the 22 refs on the remote
This was introduced in gix 0.82.0 (via gix-protocol 0.60.0) and persists in gix 0.83.0.
Root Cause
In gix/src/clone/fetch/mod.rs line 119, when use_single_branch_for_shallow is true and a ref_name is provided:
Some(Category::LocalBranch.to_full_name(ref_name.as_ref().as_bstr())?)
Category::LocalBranch unconditionally converts the ref name to refs/heads/<name>. When the ref is actually a tag (e.g., v1.26.0 → refs/tags/v1.26.0), the resulting refspec +refs/heads/v1.26.0:refs/remotes/origin/v1.26.0 doesn't match anything on the remote.
This code existed before 0.82.0, but the failure was masked because gix-protocol < 0.60.0 didn't validate that all explicit refspecs matched. The new is_missing_required_mapping() check in gix-protocol 0.60.0 now correctly rejects the unmatched refspec.
Reproduction
use gix::clone::PrepareFetch;
use gix::create;
use gix::create::Kind;
use gix::remote::fetch::Shallow;
use std::num::NonZeroU32;
let fetch = PrepareFetch::new(
"https://github.com/open-telemetry/semantic-conventions",
"/tmp/test-clone",
Kind::WithWorktree,
create::Options {
destination_must_be_empty: true,
fs_capabilities: None,
},
gix::open::Options::isolated(),
)
.unwrap()
.with_shallow(Shallow::DepthAtRemote(
NonZeroU32::new(1).unwrap(),
))
.with_ref_name(Some(&"v1.26.0".try_into().unwrap()))
.unwrap();
// This will fail with "None of the refspec(s) matched"
Expected Behavior
Shallow clone with a tag refspec should work the same as with a branch refspec. The code should detect whether the ref is a tag or branch (e.g., via DWIM resolution against the remote refs, similar to what extra_refspecs already does) rather than unconditionally assuming refs/heads/.
Workaround
Skip shallow clone when a specific refspec is provided:
let prepare = PrepareFetch::new(...).unwrap();
let mut fetch = if refspec.is_none() {
prepare.with_shallow(Shallow::DepthAtRemote(NonZeroU32::new(1).unwrap()))
} else {
prepare
}
.with_ref_name(refspec.as_ref())
.unwrap();
Affected Versions
- gix 0.82.0 (introduced)
- gix 0.83.0 (persists)
References
Summary
When using
PrepareFetchwith both.with_shallow()and.with_ref_name()where the ref name is a tag (e.g.,v1.26.0), the clone fails with:This was introduced in gix 0.82.0 (via
gix-protocol0.60.0) and persists in gix 0.83.0.Root Cause
In
gix/src/clone/fetch/mod.rsline 119, whenuse_single_branch_for_shallowis true and aref_nameis provided:Category::LocalBranchunconditionally converts the ref name torefs/heads/<name>. When the ref is actually a tag (e.g.,v1.26.0→refs/tags/v1.26.0), the resulting refspec+refs/heads/v1.26.0:refs/remotes/origin/v1.26.0doesn't match anything on the remote.This code existed before 0.82.0, but the failure was masked because
gix-protocol< 0.60.0 didn't validate that all explicit refspecs matched. The newis_missing_required_mapping()check ingix-protocol0.60.0 now correctly rejects the unmatched refspec.Reproduction
Expected Behavior
Shallow clone with a tag refspec should work the same as with a branch refspec. The code should detect whether the ref is a tag or branch (e.g., via DWIM resolution against the remote refs, similar to what
extra_refspecsalready does) rather than unconditionally assumingrefs/heads/.Workaround
Skip shallow clone when a specific refspec is provided:
Affected Versions
References