Skip to content

Shallow clone with tag refspec fails: Category::LocalBranch assumes ref is a branch #2554

@daviddahl

Description

@daviddahl

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.0refs/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

Metadata

Metadata

Assignees

No one assigned

    Labels

    acknowledgedan issue is accepted as shortcoming to be fixed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions