fix(vcs): prefill the New Branch dialog with the current branch name (#1073)

A plain New Branch (single and multi-repository) now opens with the current branch name, like JetBrains IDEs, so a related branch is an edit away; detached HEAD stays empty. New Branch from a local ref now applies its <ref>-copy suggestion, which was computed but never used.

Co-authored-by: a-rookie-of-C-language <[email protected]>
Цей коміт міститься в:
arookieofcтаa-rookie-of-C-language authored and GitHub committed 2026-11-04 07:11:19 +08:00
1 джерело d72f8a1506
коміт cd77c9c6fb
2 files changed
+56 -3

No files matched your search

+46
Переглянути файл
@@ -0,0 +1,46 @@
import { beforeEach, expect, it, vi } from "vitest";
import type { BranchInfo } from "@/bindings";
const mocks = vi.hoisted(() => ({ prompt: vi.fn() }));
vi.mock("../dialogs", async (original) => ({
...(await original<typeof import("../dialogs")>()),
promptText: mocks.prompt,
}));
import { newBranch, newBranchInRepos } from "../branchOps";
import { useVcs } from "../store";
const branch = (head: string | null): BranchInfo => ({
head,
headCommit: "abc1234567890",
upstream: null,
ahead: 0,
behind: 0,
operation: null,
});
beforeEach(() => {
vi.clearAllMocks();
useVcs.setState({ root: "/p", repo: "/p/front", branch: branch("feature/ui") });
// Cancel: only what the dialog was offered matters here, not its outcome.
mocks.prompt.mockResolvedValue(null);
});
it("prefills a plain New Branch with the current branch name", async () => {
await newBranch();
expect(mocks.prompt.mock.calls[0]?.[0].initial).toBe("feature/ui");
});
it("prefills nothing when HEAD is detached", async () => {
useVcs.setState({ branch: branch(null) });
await newBranch();
expect(mocks.prompt.mock.calls[0]?.[0].initial).toBe("");
});
it("still suggests a remote branch's name without the prefix", async () => {
await newBranch("origin/main");
expect(mocks.prompt.mock.calls[0]?.[0].initial).toBe("main");
});
it("prefills the current branch in the multi-repository dialog too", async () => {
await newBranchInRepos(["/p/front", "/p/back"]);
expect(mocks.prompt.mock.calls[0]?.[0].initial).toBe("feature/ui");
});
+10 -3
Переглянути файл
@@ -145,11 +145,18 @@ export async function checkoutBranch(name: string) {
/** New Branch: creates a branch from HEAD (or `from`) and, by default, checks it out. */
export async function newBranch(from?: string) {
const repo = currentRepo();
const suggestion = from ? (from.includes("/") ? shortRemoteName(from) : `${from}-copy`) : "";
// IDEA opens the dialog with a suggestion instead of an empty field: a
// remote's name without the prefix, the source ref as `-copy`, or the
// current branch for a plain New Branch from HEAD.
const suggestion = from
? from.includes("/")
? shortRemoteName(from)
: `${from}-copy`
: (useVcs.getState().branch?.head ?? "");
const r = await promptText({
title: from ? tx`Create New Branch from ${from}` : "Create New Branch",
label: "New branch name:",
initial: from && from.includes("/") && !validateBranchName(suggestion) ? suggestion : "",
initial: suggestion && !validateBranchName(suggestion) ? suggestion : "",
mono: true,
okLabel: "Create",
validate: validateBranchName,
@@ -360,7 +367,7 @@ export async function newBranchInRepos(roots: readonly string[], from?: string)
const r = await promptText({
title: from ? tx`Create New Branch from ${from}` : "Create New Branch",
label: tx`New branch name (in ${roots.length} repositories):`,
initial: from?.includes("/") ? shortRemoteName(from) : "",
initial: from ? (from.includes("/") ? shortRemoteName(from) : "") : (useVcs.getState().branch?.head ?? ""),
mono: true,
okLabel: "Create",
validate: validateBranchName,