What's wrong
Branch and tag names are read from git's %(refname:short) and %(upstream:short) (GitIntegration/Parsing/GitOutputFormats.cs, ForEachRefFormat and ForEachTagFormat). With core.warnAmbiguousRefs on, which is git's default, :short does not always strip the refs/heads/ or refs/tags/ prefix. If the shortest name would be ambiguous, git keeps part of the prefix:
$ git branch v1 && git tag v1
$ git for-each-ref --format='%(refname) %(refname:short)' refs/heads refs/tags
refs/heads/master master
refs/heads/v1 heads/v1
refs/tags/v1 tags/v1
(Verified with git 2.43.)
GitBranchParser takes Name from that field (fields[1]), and Upstream from fields[3]. GitTagParser takes Name from fields[0].
Failure scenario
In a repository with both a branch v1 and a tag v1 (a common "release branch plus release tag" pattern):
Branches() reports a branch named heads/v1, and Tags() reports a tag named tags/v1.
Status().Branch still says v1, so the current branch doesn't match its own Branches() entry.
- Passing the reported name back to git fails.
git branch --delete heads/v1 gives error: branch 'heads/v1' not found, and deleting tag tags/v1 fails the same way.
The same thing happens whenever a local branch is named like a remote-tracking ref. For example, a local branch origin/main makes the remote-tracking branch, and every branch's Upstream, come back as remotes/origin/main.
Suggested fix
Derive names from the full refname and don't rely on :short:
- Branches and tags:
%(refname:lstrip=2) (strips refs/heads/ or refs/tags/).
- Remote-tracking branches:
%(refname:lstrip=2) also gives origin/main for refs/remotes/origin/main. The parser already knows which kind it has from the full refname in field 0.
- Upstream: emit
%(upstream) and strip refs/heads/ or refs/remotes/ in the parser, or use %(upstream:lstrip=2).
Acceptance criteria
- A test repository with a branch and a tag both named
v1: Branches() contains v1 and Tags() contains v1, with no heads/ or tags/ prefix.
- A local branch named
origin/main alongside refs/remotes/origin/main: the upstream and the remote-tracking branch are both reported as origin/main.
What's wrong
Branch and tag names are read from git's
%(refname:short)and%(upstream:short)(GitIntegration/Parsing/GitOutputFormats.cs,ForEachRefFormatandForEachTagFormat). Withcore.warnAmbiguousRefson, which is git's default,:shortdoes not always strip therefs/heads/orrefs/tags/prefix. If the shortest name would be ambiguous, git keeps part of the prefix:(Verified with git 2.43.)
GitBranchParsertakesNamefrom that field (fields[1]), andUpstreamfromfields[3].GitTagParsertakesNamefromfields[0].Failure scenario
In a repository with both a branch
v1and a tagv1(a common "release branch plus release tag" pattern):Branches()reports a branch namedheads/v1, andTags()reports a tag namedtags/v1.Status().Branchstill saysv1, so the current branch doesn't match its ownBranches()entry.git branch --delete heads/v1giveserror: branch 'heads/v1' not found, and deleting tagtags/v1fails the same way.The same thing happens whenever a local branch is named like a remote-tracking ref. For example, a local branch
origin/mainmakes the remote-tracking branch, and every branch'sUpstream, come back asremotes/origin/main.Suggested fix
Derive names from the full refname and don't rely on
:short:%(refname:lstrip=2)(stripsrefs/heads/orrefs/tags/).%(refname:lstrip=2)also givesorigin/mainforrefs/remotes/origin/main. The parser already knows which kind it has from the full refname in field 0.%(upstream)and striprefs/heads/orrefs/remotes/in the parser, or use%(upstream:lstrip=2).Acceptance criteria
v1:Branches()containsv1andTags()containsv1, with noheads/ortags/prefix.origin/mainalongsiderefs/remotes/origin/main: the upstream and the remote-tracking branch are both reported asorigin/main.