Skip to content

fix: fetching and cloning with refspecs that are tags (in shallow clones)#2556

Merged
Sebastian Thiel (Byron) merged 3 commits into
mainfrom
shallow-clone-tag-refspecs
May 26, 2026
Merged

fix: fetching and cloning with refspecs that are tags (in shallow clones)#2556
Sebastian Thiel (Byron) merged 3 commits into
mainfrom
shallow-clone-tag-refspecs

Conversation

@Byron

@Byron Sebastian Thiel (Byron) commented Apr 29, 2026

Copy link
Copy Markdown
Member

Fixes #2554.

Tasks

  • refackiew

Summary

Fix shallow clones with an explicit tag ref by resolving the requested ref name against the remote before constructing the shallow single-ref refspec.

This addresses #2554, where PrepareFetch::with_shallow(...).with_ref_name(Some("tag")) incorrectly treated the requested name as a local branch and generated a refspec like:

+refs/heads/<tag>:refs/remotes/origin/<tag>

That refspec does not match tag-only refs and now fails once fetch refspec validation rejects unmatched required mappings.

Research

I checked baseline Git behavior using the local Git checkout at:

/Users/byron/dev/github.com/git/git

For a non-shallow clone with --branch <tag>, Git keeps the normal wildcard fetch refspec:

+refs/heads/*:refs/remotes/origin/*

For a shallow clone with --depth 1 --branch <tag>, Git writes a tag-to-tag refspec instead:

+refs/tags/<tag>:refs/tags/<tag>

The fix follows that behavior: shallow tag clones fetch the selected tag into refs/tags/*, while shallow branch clones continue to fetch into refs/remotes/<remote>/*.

Changes

  • Resolve with_ref_name() against the remote refs when using the shallow single-ref clone optimization.
  • Use the resolved full ref name when constructing the shallow refspec.
  • Preserve branch behavior by mapping refs/heads/<name> to refs/remotes/<remote>/<name>.
  • Map non-branch refs, including tags, to themselves.
  • Extend the annotated tag clone test to cover both non-shallow and shallow clones in one test.
  • Assert that the shallow tag clone stores a tag refspec of the form:
+refs/tags/<tag>:refs/tags/<tag>

…nes)

Fix shallow clone refspecs for explicit tag refs

When a shallow clone was created with `with_ref_name()`, the clone
setup treated the requested name as a branch and generated a refspec
under `refs/heads/`. For tag names this produced an unmatched required
mapping like `+refs/heads/<tag>:refs/remotes/origin/<tag>`.

Resolve the requested ref name against the remote before constructing
the shallow single-ref refspec. Branches continue to map to
`refs/remotes/<remote>/*`, while tags and other non-branch refs map to
themselves.

Baseline Git behavior was checked with `/Users/byron/dev/github.com/git/git`:
non-shallow `--branch <tag>` clones keep the normal branch wildcard
fetch refspec, while shallow `--depth 1 --branch <tag>` clones store
`+refs/tags/<tag>:refs/tags/<tag>`.

Co-authored-by: Sebastian Thiel <[email protected]>
@Byron
Sebastian Thiel (Byron) marked this pull request as ready for review May 26, 2026 05:44
Copilot AI review requested due to automatic review settings May 26, 2026 05:44
@Byron
Sebastian Thiel (Byron) force-pushed the shallow-clone-tag-refspecs branch from 844a7f4 to 92c8130 Compare May 26, 2026 05:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes shallow clones when PrepareFetch::with_shallow(...).with_ref_name(Some(<tag>)) is used by resolving the requested ref name against remote refs first, so the shallow single-ref refspec is constructed for the correct full ref (e.g. tags map to refs/tags/* instead of refs/heads/*). This aligns behavior with Git’s shallow --branch <tag> behavior and addresses #2554.

Changes:

  • Resolve with_ref_name() against the remote ref-map when using the shallow single-ref optimization, then use the resolved full ref name.
  • Adjust shallow refspec construction to map branches to refs/remotes/<remote>/<name> and non-branch refs (including tags) to themselves.
  • Extend the annotated tag clone test to assert both full and shallow clone behavior, including persisted refspecs for shallow tag clones.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
gix/src/clone/fetch/mod.rs Resolve shallow single-ref targets against remote refs and construct correct destination refspecs for tags vs branches.
gix/tests/gix/clone.rs Expand annotated-tag clone test to cover shallow vs non-shallow behavior and assert shallow tag refspec persistence.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread gix/src/clone/fetch/mod.rs
Comment thread gix/src/clone/fetch/mod.rs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 92c8130d6d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment thread gix/src/clone/fetch/mod.rs
Codex (codex) and others added 2 commits May 26, 2026 14:11
According to Codex, no priority system is needed here.

Co-authored-by: Sebastian Thiel <[email protected]>
The new preflight `ref_map_by_ref()` call will implicitly add the tag refspec
`refs/tags/*:refs/tags/*` based on the remote’s current `fetch_tags` setting,
which can cause `ls-refs` to enumerate *all* tags just to resolve a single
`ref_name`. This defeats the shallow single-ref optimization on repos with many
tags. Consider temporarily setting `remote.fetch_tags` to `Tags::None` around
this lookup (similar to the HEAD-lookup path below) so prefix filtering only
requests the DWIM-expanded candidates for the requested name, rather than all
tags.

Co-authored-by: Sebastian Thiel <[email protected]>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

@Byron
Sebastian Thiel (Byron) merged commit 3dd621c into main May 26, 2026
33 checks passed
@Byron
Sebastian Thiel (Byron) deleted the shallow-clone-tag-refspecs branch May 26, 2026 06:41
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.

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

3 participants