Implement multi-repo hydration layer with mtime caching (bd-307)
- Add repo_mtimes table to track JSONL file modification times - Implement HydrateFromMultiRepo() with mtime-based skip optimization - Support tilde expansion for repo paths in config - Add source_repo column via migration (not in base schema) - Fix schema to allow migration on existing databases - Comprehensive test coverage for hydration logic - Resurrect missing parent issues bd-cb64c226 and bd-cbed9619 Implementation: - internal/storage/sqlite/multirepo.go - Core hydration logic - internal/storage/sqlite/multirepo_test.go - Test coverage - docs/MULTI_REPO_HYDRATION.md - Documentation Schema changes: - source_repo column added via migration only (not base schema) - repo_mtimes table for mtime caching - All SELECT queries updated to include source_repo Database recovery: - Restored from 17 to 285 issues - Created placeholder parents for orphaned hierarchical children Amp-Thread-ID: https://ampcode.com/threads/T-faa1339a-14b2-426c-8e18-aa8be6f5cde6 Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -350,6 +350,56 @@ func TestMigrateExternalRefUnique(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestMigrateRepoMtimesTable(t *testing.T) {
|
||||
t.Run("creates repo_mtimes table if not exists", func(t *testing.T) {
|
||||
store, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
db := store.db
|
||||
|
||||
// Drop table if exists
|
||||
_, _ = db.Exec("DROP TABLE IF EXISTS repo_mtimes")
|
||||
|
||||
// Run migration
|
||||
if err := migrateRepoMtimesTable(db); err != nil {
|
||||
t.Fatalf("failed to migrate repo_mtimes table: %v", err)
|
||||
}
|
||||
|
||||
// Verify table exists
|
||||
var tableName string
|
||||
err := db.QueryRow(`
|
||||
SELECT name FROM sqlite_master
|
||||
WHERE type='table' AND name='repo_mtimes'
|
||||
`).Scan(&tableName)
|
||||
if err != nil {
|
||||
t.Fatalf("repo_mtimes table not found: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("is idempotent", func(t *testing.T) {
|
||||
store, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
db := store.db
|
||||
|
||||
// Run migration twice
|
||||
if err := migrateRepoMtimesTable(db); err != nil {
|
||||
t.Fatalf("first migration failed: %v", err)
|
||||
}
|
||||
if err := migrateRepoMtimesTable(db); err != nil {
|
||||
t.Fatalf("second migration failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify table still exists and is correct
|
||||
var tableName string
|
||||
err := db.QueryRow(`
|
||||
SELECT name FROM sqlite_master
|
||||
WHERE type='table' AND name='repo_mtimes'
|
||||
`).Scan(&tableName)
|
||||
if err != nil {
|
||||
t.Fatalf("repo_mtimes table not found after idempotent migration: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMigrateContentHashColumn(t *testing.T) {
|
||||
t.Run("adds content_hash column if missing", func(t *testing.T) {
|
||||
s, cleanup := setupTestDB(t)
|
||||
@@ -426,9 +476,10 @@ func TestMigrateContentHashColumn(t *testing.T) {
|
||||
compacted_at DATETIME,
|
||||
original_size INTEGER,
|
||||
compacted_at_commit TEXT,
|
||||
source_repo TEXT DEFAULT '.',
|
||||
CHECK ((status = 'closed') = (closed_at IS NOT NULL))
|
||||
);
|
||||
INSERT INTO issues SELECT id, title, description, design, acceptance_criteria, notes, status, priority, issue_type, assignee, estimated_minutes, created_at, updated_at, closed_at, external_ref, compaction_level, compacted_at, original_size, compacted_at_commit FROM issues_backup;
|
||||
INSERT INTO issues SELECT id, title, description, design, acceptance_criteria, notes, status, priority, issue_type, assignee, estimated_minutes, created_at, updated_at, closed_at, external_ref, compaction_level, compacted_at, original_size, compacted_at_commit, source_repo FROM issues_backup;
|
||||
DROP TABLE issues_backup;
|
||||
`)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user