fix: resolve P2 sync noise and cleanup issues
- bd-6pni: Auto-filter tombstoned issues with mismatched prefixes during import instead of failing. Tombstones from contributor PRs with different test prefixes are pollution and safe to ignore. - bd-ffr9: Stop recreating deletions.jsonl after tombstone migration. Added IsTombstoneMigrationComplete() check to all code paths that write to the legacy deletions manifest. - bd-admx: Fix perpetual "JSONL file hash mismatch" warning. Now clears both export_hashes AND jsonl_file_hash when mismatch detected, so the warning doesn't repeat. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -180,7 +180,8 @@ func ImportIssues(ctx context.Context, dbPath string, store storage.Storage, iss
|
||||
}
|
||||
|
||||
// Check and handle prefix mismatches
|
||||
if err := handlePrefixMismatch(ctx, sqliteStore, issues, opts, result); err != nil {
|
||||
issues, err = handlePrefixMismatch(ctx, sqliteStore, issues, opts, result)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
@@ -258,48 +259,90 @@ func getOrCreateStore(ctx context.Context, dbPath string, store storage.Storage)
|
||||
return sqliteStore, true, nil
|
||||
}
|
||||
|
||||
// handlePrefixMismatch checks and handles prefix mismatches
|
||||
func handlePrefixMismatch(ctx context.Context, sqliteStore *sqlite.SQLiteStorage, issues []*types.Issue, opts Options, result *Result) error {
|
||||
// handlePrefixMismatch checks and handles prefix mismatches.
|
||||
// Returns a filtered issues slice with tombstoned issues having wrong prefixes removed (bd-6pni).
|
||||
func handlePrefixMismatch(ctx context.Context, sqliteStore *sqlite.SQLiteStorage, issues []*types.Issue, opts Options, result *Result) ([]*types.Issue, error) {
|
||||
configuredPrefix, err := sqliteStore.GetConfig(ctx, "issue_prefix")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get configured prefix: %w", err)
|
||||
return nil, fmt.Errorf("failed to get configured prefix: %w", err)
|
||||
}
|
||||
|
||||
// Only validate prefixes if a prefix is configured
|
||||
if strings.TrimSpace(configuredPrefix) == "" {
|
||||
if opts.RenameOnImport {
|
||||
return fmt.Errorf("cannot rename: issue_prefix not configured in database")
|
||||
return nil, fmt.Errorf("cannot rename: issue_prefix not configured in database")
|
||||
}
|
||||
return nil
|
||||
return issues, nil
|
||||
}
|
||||
|
||||
result.ExpectedPrefix = configuredPrefix
|
||||
|
||||
// Analyze prefixes in imported issues
|
||||
// Track tombstones separately - they don't count as "real" mismatches (bd-6pni)
|
||||
tombstoneMismatchPrefixes := make(map[string]int)
|
||||
nonTombstoneMismatchCount := 0
|
||||
|
||||
// Also track which tombstones have wrong prefixes for filtering
|
||||
var filteredIssues []*types.Issue
|
||||
var tombstonesToRemove []string
|
||||
|
||||
for _, issue := range issues {
|
||||
prefix := utils.ExtractIssuePrefix(issue.ID)
|
||||
if prefix != configuredPrefix {
|
||||
result.PrefixMismatch = true
|
||||
result.MismatchPrefixes[prefix]++
|
||||
if issue.IsTombstone() {
|
||||
tombstoneMismatchPrefixes[prefix]++
|
||||
tombstonesToRemove = append(tombstonesToRemove, issue.ID)
|
||||
// Don't add to filtered list - we'll remove these
|
||||
} else {
|
||||
result.PrefixMismatch = true
|
||||
result.MismatchPrefixes[prefix]++
|
||||
nonTombstoneMismatchCount++
|
||||
filteredIssues = append(filteredIssues, issue)
|
||||
}
|
||||
} else {
|
||||
// Correct prefix - keep the issue
|
||||
filteredIssues = append(filteredIssues, issue)
|
||||
}
|
||||
}
|
||||
|
||||
// If prefix mismatch detected and not handling it, return error or warning
|
||||
if result.PrefixMismatch && !opts.RenameOnImport && !opts.DryRun && !opts.SkipPrefixValidation {
|
||||
return fmt.Errorf("prefix mismatch detected: database uses '%s-' but found issues with prefixes: %v (use --rename-on-import to automatically fix)", configuredPrefix, GetPrefixList(result.MismatchPrefixes))
|
||||
// bd-6pni: If ALL mismatched prefix issues are tombstones, they're just pollution
|
||||
// from contributor PRs that used different test prefixes. These are safe to remove.
|
||||
if nonTombstoneMismatchCount == 0 && len(tombstoneMismatchPrefixes) > 0 {
|
||||
// Log that we're ignoring tombstoned mismatches
|
||||
var tombstonePrefixList []string
|
||||
for prefix, count := range tombstoneMismatchPrefixes {
|
||||
tombstonePrefixList = append(tombstonePrefixList, fmt.Sprintf("%s- (%d tombstones)", prefix, count))
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "Ignoring prefix mismatches (all are tombstones): %v\n", tombstonePrefixList)
|
||||
// Clear mismatch flags - no real issues to worry about
|
||||
result.PrefixMismatch = false
|
||||
result.MismatchPrefixes = make(map[string]int)
|
||||
// Return filtered list without the tombstones
|
||||
return filteredIssues, nil
|
||||
}
|
||||
|
||||
// If there are non-tombstone mismatches, we need to include all issues (tombstones too)
|
||||
// but still report the error for non-tombstones
|
||||
if result.PrefixMismatch {
|
||||
// If not handling the mismatch, return error
|
||||
if !opts.RenameOnImport && !opts.DryRun && !opts.SkipPrefixValidation {
|
||||
return nil, fmt.Errorf("prefix mismatch detected: database uses '%s-' but found issues with prefixes: %v (use --rename-on-import to automatically fix)", configuredPrefix, GetPrefixList(result.MismatchPrefixes))
|
||||
}
|
||||
}
|
||||
|
||||
// Handle rename-on-import if requested
|
||||
if result.PrefixMismatch && opts.RenameOnImport && !opts.DryRun {
|
||||
if err := RenameImportedIssuePrefixes(issues, configuredPrefix); err != nil {
|
||||
return fmt.Errorf("failed to rename prefixes: %w", err)
|
||||
return nil, fmt.Errorf("failed to rename prefixes: %w", err)
|
||||
}
|
||||
// After renaming, clear the mismatch flags since we fixed them
|
||||
result.PrefixMismatch = false
|
||||
result.MismatchPrefixes = make(map[string]int)
|
||||
return issues, nil
|
||||
}
|
||||
|
||||
return nil
|
||||
// Return original issues if no filtering needed
|
||||
return issues, nil
|
||||
}
|
||||
|
||||
// detectUpdates detects same-ID scenarios (which are updates with hash IDs, not collisions)
|
||||
@@ -1034,14 +1077,17 @@ func purgeDeletedIssues(ctx context.Context, sqliteStore *sqlite.SQLiteStorage,
|
||||
}
|
||||
|
||||
// Backfill the deletions manifest (self-healing)
|
||||
backfillRecord := deletions.DeletionRecord{
|
||||
ID: id,
|
||||
Timestamp: time.Now().UTC(),
|
||||
Actor: "git-history-backfill",
|
||||
Reason: "recovered from git history (pruned from manifest)",
|
||||
}
|
||||
if err := deletions.AppendDeletion(deletionsPath, backfillRecord); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: failed to backfill deletion record for %s: %v\n", id, err)
|
||||
// bd-ffr9: Skip writing to deletions.jsonl if tombstone migration is complete
|
||||
if !deletions.IsTombstoneMigrationComplete(beadsDir) {
|
||||
backfillRecord := deletions.DeletionRecord{
|
||||
ID: id,
|
||||
Timestamp: time.Now().UTC(),
|
||||
Actor: "git-history-backfill",
|
||||
Reason: "recovered from git history (pruned from manifest)",
|
||||
}
|
||||
if err := deletions.AppendDeletion(deletionsPath, backfillRecord); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: failed to backfill deletion record for %s: %v\n", id, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to tombstone (bd-dve)
|
||||
|
||||
Reference in New Issue
Block a user