bd-ckej: fix orphan skip count mismatch on fresh import (#572)

* bd-ckej: fix orphan skip count mismatch on fresh import

When OrphanSkip mode is used during import and a child issue's parent doesn't
exist, the issue ID was cleared to '' but then regenerated anyway in
GenerateBatchIssueIDs, causing it to be created in the database. This resulted
in a count mismatch: JSONL had 824 issues but only 823 were in the database (one
orphan was counted but not created).

Fix: Filter out orphaned issues with empty IDs before batch creation and track
them in result.Skipped so the count stays accurate.

* test: add TestImportOrphanSkip_CountMismatch for bd-ckej

Adds comprehensive test that verifies orphaned issues are properly skipped
during import when orphan_handling=OrphanSkip and parent doesn't exist.

Also improves the fix to pre-filter orphaned issues before batch creation,
ensuring they're not inserted then have IDs cleared (preventing count
mismatches).

---------

Co-authored-by: Amp <amp@example.com>
This commit is contained in:
matt wilkie
2025-12-15 22:17:32 -07:00
committed by GitHub
parent bb3ca0c51d
commit cd3b0e30be
2 changed files with 151 additions and 9 deletions

View File

@@ -671,24 +671,62 @@ func upsertIssues(ctx context.Context, sqliteStore *sqlite.SQLiteStorage, issues
}
}
// Filter out orphaned issues if orphan_handling is set to skip (bd-ckej)
// Pre-filter before batch creation to prevent orphans from being created then ID-cleared
if opts.OrphanHandling == sqlite.OrphanSkip {
var filteredNewIssues []*types.Issue
for _, issue := range newIssues {
// Check if this is a hierarchical child whose parent doesn't exist
if strings.Contains(issue.ID, ".") {
lastDot := strings.LastIndex(issue.ID, ".")
parentID := issue.ID[:lastDot]
// Check if parent exists in either existing DB issues or in newIssues batch
var parentExists bool
for _, dbIssue := range dbIssues {
if dbIssue.ID == parentID {
parentExists = true
break
}
}
if !parentExists {
for _, newIssue := range newIssues {
if newIssue.ID == parentID {
parentExists = true
break
}
}
}
if !parentExists {
// Skip this orphaned issue
result.Skipped++
continue
}
}
filteredNewIssues = append(filteredNewIssues, issue)
}
newIssues = filteredNewIssues
}
// Batch create all new issues
// Sort by hierarchy depth to ensure parents are created before children
if len(newIssues) > 0 {
sort.Slice(newIssues, func(i, j int) bool {
depthI := strings.Count(newIssues[i].ID, ".")
depthJ := strings.Count(newIssues[j].ID, ".")
depthI := strings.Count(newIssues[i].ID, ".")
depthJ := strings.Count(newIssues[j].ID, ".")
if depthI != depthJ {
return depthI < depthJ // Shallower first
}
return newIssues[i].ID < newIssues[j].ID // Stable sort
return depthI < depthJ // Shallower first
}
return newIssues[i].ID < newIssues[j].ID // Stable sort
})
// Create in batches by depth level (max depth 3)
for depth := 0; depth <= 3; depth++ {
var batchForDepth []*types.Issue
for _, issue := range newIssues {
if strings.Count(issue.ID, ".") == depth {
batchForDepth = append(batchForDepth, issue)
var batchForDepth []*types.Issue
for _, issue := range newIssues {
if strings.Count(issue.ID, ".") == depth {
batchForDepth = append(batchForDepth, issue)
}
}
if len(batchForDepth) > 0 {