fix(git): resolve the base branch from the repository instead of its name

Follow-up to #2629, which stopped the walkthrough from comparing against a
branch that does not exist. The same guessing, and the same near-misses in how
the answer was applied, were left elsewhere:

- The default branch travelled as `rootBranchHint`, whose documented meaning is
  "the branch the project root worktree is on". It gets its own option, because
  a parameter that means two things is one the next caller gets wrong.
- A candidate equal to the branch being compared is skipped. In a plain checkout
  the root hint *is* the current branch, so it won every time and produced a
  comparison with itself; the repository default now wins there.
- The Changes and pull-request surfaces read the default branch too. A pull
  request opened against a branch that does not exist is a worse failure than a
  walkthrough that will not generate.
- `hasResolvableBaseBranch` matched `origin/feature/main` for a base of `main`,
  passing the check and then failing the comparison it exists to prevent.
- `getRangeDiff` promoted only `origin/<base>`. A base carried by any other
  remote stayed a bare name, which git resolves against refs/heads and nowhere
  else, so it failed exactly as before.
- `getBranches` dropped every branch of a remote that did not answer, turning
  "we could not ask" into "these branches are gone" — offline, that silently
  removed comparisons that work fine against local remote-tracking refs.
- A remote with no `remote/HEAD` is asked once with `ls-remote --symref` rather
  than falling back to the guess this data exists to replace.

The `defaultBranches` contract was documented under the status response; it
belongs to the branches response, which now has a section of its own.
This commit is contained in:
Bohdan Triapitsyn
2026-08-04 22:41:09 +03:00
parent 9aa8a3e375
commit ce0e1cea27
9 changed files with 292 additions and 37 deletions
@@ -55,10 +55,17 @@ written against staged code never silently re-anchors onto an unstaged edit.
| `branch` | `branch` | `getRangeDiff` uses three-dot `base...head`, so work merged in from the base branch is excluded |
| `pr` | `pr:<number>` | GitHub returns the merge-base diff, matching the branch semantics |
For the current-branch source, the UI prefers the default branch of the current
branch's tracking remote (from its local `remote/HEAD` symbolic ref), then uses
the existing conventional-branch fallback. It does not offer the source when the
chosen base cannot be resolved locally or through a remote-tracking ref.
For the current-branch source, the UI takes the base from the default branch of
the current branch's tracking remote (`defaultBranches` in the branches
response), and only then falls back to the conventional names. It does not offer
the source at all when the chosen base exists neither locally nor on a remote —
a repository whose default is neither `main`, `master` nor `develop` used to be
handed `main...<head>`, which git rejects outright.
A base that exists only on a remote still works: `getRangeDiff` prefers
`origin/<base>` when it exists, and otherwise resolves the base through whichever
remote carries it, because a bare branch name git cannot find in `refs/heads`
fails the same way.
The panel offers the current branch's pull request on its own: it registers with
the shared GitHub PR status store (`useGitHubPrStatusStore`) rather than waiting