fix(import): support hierarchical hash IDs in --rename-on-import

The isNumeric function was rejecting valid hierarchical hash IDs like
'6we.2' that contain dots for parent.child notation. This caused
`bd import --rename-on-import` to fail with "non-numeric suffix" errors.

Changes:
- Rename isNumeric to isValidIDSuffix for clarity
- Accept dots (.) in addition to alphanumeric for hierarchical IDs
- Update test cases to cover hierarchical ID formats
This commit is contained in:
Ryan Snodgrass
2025-12-16 00:12:10 -08:00
parent fa566a9700
commit c7b45a8a40
3 changed files with 895 additions and 246 deletions

View File

@@ -524,7 +524,7 @@ func TestIsBoundary(t *testing.T) {
}
}
func TestIsNumeric(t *testing.T) {
func TestIsValidIDSuffix(t *testing.T) {
tests := []struct {
s string
want bool
@@ -538,18 +538,24 @@ func TestIsNumeric(t *testing.T) {
{"09ea", true},
{"abc123", true},
{"zzz", true},
// Hierarchical suffixes (hash.number format)
{"6we.2", true},
{"abc.1", true},
{"abc.1.2", true},
{"abc.1.2.3", true},
{"1.5", true},
// Invalid suffixes
{"", false}, // Empty string now returns false
{"1.5", false}, // Non-base36 characters
{"A3F8", false}, // Uppercase not allowed
{"@#$!", false}, // Special characters not allowed
{"", false}, // Empty string
{"A3F8", false}, // Uppercase not allowed
{"@#$!", false}, // Special characters not allowed
{"abc-def", false}, // Hyphens not allowed in suffix
}
for _, tt := range tests {
t.Run(tt.s, func(t *testing.T) {
got := isNumeric(tt.s)
got := isValidIDSuffix(tt.s)
if got != tt.want {
t.Errorf("isNumeric(%q) = %v, want %v", tt.s, got, tt.want)
t.Errorf("isValidIDSuffix(%q) = %v, want %v", tt.s, got, tt.want)
}
})
}

View File

@@ -151,15 +151,15 @@ func RenameImportedIssuePrefixes(issues []*types.Issue, targetPrefix string) err
}
if oldPrefix != targetPrefix {
// Extract the numeric part
numPart := strings.TrimPrefix(issue.ID, oldPrefix+"-")
// Extract the suffix part (supports both numeric "123" and hash "abc1" and hierarchical "abc.1.2")
suffix := strings.TrimPrefix(issue.ID, oldPrefix+"-")
// Validate that the numeric part is actually numeric
if numPart == "" || !isNumeric(numPart) {
return fmt.Errorf("cannot rename issue %s: non-numeric suffix '%s'", issue.ID, numPart)
// Validate that the suffix is valid (alphanumeric + dots for hierarchy)
if suffix == "" || !isValidIDSuffix(suffix) {
return fmt.Errorf("cannot rename issue %s: invalid suffix '%s'", issue.ID, suffix)
}
newID := fmt.Sprintf("%s-%s", targetPrefix, numPart)
newID := fmt.Sprintf("%s-%s", targetPrefix, suffix)
idMapping[issue.ID] = newID
}
}
@@ -270,13 +270,15 @@ func isBoundary(c byte) bool {
return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' || c == '.' || c == '!' || c == '?' || c == ':' || c == ';' || c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}'
}
func isNumeric(s string) bool {
// isValidIDSuffix checks if a string is a valid issue ID suffix
// Accepts: digits (0-9), lowercase letters (a-z), and dots (.) for hierarchy
// Examples: "123", "abc1", "6we", "6we.2", "abc.1.2"
func isValidIDSuffix(s string) bool {
if len(s) == 0 {
return false
}
// Accept base36 characters (0-9, a-z) for hash-based suffixes
for _, c := range s {
if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z')) {
if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || c == '.') {
return false
}
}