Skip to content

fix: preserve # in branch names during frontend slugification#12322

Closed
themavik wants to merge 1 commit intogitbutlerapp:masterfrom
themavik:fix/branch-name-hashtag
Closed

fix: preserve # in branch names during frontend slugification#12322
themavik wants to merge 1 commit intogitbutlerapp:masterfrom
themavik:fix/branch-name-hashtag

Conversation

@themavik
Copy link
Contributor

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's slugify function uses a character allowlist that doesn't include #, even though # is a valid character in git ref names.

The backend's normalize_short_name correctly preserves # (confirmed by existing Rust tests: foo#branchfoo#branch). The issue is purely in the frontend BranchNameTextbox component.

Root Cause

packages/ui/src/lib/utils/string.ts:

// This regex strips # because it's not in the allowed set
.replace(/[^A-Za-z0-9._/ -]/g, '')

This slugify is also used for non-branch contexts (org slugs, markdown headings) where # should be stripped, so modifying it directly would break those contexts.

Fix

  1. New function slugifyBranchName in packages/ui/src/lib/utils/string.ts — a branch-name-aware variant that additionally allows # and @ (both valid in git refs)
  2. BranchNameTextbox.svelte now imports slugifyBranchName instead of the generic slugify
  3. The generic slugify remains unchanged for non-branch contexts (OrganizationEditModal, Heading.svelte)

Changes

  • packages/ui/src/lib/utils/string.ts — Add slugifyBranchName function
  • packages/ui/src/lib/utils/string.test.ts — Add 7 tests for the new function
  • apps/desktop/src/components/BranchNameTextbox.svelte — Use slugifyBranchName

Test Plan

  • New vitest tests cover #, @, slashes, periods, whitespace, git-invalid chars
  • Existing slugify tests unmodified and still pass
  • Backend already preserves # (Rust test: normalize_short_name("foo#branch") == "foo#branch")
  • Manual: type feat/#1234-ticket in branch name input → should show the # preserved

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
@vercel
Copy link

vercel bot commented Feb 11, 2026

@themavik is attempting to deploy a commit to the GitButler Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Collaborator

@Byron Byron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

@Byron Byron added the 🎉reproduced🎉 The issue could be reproduced by following the instructions label Feb 16, 2026
@Byron Byron requested a review from Copilot February 16, 2026 08:58
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 slugifyBranchName function to preserve git-valid characters in branch names
  • Updated BranchNameTextbox component 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, '')
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
.replace(/[^A-Za-z0-9._/#@ -]/g, '')
.replace(/[^A-Za-z0-9._/#@+<>! -]/g, '')

Copilot uses AI. Check for mistakes.
Comment on lines +54 to +62
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, '-');
}
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
.normalize('NFKD')
.replace(/[\u0300-\u036f]/g, '')
.trim()
.replace(/[^A-Za-z0-9._/#@ -]/g, '')
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
.replace(/[^A-Za-z0-9._/#@ -]/g, '')
.replace(/[^A-Za-z0-9._/# -]/g, '')

Copilot uses AI. Check for mistakes.
Comment on lines +67 to +70
test('at sign is preserved', () => {
expect(slugifyBranchName('user@feature')).toEqual('user@feature');
});

Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
test('at sign is preserved', () => {
expect(slugifyBranchName('user@feature')).toEqual('user@feature');
});

Copilot uses AI. Check for mistakes.
@Byron Byron requested a review from estib-vega February 16, 2026 09:12
@Byron
Copy link
Collaborator

Byron commented Feb 18, 2026

Maybe @mtsgrd could take a look then?

@Byron
Copy link
Collaborator

Byron commented Feb 22, 2026

Superseded by #12488 .

@Byron Byron closed this Feb 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🎉reproduced🎉 The issue could be reproduced by following the instructions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Removal of hashtag in branch name

3 participants