feat(vcs): replace spaces typed in a branch name with a separator (#1571)

Ref-name inputs only validated after the fact, so a space typed or pasted into a branch name showed "Branch name contains invalid characters" and disabled Create. Branch-name fields (New Branch, from a revision and in several repositories, Rename Branch, New Worktree, GitHub New Remote Branch) now clean the name while typing: forbidden characters are dropped and whitespace becomes '-', without stacking separators, keeping the caret in place. Shared via src/platform/gitRefName.ts and the useSanitizedField hook in src/ui.

Closes #1553

Co-authored-by: jaw52 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Цей коміт міститься в:
JawтаClaude Opus 5.5 authored and GitHub committed 2026-11-05 23:09:55 +08:00
1 джерело c179b1a822
коміт d1acf9f9ac
12 files changed
+310 -8

No files matched your search

+19
Переглянути файл
@@ -0,0 +1,19 @@
import { act, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { expect, it } from "vitest";
import { GithubDialogHost } from "../ui/dialogs";
import { remoteBranchNameDialog } from "./RemoteBranchDialog";
it("turns spaces typed in the remote branch name into separators", async () => {
const user = userEvent.setup();
render(<GithubDialogHost />);
let result: Promise<string | null> | undefined;
act(() => {
result = remoteBranchNameDialog("");
});
const input = await screen.findByRole("textbox", { name: "Remote branch name" });
await user.type(input, "my feature");
expect(input).toHaveValue("my-feature");
await user.click(screen.getByRole("button", { name: "OK" }));
await expect(result).resolves.toBe("my-feature");
});
+10 -2
Переглянути файл
@@ -1,6 +1,7 @@
/** "New Remote Branch for Pull Request": the name to push a local head without a push target to. */
import { useState } from "react";
import { Dialog, Input, T } from "@/ui";
import { cleanUpBranchNameOnTyping } from "@/platform/gitRefName";
import { Dialog, Input, T, useSanitizedField } from "@/ui";
import { openDialog } from "../ui/dialogs";
import styles from "./Create.module.css";
@@ -11,6 +12,7 @@ export function remoteBranchNameDialog(suggested: string): Promise<string | null
function RemoteBranchDialog(props: { suggested: string; onDone: (name: string | null) => void }) {
const [name, setName] = useState(props.suggested);
const { ref, clean } = useSanitizedField(cleanUpBranchNameOnTyping);
const valid = !!name.trim();
return (
<Dialog
@@ -33,7 +35,13 @@ function RemoteBranchDialog(props: { suggested: string; onDone: (name: string |
<label className={styles.dialogLabel}>
<T>Remote branch name:</T>
</label>
<Input data-autofocus aria-label="Remote branch name" value={name} onChange={(e) => setName(e.target.value)} />
<Input
ref={ref}
data-autofocus
aria-label="Remote branch name"
value={name}
onChange={(e) => setName(clean(e))}
/>
<div className={styles.dialogComment}>
<T>Your changes will be pushed to the remote repository to create the pull request</T>
</div>