Skip to content

Commit 8f8b3ad

Browse files
authored
feat(branch): sanitize branch name (#44)
1 parent 91f5c20 commit 8f8b3ad

File tree

6 files changed

+58
-33
lines changed

6 files changed

+58
-33
lines changed

l10n/bundle.l10n.ja.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
"Create Worktree": "ワークツリーを作成",
1616
"Create Worktree ({0})": "ワークツリーを作成 ({0})",
1717
"Choose a branch to create a new worktree from": "新しいワークツリーを作成するためのブランチを選択",
18+
"The new branch will be \"{0}\"": "新しいブランチは \"{0}\" になります",
19+
"Branch name needs to match regex: {0}": "ブランチ名は正規表現 {0} に一致する必要があります",
1820
"Checkout branch ( {0} )": "ブランチをチェックアウト ({0})",
1921
"Checkout branch ( {0} ) on {1}": "{1} でブランチ ({0}) をチェックアウト",
2022
"Checkout": "チェックアウト",

l10n/bundle.l10n.zh-cn.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
"Create Worktree": "创建 Worktree",
1616
"Create Worktree ({0})": "创建 Worktree ({0})",
1717
"Choose a branch to create a new worktree from": "选择用于创建 Worktree 的分支",
18+
"The new branch will be \"{0}\"": "新分支将为 \"{0}\"",
19+
"Branch name needs to match regex: {0}": "分支名称必须匹配正则表达式: {0}",
1820
"Checkout branch ( {0} )": "签出分支 ({0})",
1921
"Checkout branch ( {0} ) on {1}": "签出分支 ({0}) 于 {1}",
2022
"Checkout": "签出分支",

l10n/bundle.l10n.zh-tw.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
"Create Worktree": "建立 Worktree",
1616
"Create Worktree ({0})": "建立 Worktree ({0})",
1717
"Choose a branch to create a new worktree from": "選擇用於建立 Worktree 的分支",
18+
"The new branch will be \"{0}\"": "新分支會是 \"{0}\"",
19+
"Branch name needs to match regex: {0}": "分支名稱需要匹配 正規表達式: {0}",
1820
"Checkout branch ( {0} )": "切換分支 ({0})",
1921
"Checkout branch ( {0} ) on {1}": "在 {1} 切換分支 ({0})",
2022
"Checkout": "切換分支",

src/core/git/checkBranchNameValid.ts

Lines changed: 0 additions & 10 deletions
This file was deleted.

src/core/ui/inputNewBranch.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import * as vscode from 'vscode';
22
import { withResolvers } from '@/core/util/promise';
3-
import { validateBranchInput } from '@/core/util/branch';
4-
import { debounce } from 'lodash-es';
3+
import { validateBranchName } from '@/core/util/branch';
54

65
const backButton = vscode.QuickInputButtons.Back;
76

@@ -20,24 +19,22 @@ export const inputNewBranch = async (cwd: string, defaultValue?: string) => {
2019
inputBox.dispose();
2120
}
2221
});
23-
inputBox.onDidAccept(async () => {
24-
const errMsg = await validateBranchInput(cwd, inputBox.value);
25-
if (errMsg) {
26-
inputBox.validationMessage = errMsg;
22+
inputBox.onDidAccept(() => {
23+
const { sanitizedName, validationMessage } = validateBranchName(inputBox.value);
24+
if (validationMessage && validationMessage.severity > vscode.InputBoxValidationSeverity.Info) {
25+
inputBox.validationMessage = validationMessage;
2726
return;
2827
}
29-
resolve(inputBox.value);
28+
resolve(sanitizedName);
3029
inputBox.hide();
3130
});
3231
inputBox.onDidHide(() => {
3332
resolve(false);
3433
});
35-
inputBox.onDidChangeValue(
36-
debounce(async (value) => {
37-
const errMsg = await validateBranchInput(cwd, value);
38-
inputBox.validationMessage = errMsg;
39-
}, 300),
40-
);
34+
inputBox.onDidChangeValue((value) => {
35+
const { validationMessage } = validateBranchName(value);
36+
inputBox.validationMessage = validationMessage;
37+
});
4138
inputBox.show();
4239
return promise;
4340
};

src/core/util/branch.ts

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,45 @@
11
import * as vscode from 'vscode';
2-
import { checkBranchNameValid } from '@/core/git/checkBranchNameValid';
3-
4-
export const validateBranchInput = async (cwd: string, value: string) => {
5-
try {
6-
if (!value) return vscode.l10n.t('Branch name cannot be empty');
7-
const isValidBranchName = await checkBranchNameValid(cwd, value);
8-
if (!isValidBranchName) return vscode.l10n.t('Branch name is invalid');
9-
return '';
10-
} catch (error) {
11-
return String(error);
2+
3+
/**
4+
* Sanitize branch name similar to VSCode behavior. Removes or replaces invalid characters according to Git branch naming rules.
5+
* @see https://github.com/microsoft/vscode/blob/1.107.1/extensions/git/src/commands.ts#L629
6+
*/
7+
export const sanitizeBranchName = (name: string, branchWhitespaceChar = '-') => {
8+
if (!name) return name;
9+
10+
return name
11+
.trim()
12+
.replace(/^-+/, '')
13+
.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$|\[|\]$/g, branchWhitespaceChar);
14+
};
15+
16+
export const validateBranchName = (
17+
name: string,
18+
): { sanitizedName?: string; validationMessage?: vscode.InputBoxValidationMessage } => {
19+
const config = vscode.workspace.getConfiguration('git');
20+
const branchWhitespaceChar = config.get<string>('branchWhitespaceChar')!;
21+
const branchValidationRegex = config.get<string>('branchValidationRegex')!;
22+
const validateName = new RegExp(branchValidationRegex);
23+
const sanitizedName = sanitizeBranchName(name, branchWhitespaceChar);
24+
25+
if (validateName.test(sanitizedName)) {
26+
if (name === sanitizedName) {
27+
return { sanitizedName };
28+
}
29+
return {
30+
sanitizedName,
31+
validationMessage: {
32+
message: vscode.l10n.t('The new branch will be "{0}"', sanitizedName),
33+
severity: vscode.InputBoxValidationSeverity.Info,
34+
},
35+
};
1236
}
37+
38+
return {
39+
sanitizedName,
40+
validationMessage: {
41+
message: vscode.l10n.t('Branch name needs to match regex: {0}', branchValidationRegex),
42+
severity: vscode.InputBoxValidationSeverity.Error,
43+
},
44+
};
1345
};

0 commit comments

Comments
 (0)