Skip to content

fix(git): the refname validator allows TAB but the ls-remote parsers split on TAB #798

Description

@BryanFRD

ensure_safe_refname_fragment (src/git/validate.rs:30) explicitly permits tabs, with a test pinning the behaviour:

#[test]
fn allows_tab() {
    // Tabs are technically allowed in refs (rare but not unsafe).
    assert!(ensure_safe_refname_fragment("foo\tbar", "tag").is_ok());
}

But every consumer of git ls-remote output parses it as tab-delimited:

  • parse_ls_remote_tags (src/git/push.rs:32): line.split_once('\t')
  • verify_remote_branch (src/git/push.rs:126): line.split_once('\t')

ls-remote emits <sha>\t<refname>. A refname containing a tab produces a line with two tabs, so split_once yields refname = "foo" and the rest is dropped. The tag is then absent from the returned map, or matched under a truncated name.

Concretely, for a tag like release\tcandidate (or any name a tagTemplate produces with a tab in a scope):

  • remote_tag_target_shas reports the tag as not present on the remote, so the "already at the right sha" fast path is skipped and a floating-tag force-push happens that shouldn't.
  • verify_remote_branch never matches expected_ref and fails with "Remote branch not found after push" even though the push succeeded.

Neither is dangerous — worst case is a redundant force-push or a false negative on verification — but it is a validator and a parser that disagree, and the validator has a test asserting the disagreement is intentional.

Fix

Pick one:

  • Reject tabs in the validator. Simplest, and defensible: git allows them, but they break the plumbing format that git itself uses to report refs, so nothing downstream can round-trip them. One-line change plus flipping allows_tab to rejects_tab.
  • Split on the first tab only and keep the remainder, i.e. treat everything after the first tab as the refname. split_once already does this — the bug is that strip_prefix("refs/tags/") then operates on the full remainder, which is actually correct. Worth re-checking: the real failure may be narrower than described, so confirm with a test before choosing.

Recommend the first. A tab in a release tag has no legitimate use and every other tool in the chain (GitHub's ref API, git for-each-ref --format) has the same problem.

Tests

  • Create a tag containing a tab, push it, and assert remote_tag_target_shas either returns it correctly or the validator rejected it upstream — whichever fix is chosen, the two must agree.
  • Same for a branch name in verify_remote_branch.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Low priority / somedaybugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions