fix(orphan): handle prefixes with dots in orphan detection (GH#508)
The orphan detection was incorrectly flagging issues with dots in their
prefix (e.g., "my.project-abc123") as orphans because it was looking for
any dot in the ID, treating everything before the first dot as the
parent ID.
The fix:
- Add IsHierarchicalID() helper that correctly detects hierarchical IDs
by checking if the ID ends with .{digits} (e.g., "bd-abc.1")
- Update SQL query in orphan detection migration to use GLOB patterns
that only match IDs ending with numeric suffixes
- Update all Go code that checks for hierarchical IDs to use the new
helper function
Test cases added:
- Unit tests for IsHierarchicalID covering normal, dotted prefix, and
edge cases
- Integration test verifying dotted prefixes do not trigger false
positives
Fixes: #508
This commit is contained in:
@@ -73,6 +73,38 @@ func isValidHex(s string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsHierarchicalID checks if an issue ID is hierarchical (has a parent).
|
||||
// Hierarchical IDs have the format {parentID}.{N} where N is a numeric child suffix.
|
||||
// Returns true and the parent ID if hierarchical, false and empty string otherwise.
|
||||
//
|
||||
// This correctly handles prefixes that contain dots (e.g., "my.project-abc123"
|
||||
// is NOT hierarchical, but "my.project-abc123.1" IS hierarchical with parent
|
||||
// "my.project-abc123").
|
||||
//
|
||||
// The key insight is that hierarchical IDs always end with .{digits} where
|
||||
// the digits represent the child number (1, 2, 3, etc.).
|
||||
func IsHierarchicalID(id string) (isHierarchical bool, parentID string) {
|
||||
lastDot := strings.LastIndex(id, ".")
|
||||
if lastDot == -1 {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// Check if the suffix after the last dot is purely numeric
|
||||
suffix := id[lastDot+1:]
|
||||
if len(suffix) == 0 {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
for _, c := range suffix {
|
||||
if c < '0' || c > '9' {
|
||||
return false, ""
|
||||
}
|
||||
}
|
||||
|
||||
// It's hierarchical - parent is everything before the last dot
|
||||
return true, id[:lastDot]
|
||||
}
|
||||
|
||||
// ValidateIssueIDPrefix validates that an issue ID matches the configured prefix
|
||||
// Supports both top-level (bd-a3f8e9) and hierarchical (bd-a3f8e9.1) IDs
|
||||
func ValidateIssueIDPrefix(id, prefix string) error {
|
||||
@@ -203,11 +235,8 @@ func EnsureIDs(ctx context.Context, conn *sql.Conn, prefix string, issues []*typ
|
||||
}
|
||||
|
||||
// For hierarchical IDs (bd-a3f8e9.1), ensure parent exists
|
||||
if strings.Contains(issues[i].ID, ".") {
|
||||
// Extract parent ID (everything before the last dot)
|
||||
lastDot := strings.LastIndex(issues[i].ID, ".")
|
||||
parentID := issues[i].ID[:lastDot]
|
||||
|
||||
// Use IsHierarchicalID to correctly handle prefixes with dots (GH#508)
|
||||
if isHierarchical, parentID := IsHierarchicalID(issues[i].ID); isHierarchical {
|
||||
var parentCount int
|
||||
err := conn.QueryRowContext(ctx, `SELECT COUNT(*) FROM issues WHERE id = ?`, parentID).Scan(&parentCount)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user