Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions gitcmds/gitcmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -1836,12 +1836,69 @@ func GetIssueNameByNumber(issueNum string, parentrepo string) (string, error) {
return issue.Title, nil
}

// normalizeBranchName normalizes a branch name to comply with Git branch naming rules.
// It replaces all invalid characters with dashes and ensures the name follows Git conventions.
// Rules applied:
// - Replace invalid characters (ASCII control chars, ~, ^, :, ?, [, *, spaces, ..) with dash
// - Remove leading/trailing dots, slashes, and dashes
// - Replace consecutive dashes with a single dash
// - Ensure lowercase for consistency
func normalizeBranchName(branchName string) string {
if branchName == "" {
return branchName
}

// Define characters that should be replaced with dash
invalidChars := []string{
" ", "~", "^", ":", "?", "[", "]", "*", "\\", "#", "!",
"\t", "\n", "\r", // whitespace characters
"\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", // ASCII control chars
"\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f",
"\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17",
"\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f",
"\x7f", // DEL character
}

// Replace invalid characters with dash
normalized := branchName
for _, char := range invalidChars {
normalized = strings.ReplaceAll(normalized, char, "-")
}

// Replace ".." (double dot) with single dash
normalized = strings.ReplaceAll(normalized, "..", "-")

// Remove leading dots
normalized = strings.TrimLeft(normalized, ".")

// Remove trailing slashes
normalized = strings.TrimRight(normalized, "/")

// Remove trailing dash
normalized = strings.TrimRight(normalized, "-")

// Remove leading and trailing dashes and underscores
normalized = strings.Trim(normalized, "-_")

// Replace consecutive dashes with a single dash
re := regexp.MustCompile(`-+`)
normalized = re.ReplaceAllString(normalized, "-")

return normalized
}

// CreateDevBranch creates dev branch and pushes it to origin
// Parameters:
// branch - branch name
// notes - notes for branch
// checkRemoteBranchExistence - if true, checks if a branch already exists in remote
func CreateDevBranch(wd, branchName string, notes []string, checkRemoteBranchExistence bool) error {
// Normalize branch name to comply with Git naming rules
branchName = normalizeBranchName(branchName)
if branchName == "" {
return errors.New("branch name is empty after normalization")
}

mainBranch, err := GetMainBranch(wd)
if err != nil {
return fmt.Errorf(errMsgFailedToGetMainBranch, err)
Expand Down
96 changes: 96 additions & 0 deletions gitcmds/gitcmds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,99 @@ func TestGetBody(t *testing.T) {
body = GetBodyFromNotes(notes)
require.Equal(t, "Resolves #324", body)
}

func TestNormalizeBranchName(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "simple branch name",
input: "feature-123",
expected: "feature-123",
},
{
name: "branch with spaces",
input: "feature 123 test",
expected: "feature-123-test",
},
{
name: "branch with invalid characters",
input: "feature~123^test:branch?name",
expected: "feature-123-test-branch-name",
},
{
name: "branch with double dots",
input: "feature..123",
expected: "feature-123",
},
{
name: "branch with leading dot",
input: ".feature-123",
expected: "feature-123",
},
{
name: "branch with trailing slash",
input: "feature-123/",
expected: "feature-123",
},
{
name: "branch with leading and trailing dashes",
input: "-feature-123-",
expected: "feature-123",
},
{
name: "branch with consecutive dashes",
input: "feature---123-",
expected: "feature-123",
},
{
name: "branch with uppercase",
input: "Feature-123-Test",
expected: "Feature-123-Test",
},
{
name: "branch with special characters",
input: "feature[123]*test",
expected: "feature-123-test",
},
{
name: "branch with backslash",
input: "feature\\123",
expected: "feature-123",
},
{
name: "branch with tabs and newlines",
input: "feature\t123\ntest",
expected: "feature-123-test",
},
{
name: "complex branch name",
input: "Fix: Issue #123 - Add new feature!",
expected: "Fix-Issue-123-Add-new-feature",
},
{
name: "branch with underscores at edges",
input: "_feature-123_",
expected: "feature-123",
},
{
name: "empty string",
input: "",
expected: "",
},
{
name: "only invalid characters",
input: "~~~^^^:::???",
expected: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := normalizeBranchName(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
Loading