fix: preserve # in branch names during frontend slugification#12322
fix: preserve # in branch names during frontend slugification#12322themavik wants to merge 1 commit intogitbutlerapp:masterfrom
# in branch names during frontend slugification#12322Conversation
The generic `slugify` function strips all characters outside `A-Za-z0-9._/ -`, which removes `#` from branch names like `feat/#1234-ticket-name`. However, `#` is a valid character in git ref names (confirmed by the backend's `normalize_short_name` tests). Introduce `slugifyBranchName` — a branch-name-aware variant that additionally allows `#` and `@` (both valid in git refs). Update `BranchNameTextbox.svelte` to use this instead of the generic `slugify`, which remains unchanged for non-branch contexts (e.g., organization slugs, markdown heading IDs). Closes #9076
|
@themavik is attempting to deploy a commit to the GitButler Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
Thanks a lot for fixing this!
The problem is indeed resolved.
Screen.Recording.2026-02-16.at.09.56.09.mov
What I wonder though is if the frontend shouldn't just use the backend function instead of implementing its own?
Could @estib-vega decide if this can be merged, and if it makes sense to have a follow-up that relies completely on the backend instead?
There was a problem hiding this comment.
Pull request overview
This pull request fixes issue #9076 where the # character is silently stripped from branch names during creation or renaming in the frontend. The root cause is that the generic slugify function uses a character allowlist that doesn't include #, even though it's valid in git ref names. The fix introduces a new slugifyBranchName function specifically for branch names that preserves git-valid characters like # and @.
Changes:
- Added
slugifyBranchNamefunction to preserve git-valid characters in branch names - Updated
BranchNameTextboxcomponent to use the new branch-aware slugification - Added comprehensive tests for the new function
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| packages/ui/src/lib/utils/string.ts | Adds new slugifyBranchName function with expanded character allowlist including # and @ |
| packages/ui/src/lib/utils/string.test.ts | Adds 7 tests covering the new function's behavior with git-valid and git-invalid characters |
| apps/desktop/src/components/BranchNameTextbox.svelte | Updates import and usage to use slugifyBranchName instead of generic slugify |
| .normalize('NFKD') | ||
| .replace(/[\u0300-\u036f]/g, '') | ||
| .trim() | ||
| .replace(/[^A-Za-z0-9._/#@ -]/g, '') |
There was a problem hiding this comment.
The character allowlist is more restrictive than the backend's normalize_short_name function. The backend preserves characters like +, <, >, and ! (see crates/but-core/tests/core/branch/normalize_short_name.rs lines 7, 11-12, 53), but this regex strips them. This creates inconsistency where users could type feature+bugfix in the UI, see it stripped to featurebugfix, but the backend would accept feature+bugfix as valid. Consider adding +<>! to the allowlist to match backend behavior.
| .replace(/[^A-Za-z0-9._/#@ -]/g, '') | |
| .replace(/[^A-Za-z0-9._/#@+<>! -]/g, '') |
| export function slugifyBranchName(input: string) { | ||
| return String(input) | ||
| .normalize('NFKD') | ||
| .replace(/[\u0300-\u036f]/g, '') | ||
| .trim() | ||
| .replace(/[^A-Za-z0-9._/#@ -]/g, '') | ||
| .replace(/\s+/g, '-') | ||
| .replace(/-+/g, '-'); | ||
| } |
There was a problem hiding this comment.
The backend's normalize_short_name strips -, ., and / from the beginning and end of branch names (see crates/but-core/src/branch/normalize.rs lines 9-25). The frontend doesn't implement this logic, which could lead to inconsistency. For example, -my-branch- would show as-is in the UI but be saved as my-branch by the backend. Consider adding similar logic to strip these characters from the start and end to match backend behavior and provide accurate preview to users.
| .normalize('NFKD') | ||
| .replace(/[\u0300-\u036f]/g, '') | ||
| .trim() | ||
| .replace(/[^A-Za-z0-9._/#@ -]/g, '') |
There was a problem hiding this comment.
The @ character is included in the allowlist, but there are no backend tests confirming that @ is preserved by normalize_short_name (see crates/but-core/tests/core/branch/normalize_short_name.rs). The @ character has special meaning in git refs (e.g., HEAD@{1}), so it's worth verifying that @ is actually safe to allow in branch names. Consider adding a backend test to confirm this behavior or removing @ from the allowlist if it's not supported.
| .replace(/[^A-Za-z0-9._/#@ -]/g, '') | |
| .replace(/[^A-Za-z0-9._/# -]/g, '') |
| test('at sign is preserved', () => { | ||
| expect(slugifyBranchName('user@feature')).toEqual('user@feature'); | ||
| }); | ||
|
|
There was a problem hiding this comment.
This test assumes @ is valid in git branch names, but there's no corresponding backend test confirming that normalize_short_name preserves @. The @ character has special meaning in git refs (e.g., HEAD@{1}), so verify this is actually supported by the backend before including it in tests. Consider adding a backend test first, or remove this test if @ is not supported.
| test('at sign is preserved', () => { | |
| expect(slugifyBranchName('user@feature')).toEqual('user@feature'); | |
| }); |
|
Maybe @mtsgrd could take a look then? |
|
Superseded by #12488 . |
Summary
Fixes #9076
Branch names containing
#(e.g.feat/#1234-ticket-name) have the#silently stripped when creating or renaming branches. This is because the frontend'sslugifyfunction uses a character allowlist that doesn't include#, even though#is a valid character in git ref names.The backend's
normalize_short_namecorrectly preserves#(confirmed by existing Rust tests:foo#branch→foo#branch). The issue is purely in the frontendBranchNameTextboxcomponent.Root Cause
packages/ui/src/lib/utils/string.ts:This
slugifyis also used for non-branch contexts (org slugs, markdown headings) where#should be stripped, so modifying it directly would break those contexts.Fix
slugifyBranchNameinpackages/ui/src/lib/utils/string.ts— a branch-name-aware variant that additionally allows#and@(both valid in git refs)BranchNameTextbox.sveltenow importsslugifyBranchNameinstead of the genericslugifyslugifyremains unchanged for non-branch contexts (OrganizationEditModal,Heading.svelte)Changes
packages/ui/src/lib/utils/string.ts— AddslugifyBranchNamefunctionpackages/ui/src/lib/utils/string.test.ts— Add 7 tests for the new functionapps/desktop/src/components/BranchNameTextbox.svelte— UseslugifyBranchNameTest Plan
#,@, slashes, periods, whitespace, git-invalid charsslugifytests unmodified and still pass#(Rust test:normalize_short_name("foo#branch") == "foo#branch")feat/#1234-ticketin branch name input → should show the#preserved