This commit addresses critical code review findings from bd-dvd and bd-ymj fixes: ## Completed Tasks ### bd-ar2.1: Extract duplicated metadata update code - Created `updateExportMetadata()` helper function - Eliminated 22-line duplication between createExportFunc and createSyncFunc - Single source of truth for metadata updates ### bd-ar2.2: Add multi-repo support to export metadata updates - Added per-repo metadata key tracking with keySuffix parameter - Both export and sync functions now update metadata for all repos ### bd-ar2.3: Fix tests to use actual daemon functions - TestExportUpdatesMetadata now calls updateExportMetadata() directly - Added TestUpdateExportMetadataMultiRepo() for multi-repo testing - Fixed export_mtime_test.go tests to call updateExportMetadata() ### bd-ar2.9: Fix variable shadowing in GetNextChildID - Changed `err` to `resurrectErr` to avoid shadowing - Improves code clarity and passes linter checks ### bd-ar2.10: Fix hasJSONLChanged to support per-repo keys - Updated hasJSONLChanged() to accept keySuffix parameter - Reads metadata with correct per-repo keys - All callers updated (validatePreExport, daemon import, sync command) ### bd-ar2.11: Use stable repo identifiers instead of paths - Added getRepoKeyForPath() helper function - Uses stable identifiers like ".", "../frontend" instead of absolute paths - Metadata keys now portable across machines and clones - Prevents orphaned metadata when repos are moved ## Files Changed - cmd/bd/daemon_sync.go: Helper functions, metadata updates - cmd/bd/integrity.go: hasJSONLChanged() with keySuffix support - cmd/bd/sync.go: Updated to use getRepoKeyForPath() - cmd/bd/*_test.go: Tests updated for new signatures - internal/storage/sqlite/hash_ids.go: Fixed variable shadowing ## Testing All export, sync, and integrity tests pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
65 lines
2.1 KiB
Go
65 lines
2.1 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// getNextChildNumber atomically increments and returns the next child counter for a parent issue.
|
|
// Uses INSERT...ON CONFLICT to ensure atomicity without explicit locking.
|
|
func (s *SQLiteStorage) getNextChildNumber(ctx context.Context, parentID string) (int, error) {
|
|
var nextChild int
|
|
err := s.db.QueryRowContext(ctx, `
|
|
INSERT INTO child_counters (parent_id, last_child)
|
|
VALUES (?, 1)
|
|
ON CONFLICT(parent_id) DO UPDATE SET
|
|
last_child = last_child + 1
|
|
RETURNING last_child
|
|
`, parentID).Scan(&nextChild)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to generate next child number for parent %s: %w", parentID, err)
|
|
}
|
|
return nextChild, nil
|
|
}
|
|
|
|
// GetNextChildID generates the next hierarchical child ID for a given parent
|
|
// Returns formatted ID as parentID.{counter} (e.g., bd-a3f8e9.1 or bd-a3f8e9.1.5)
|
|
// Works at any depth (max 3 levels)
|
|
func (s *SQLiteStorage) GetNextChildID(ctx context.Context, parentID string) (string, error) {
|
|
// Validate parent exists
|
|
var count int
|
|
err := s.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM issues WHERE id = ?`, parentID).Scan(&count)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to check parent existence: %w", err)
|
|
}
|
|
if count == 0 {
|
|
// Try to resurrect parent from JSONL history before failing (bd-dvd fix)
|
|
resurrected, resurrectErr := s.TryResurrectParent(ctx, parentID)
|
|
if resurrectErr != nil {
|
|
return "", fmt.Errorf("failed to resurrect parent %s: %w", parentID, resurrectErr)
|
|
}
|
|
if !resurrected {
|
|
return "", fmt.Errorf("parent issue %s does not exist and could not be resurrected from JSONL history", parentID)
|
|
}
|
|
}
|
|
|
|
// Calculate current depth by counting dots
|
|
depth := strings.Count(parentID, ".")
|
|
if depth >= 3 {
|
|
return "", fmt.Errorf("maximum hierarchy depth (3) exceeded for parent %s", parentID)
|
|
}
|
|
|
|
// Get next child number atomically
|
|
nextNum, err := s.getNextChildNumber(ctx, parentID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Format as parentID.counter
|
|
childID := fmt.Sprintf("%s.%d", parentID, nextNum)
|
|
return childID, nil
|
|
}
|
|
|
|
// generateHashID moved to ids.go (bd-0702)
|