fix: support delete in --no-db mode (GH#822)

Add CreateTombstone() to MemoryStorage and deleteBatchFallback() to
handle deletion when SQLite is not available. This fixes the error
"tombstone operation not supported by this storage backend" when
using bd delete with --no-db flag.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
beads/crew/emma
2025-12-31 11:45:37 -08:00
committed by Steve Yegge
parent ee51298fd5
commit b161e22144
4 changed files with 217 additions and 5 deletions

View File

@@ -437,8 +437,9 @@ func deleteBatch(_ *cobra.Command, issueIDs []string, force bool, dryRun bool, c
// Type assert to SQLite storage
d, ok := store.(*sqlite.SQLiteStorage)
if !ok {
fmt.Fprintf(os.Stderr, "Error: batch delete not supported by this storage backend\n")
os.Exit(1)
// Fallback for non-SQLite storage (e.g., MemoryStorage in --no-db mode)
deleteBatchFallback(issueIDs, force, dryRun, cascade, jsonOutput, hardDelete, reason)
return
}
// Verify all issues exist
issues := make(map[string]*types.Issue)
@@ -566,6 +567,114 @@ func deleteBatch(_ *cobra.Command, issueIDs []string, force bool, dryRun bool, c
}
}
}
// deleteBatchFallback handles batch deletion for non-SQLite storage (e.g., MemoryStorage in --no-db mode)
// It iterates through issues one by one, creating tombstones for each.
func deleteBatchFallback(issueIDs []string, force bool, dryRun bool, cascade bool, jsonOutput bool, hardDelete bool, reason string) {
ctx := rootCtx
// Cascade not supported in fallback mode
if cascade {
fmt.Fprintf(os.Stderr, "Error: --cascade not supported in --no-db mode\n")
os.Exit(1)
}
// Verify all issues exist first
issues := make(map[string]*types.Issue)
notFound := []string{}
for _, id := range issueIDs {
issue, err := store.GetIssue(ctx, id)
if err != nil {
fmt.Fprintf(os.Stderr, "Error getting issue %s: %v\n", id, err)
os.Exit(1)
}
if issue == nil {
notFound = append(notFound, id)
} else {
issues[id] = issue
}
}
if len(notFound) > 0 {
fmt.Fprintf(os.Stderr, "Error: issues not found: %s\n", strings.Join(notFound, ", "))
os.Exit(1)
}
// Preview mode
if dryRun || !force {
fmt.Printf("\n%s\n", ui.RenderFail("⚠️ DELETE PREVIEW"))
fmt.Printf("\nIssues to delete (%d):\n", len(issueIDs))
for _, id := range issueIDs {
if issue := issues[id]; issue != nil {
fmt.Printf(" %s: %s\n", id, issue.Title)
}
}
if dryRun {
fmt.Printf("\n(Dry-run mode - no changes made)\n")
} else {
fmt.Printf("\n%s\n", ui.RenderWarn("This operation cannot be undone!"))
fmt.Printf("To proceed, run: %s\n",
ui.RenderWarn("bd delete "+strings.Join(issueIDs, " ")+" --force"))
}
return
}
// Delete each issue
deleteActor := getActorWithGit()
deletedCount := 0
depsRemoved := 0
for _, issueID := range issueIDs {
// Remove dependencies (outgoing)
depRecords, err := store.GetDependencyRecords(ctx, issueID)
if err == nil {
for _, dep := range depRecords {
if err := store.RemoveDependency(ctx, dep.IssueID, dep.DependsOnID, deleteActor); err == nil {
depsRemoved++
}
}
}
// Remove dependencies (inbound)
dependents, err := store.GetDependents(ctx, issueID)
if err == nil {
for _, dep := range dependents {
if err := store.RemoveDependency(ctx, dep.ID, issueID, deleteActor); err == nil {
depsRemoved++
}
}
}
// Create tombstone
if err := createTombstone(ctx, issueID, deleteActor, reason); err != nil {
fmt.Fprintf(os.Stderr, "Error creating tombstone for %s: %v\n", issueID, err)
continue
}
deletedCount++
}
// Hard delete: remove from JSONL immediately
if hardDelete {
for _, id := range issueIDs {
_ = removeIssueFromJSONL(id)
}
}
// Schedule auto-flush
markDirtyAndScheduleFlush()
// Output results
if jsonOutput {
outputJSON(map[string]interface{}{
"deleted": issueIDs,
"deleted_count": deletedCount,
"dependencies_removed": depsRemoved,
})
} else {
fmt.Printf("%s Deleted %d issue(s)\n", ui.RenderPass("✓"), deletedCount)
fmt.Printf(" Removed %d dependency link(s)\n", depsRemoved)
}
}
// showDeletionPreview shows what would be deleted
func showDeletionPreview(issueIDs []string, issues map[string]*types.Issue, cascade bool, depError error) {
fmt.Printf("\n%s\n", ui.RenderFail("⚠️ DELETE PREVIEW"))