Files
beads/internal/storage/sqlite/migrations/016_orphan_detection.go
Steve Yegge 39e09449cc fix: orphan detection false positive with dots in directory name
Only treat issue IDs as hierarchical (parent.child) when the dot appears
AFTER the first hyphen. This prevents false positives when the project
directory name contains a dot (e.g., "my.project-abc123" was incorrectly
being treated as having parent "my").

Fixes GH#508

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 01:06:14 -08:00

67 lines
2.2 KiB
Go

package migrations
import (
"database/sql"
"fmt"
"log"
)
// MigrateOrphanDetection detects orphaned child issues and logs them for user action
// Orphaned children are issues with hierarchical IDs (e.g., "parent.child") where the
// parent issue no longer exists in the database.
//
// This migration does NOT automatically delete or convert orphans - it only logs them
// so the user can decide whether to:
// - Delete the orphans if they're no longer needed
// - Convert them to top-level issues by renaming them
// - Restore the missing parent issues
func MigrateOrphanDetection(db *sql.DB) error {
// Query for orphaned children:
// - ID contains a dot (potential hierarchical structure)
// - The part before the first dot must contain a hyphen (GH#508)
// This ensures the dot is in the hash portion (e.g., "bd-abc.1"), not in the prefix
// (e.g., "my.project-abc123" where "my.project" is the prefix from directory name)
// - Parent (part before first dot) doesn't exist in database
rows, err := db.Query(`
SELECT id
FROM issues
WHERE id LIKE '%.%'
AND instr(substr(id, 1, instr(id, '.') - 1), '-') > 0
AND substr(id, 1, instr(id || '.', '.') - 1) NOT IN (SELECT id FROM issues)
ORDER BY id
`)
if err != nil {
return fmt.Errorf("failed to query for orphaned children: %w", err)
}
defer rows.Close()
var orphans []string
for rows.Next() {
var id string
if err := rows.Scan(&id); err != nil {
return fmt.Errorf("failed to scan orphan ID: %w", err)
}
orphans = append(orphans, id)
}
if err := rows.Err(); err != nil {
return fmt.Errorf("error iterating orphan results: %w", err)
}
// Log results for user review
if len(orphans) > 0 {
log.Printf("⚠️ Orphan Detection: Found %d orphaned child issue(s):", len(orphans))
for _, id := range orphans {
log.Printf(" - %s", id)
}
log.Println("\nThese issues have hierarchical IDs but their parent issues no longer exist.")
log.Println("You can:")
log.Println(" 1. Delete them if no longer needed: bd delete <issue-id>")
log.Println(" 2. Convert to top-level issues by exporting and reimporting with new IDs")
log.Println(" 3. Restore the missing parent issues")
}
// Migration is idempotent - always succeeds since it's just detection/logging
return nil
}