Git: wrong upstream name when branch.<name>.merge is not under refs/heads/, Source Control Graph shows no history
- Dominant language
- TypeScript
- Stars
- 193k
- Forks
- 42.4k
- PR merge metrics
- PR metrics pending
Description
Does this issue occur when all extensions are disabled?: Yes (the cause is in the built-in git extension)
- VS Code Version: 1.137.0 (645f29cc3176500b4b5762ba887cf2a7f0ffdf2c)
- OS Version: Windows 11 Pro 10.0.26200
- Git: 2.55.0.windows.3
## Summary
When a branch's upstream is configured with `branch..merge` pointing to a ref that is **not** under `refs/heads/` on the remote side, the git extension builds a wrong upstream name. The Source Control Graph then passes this non-existing ref to `git log`, the command fails, and the Graph shows no history at all. Git itself resolves the upstream correctly.
This setup is valid git. It happens when the remote is a mirror or bundle that stores branches under `refs/remotes/...`, and the fetch refspec maps them, for example:
```
[remote "mirror"]
fetch = refs/remotes/upstream/*:refs/remotes/mirror/*
[branch "topic"]
remote = mirror
merge = refs/remotes/upstream/main
```
`git branch --set-upstream-to=mirror/main` writes exactly this `merge` value, because it reverse-maps the tracking ref through the refspec.
## Steps to reproduce
```powershell
git init -q -b main source
git -C source -c user.email=a@example.com -c user.name=a commit -q --allow-empty -m one
git -C source update-ref refs/remotes/upstream/main HEAD
git init -q -b main work
cd work
git remote add mirror ../source
git config remote.mirror.fetch 'refs/remotes/upstream/*:refs/remotes/mirror/*'
git fetch -q mirror
git checkout -q -b topic mirror/main
git branch -q --set-upstream-to=mirror/main topic
```
Open `work` in VS Code and open the Source Control Graph.
**Expected:** the Graph shows the commit, with `mirror/main` as the upstream.
**Actual:** the Graph shows no history.
Git resolves the upstream correctly:
```
> git rev-parse --abbrev-ref topic@{u}
mirror/main
> git for-each-ref --format='%(refname) | short=%(upstream:short) | remote=%(upstream:remotename) | remoteref=%(upstream:remoteref)' refs/heads/topic
refs/heads/topic | short=mirror/main | remote=mirror | remoteref=refs/remotes/upstream/main
```
The ref VS Code derives does not exist:
```
> git log -1 refs/remotes/mirror/s/upstream/main
fatal: ambiguous argument 'refs/remotes/mirror/s/upstream/main': unknown revision or path not in the working tree.
```
Git output log from my real repository (only the remote and branch names changed):
```
> git log --format=%H%n%aN%n%aE%n%at%n%ct%n%P%n%D%n%B -z --shortstat --diff-merges=first-parent -n50 --skip=0 --topo-order --decorate=full --stdin
fatal: bad revision 'refs/remotes/mirror/s/upstream/main'
[error] [GitHistoryProvider][provideHistoryItems] Failed to get history items with options '{"historyItemRefs":[...,"refs/remotes/mirror/s/upstream/main", ...
> git merge-base main mirror/s/upstream/main
fatal: Not a valid object name mirror/s/upstream/main
```
## Cause
In `extensions/git/src/git.ts` the upstream name is always taken as `upstreamRef.substring(11)`, which assumes the remote ref starts with `refs/heads/`:
https://github.com/microsoft/vscode/blob/7610bde817d8316d5098b1f7b1132fd8d79bbae8/extensions/git/src/git.ts#L3190-L3207
```ts
name: upstreamRef ? upstreamRef.substring(11) : upstream.substring(index + 1),
```
For `refs/remotes/upstream/main` this cuts off `refs/remote` and leaves `s/upstream/main`, so the upstream becomes `mirror/s/upstream/main`.
## Possible fix
Only strip the prefix when it is really `refs/heads/`, and otherwise use the short name from `%(upstream:short)`, which git has already mapped through the refspec:
```ts
name: upstreamRef?.startsWith('refs/heads/') ? upstreamRef.substring(11) : upstream.substring(index + 1),
```
With the example above this gives `main` on remote `mirror`, which is `refs/remotes/mirror/main`, the correct tracking ref.
Related, but a different trigger (`remote = .`): #261561
Contributor guide
Assessment
This issue has not been assessed yet.