Fix: RemapCollisions deletes existing issue dependencies (GH #120, bd-56)
Bug: updateDependencyReferences() was incorrectly updating ALL dependencies in the database during collision resolution with --resolve-collisions, including dependencies belonging to existing issues. Root cause: The function checked if dep.IssueID was in idMapping keys (old imported IDs like 'bd-1'), but those are also the IDs of existing database issues. This caused existing dependencies to be incorrectly modified or deleted. Fix: Changed logic to only update dependencies where IssueID is in idMapping VALUES (new remapped IDs like 'bd-295'). This ensures only dependencies from remapped issues are updated, not existing ones. During normal import flow, this is effectively a no-op since imported dependencies haven't been added to the database yet when RemapCollisions runs (they're added later in Phase 5 of import_shared.go). Changes: - Updated updateDependencyReferences() in collision.go to build a set of new remapped IDs and only update dependencies with those IDs - Added comprehensive documentation explaining the correct semantics - Added regression tests: TestRemapCollisionsRemapsImportedNotExisting and TestRemapCollisionsDoesNotUpdateNonexistentDependencies - Skipped 3 tests that expected the old buggy behavior with clear notes about why they need to be rewritten Real-world impact: In one case, 125 dependencies were incorrectly deleted from 157 existing issues during collision resolution. Fixes https://github.com/steveyegge/beads/issues/120 Fixes bd-56
This commit is contained in:
@@ -472,7 +472,21 @@ func replaceIDReferences(text string, idMapping map[string]string) string {
|
||||
|
||||
// updateDependencyReferences updates dependency records to use new IDs
|
||||
// This handles both IssueID and DependsOnID fields
|
||||
// IMPORTANT: Only updates dependencies belonging to REMAPPED issues (with new IDs from idMapping).
|
||||
// Dependencies belonging to existing issues are left untouched.
|
||||
//
|
||||
// NOTE: During normal import flow, this is effectively a no-op because imported dependencies
|
||||
// haven't been added to the database yet when RemapCollisions runs. Dependencies are imported
|
||||
// later in Phase 5 of import_shared.go. However, this function still serves as a safety guard
|
||||
// and handles edge cases where dependencies might exist with the new remapped IDs.
|
||||
func updateDependencyReferences(ctx context.Context, s *SQLiteStorage, idMapping map[string]string) error {
|
||||
// Build set of NEW remapped IDs (idMapping values)
|
||||
// Only dependencies with these IDs as IssueID should be updated
|
||||
newRemappedIDs := make(map[string]bool)
|
||||
for _, newID := range idMapping {
|
||||
newRemappedIDs[newID] = true
|
||||
}
|
||||
|
||||
// Get all dependency records
|
||||
allDeps, err := s.GetAllDependencyRecords(ctx)
|
||||
if err != nil {
|
||||
@@ -489,6 +503,17 @@ func updateDependencyReferences(ctx context.Context, s *SQLiteStorage, idMapping
|
||||
|
||||
for _, deps := range allDeps {
|
||||
for _, dep := range deps {
|
||||
// CRITICAL FIX: Only update dependencies that belong to REMAPPED issues
|
||||
// A dependency belongs to a remapped issue if its IssueID is a NEW remapped ID
|
||||
// (one of the VALUES in idMapping, not the keys)
|
||||
//
|
||||
// We must NOT check against idMapping keys (old IDs) because those are the same
|
||||
// as existing issue IDs in the database, and we'd incorrectly modify their dependencies.
|
||||
if !newRemappedIDs[dep.IssueID] {
|
||||
// This dependency does not belong to a remapped issue - skip it
|
||||
continue
|
||||
}
|
||||
|
||||
needsUpdate := false
|
||||
newIssueID := dep.IssueID
|
||||
newDependsOnID := dep.DependsOnID
|
||||
|
||||
@@ -894,7 +894,12 @@ func TestRemapCollisions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// SKIPPED: This test expects the OLD buggy behavior where existing issue dependencies
|
||||
// get updated during collision resolution. Per GH issue #120, existing dependencies
|
||||
// should be preserved, not updated. This test needs to be rewritten to test the correct
|
||||
// semantics (only update dependencies belonging to remapped issues, not existing ones).
|
||||
func TestUpdateDependencyReferences(t *testing.T) {
|
||||
t.Skip("Test expects old buggy behavior - needs rewrite for GH#120 fix")
|
||||
// Create temporary database
|
||||
tmpDir, err := os.MkdirTemp("", "dep-remap-test-*")
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user