feat(desktop): base-branch picker for new worktree dialog

The sidebar "New worktree" button branched off whatever HEAD you were
on — now a filterable Popover+Command combobox lets you pick any local
or remote-tracking branch as the base, defaulting to origin/HEAD.

Backend listBaseBranches() queries refs/heads + refs/remotes via
for-each-ref, flags origin/HEAD (falling back to the local default for
no-remote repos). Mirrored on the Python REST API side
(base_branch_list + /api/git/base-branches route).
This commit is contained in:
ethernet 2026-07-10 13:40:23 -04:00
parent 2627933f33
commit 6f7ee72be5
16 changed files with 390 additions and 19 deletions

View file

@ -644,3 +644,46 @@ def branch_switch(cwd: str, branch: str) -> dict:
raise RuntimeError("Branch name is required.")
_git_ok(cwd, ["switch", target])
return {"branch": target}
def base_branch_list(cwd: str) -> list[dict]:
"""Local heads + remote-tracking refs for the base-branch picker.
The remote default (origin/HEAD) is flagged so the UI can preselect it.
"""
out = _git_out(
cwd,
[
"for-each-ref",
"--format=%(refname:short)\t%(committerdate:iso)",
"--sort=-committerdate",
"refs/heads",
"refs/remotes",
],
)
if not out:
return []
remote_default = _git_out(
cwd, ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"]
).strip()
local_default = _default_branch(cwd) if not remote_default else ""
result: list[dict] = []
for line in out.split("\n"):
line = line.strip()
if not line:
continue
name = line.split("\t")[0]
result.append(
{
"name": name,
"isRemote": name.startswith("origin/"),
# origin/HEAD when a remote exists; otherwise the local
# default (main/master/init.defaultBranch) so a no-remote
# repo still flags its trunk.
"isDefault": bool(
(remote_default and name == remote_default)
or (not remote_default and local_default and name == local_default)
),
}
)
return result