Remove sequential ID generation and SyncAllCounters (bd-c7af, bd-8e05, bd-4c74)
- Removed SyncAllCounters() and all call sites (already no-op with hash IDs) - Removed AllocateNextID() and getNextIDForPrefix() - sequential ID generation - Removed collision remapping logic in internal/storage/sqlite/collision.go - Removed rename collision handling in internal/importer/importer.go - Removed branch-merge example (collision resolution no longer needed) - Updated EXTENDING.md to remove counter sync examples These were all deprecated code paths for sequential IDs that are obsolete with hash-based IDs. Hash ID collisions are handled by extending the hash, not by remapping to new sequential IDs.
This commit is contained in:
10
EXTENDING.md
10
EXTENDING.md
@@ -631,10 +631,7 @@ if err := store.CreateIssues(ctx, issues, "import"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// If you used explicit IDs, sync counters to prevent collisions
|
||||
if err := store.SyncAllCounters(ctx); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// REMOVED (bd-c7af): SyncAllCounters - no longer needed with hash IDs
|
||||
```
|
||||
|
||||
### Performance Comparison
|
||||
@@ -695,10 +692,7 @@ func ImportFromExternal(externalIssues []ExternalIssue) error {
|
||||
return fmt.Errorf("batch create failed: %w", err)
|
||||
}
|
||||
|
||||
// Sync counters since we used explicit IDs
|
||||
if err := store.SyncAllCounters(ctx); err != nil {
|
||||
return fmt.Errorf("counter sync failed: %w", err)
|
||||
}
|
||||
// REMOVED (bd-c7af): SyncAllCounters - no longer needed with hash IDs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -711,10 +711,7 @@ func TestImportCounterSyncAfterHighID(t *testing.T) {
|
||||
t.Fatalf("Failed to import high ID issue: %v", err)
|
||||
}
|
||||
|
||||
// Step 4: Sync counters after import (mimics import command behavior)
|
||||
if err := testStore.SyncAllCounters(ctx); err != nil {
|
||||
t.Fatalf("Failed to sync counters: %v", err)
|
||||
}
|
||||
// REMOVED (bd-c7af): Counter sync - no longer needed with hash IDs
|
||||
|
||||
// Step 5: Create another auto-generated issue
|
||||
// This should get bd-101 (counter should have synced to 100), not bd-4
|
||||
|
||||
@@ -151,11 +151,7 @@ func profileImportOperation(t *testing.T, numIssues int) {
|
||||
phases["create_update"] = time.Since(createStart)
|
||||
|
||||
// Phase 4: Sync counters
|
||||
syncStart := time.Now()
|
||||
if err := sqliteStore.SyncAllCounters(ctx); err != nil {
|
||||
t.Fatalf("Failed to sync counters: %v", err)
|
||||
}
|
||||
phases["sync_counters"] = time.Since(syncStart)
|
||||
// REMOVED (bd-c7af): Counter sync - no longer needed with hash IDs
|
||||
|
||||
totalDuration := time.Since(startTime)
|
||||
|
||||
|
||||
@@ -290,17 +290,7 @@ func renumberIssuesInDB(ctx context.Context, prefix string, idMapping map[string
|
||||
}
|
||||
|
||||
// Update the counter to the highest renumbered ID so next issue gets correct number
|
||||
// After renumbering to bd-1..bd-N, set counter to N so next issue is bd-(N+1)
|
||||
// We need to FORCE set it (not MAX) because counter may be higher from deleted issues
|
||||
// Strategy: Reset (delete) the counter row, then SyncAllCounters recreates it from actual max ID
|
||||
sqliteStore, _ := store.(*sqlite.SQLiteStorage)
|
||||
if err := sqliteStore.ResetCounter(ctx, prefix); err != nil {
|
||||
return fmt.Errorf("failed to reset counter: %w", err)
|
||||
}
|
||||
// Now sync will recreate it from the actual max ID in the database
|
||||
if err := sqliteStore.SyncAllCounters(ctx); err != nil {
|
||||
return fmt.Errorf("failed to sync counter: %w", err)
|
||||
}
|
||||
// REMOVED (bd-c7af): Counter sync after renumbering - no longer needed with hash IDs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ This directory contains examples of how to integrate bd with AI agents and workf
|
||||
- **[markdown-to-jsonl/](markdown-to-jsonl/)** - Convert markdown planning docs to bd issues
|
||||
- **[github-import/](github-import/)** - Import issues from GitHub repositories
|
||||
- **[git-hooks/](git-hooks/)** - Pre-configured git hooks for automatic export/import
|
||||
- **[branch-merge/](branch-merge/)** - Branch merge workflow with collision resolution
|
||||
<!-- REMOVED (bd-4c74): branch-merge example - collision resolution no longer needed with hash IDs -->
|
||||
- **[claude-desktop-mcp/](claude-desktop-mcp/)** - MCP server for Claude Desktop integration
|
||||
- **[claude-code-skill/](claude-code-skill/)** - Claude Code skill for effective beads usage patterns
|
||||
|
||||
@@ -28,9 +28,7 @@ cd bash-agent
|
||||
cd git-hooks
|
||||
./install.sh
|
||||
|
||||
# Try branch merge collision resolution
|
||||
cd branch-merge
|
||||
./demo.sh
|
||||
# REMOVED (bd-4c74): branch-merge demo - hash IDs eliminate collision resolution
|
||||
```
|
||||
|
||||
## Creating Your Own Agent
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
# Branch Merge Workflow with Collision Resolution
|
||||
|
||||
This example demonstrates how to handle ID collisions when merging branches that have diverged and created issues with the same IDs.
|
||||
|
||||
## The Problem
|
||||
|
||||
When two branches work independently and both create issues, they'll often generate overlapping IDs:
|
||||
|
||||
```
|
||||
main: bd-1, bd-2, bd-3, bd-4, bd-5
|
||||
feature: bd-1, bd-2, bd-3, bd-6, bd-7 (diverged from main earlier)
|
||||
```
|
||||
|
||||
When you try to merge `feature` into `main`, you'll have ID collisions for bd-1 through bd-3 (if the content differs).
|
||||
|
||||
## The Solution
|
||||
|
||||
bd provides automatic collision resolution that:
|
||||
1. Detects collisions (same ID, different content)
|
||||
2. Renumbers the incoming colliding issues
|
||||
3. Updates ALL text references and dependencies automatically
|
||||
|
||||
## Demo Workflow
|
||||
|
||||
### 1. Setup - Two Diverged Branches
|
||||
|
||||
```bash
|
||||
# Start on main branch
|
||||
git checkout main
|
||||
bd create "Feature A" -t feature -p 1
|
||||
bd create "Bug fix B" -t bug -p 0
|
||||
bd create "Task C" -t task -p 2
|
||||
bd export -o .beads/issues.jsonl
|
||||
git add .beads/issues.jsonl
|
||||
git commit -m "Add main branch issues"
|
||||
|
||||
# Create feature branch from an earlier commit
|
||||
git checkout -b feature-branch HEAD~5
|
||||
|
||||
# On feature branch, create overlapping issues
|
||||
bd create "Different feature A" -t feature -p 2
|
||||
bd create "Different bug B" -t bug -p 1
|
||||
bd create "Feature D" -t feature -p 1
|
||||
bd export -o .beads/issues.jsonl
|
||||
git add .beads/issues.jsonl
|
||||
git commit -m "Add feature branch issues"
|
||||
```
|
||||
|
||||
At this point:
|
||||
- `main` has: bd-1 (Feature A), bd-2 (Bug fix B), bd-3 (Task C)
|
||||
- `feature-branch` has: bd-1 (Different feature A), bd-2 (Different bug B), bd-3 (Feature D)
|
||||
|
||||
The bd-1 and bd-2 on each branch have different content = collisions!
|
||||
|
||||
### 2. Merge and Detect Collisions
|
||||
|
||||
```bash
|
||||
# Merge feature branch into main
|
||||
git checkout main
|
||||
git merge feature-branch
|
||||
|
||||
# Git will show merge conflict in .beads/issues.jsonl
|
||||
# Manually resolve the conflict by keeping both versions
|
||||
# (or use a merge tool)
|
||||
|
||||
# After resolving the git conflict, check for ID collisions
|
||||
bd import -i .beads/issues.jsonl --dry-run
|
||||
```
|
||||
|
||||
Output shows:
|
||||
```
|
||||
=== Collision Detection Report ===
|
||||
Exact matches (idempotent): 0
|
||||
New issues: 1
|
||||
COLLISIONS DETECTED: 2
|
||||
|
||||
Colliding issues:
|
||||
bd-1: Different feature A
|
||||
Conflicting fields: [title, priority]
|
||||
bd-2: Different bug B
|
||||
Conflicting fields: [title, priority]
|
||||
```
|
||||
|
||||
### 3. Resolve Collisions Automatically
|
||||
|
||||
```bash
|
||||
# Let bd resolve the collisions
|
||||
bd import -i .beads/issues.jsonl --resolve-collisions
|
||||
```
|
||||
|
||||
Output shows:
|
||||
```
|
||||
Resolving collisions...
|
||||
|
||||
=== Remapping Report ===
|
||||
Issues remapped: 2
|
||||
|
||||
Remappings (sorted by reference count):
|
||||
bd-1 → bd-4 (refs: 0)
|
||||
bd-2 → bd-5 (refs: 0)
|
||||
|
||||
All text and dependency references have been updated.
|
||||
|
||||
Import complete: 2 created, 0 updated, 1 dependencies added, 2 issues remapped
|
||||
```
|
||||
|
||||
Result:
|
||||
- `main` keeps: bd-1 (Feature A), bd-2 (Bug fix B), bd-3 (Task C)
|
||||
- `feature-branch` issues become: bd-4 (Different feature A), bd-5 (Different bug B), bd-3 (Feature D)
|
||||
|
||||
### 4. Export and Commit
|
||||
|
||||
```bash
|
||||
# Export the resolved state back to JSONL
|
||||
bd export -o .beads/issues.jsonl
|
||||
|
||||
# Commit the merge
|
||||
git add .beads/issues.jsonl
|
||||
git commit -m "Merge feature-branch with collision resolution"
|
||||
```
|
||||
|
||||
## Advanced: Cross-References
|
||||
|
||||
If your issues reference each other in text or dependencies, bd updates those automatically:
|
||||
|
||||
```bash
|
||||
# On feature branch, create issues with references
|
||||
bd create "Feature X" -d "Implements the core logic" -t feature -p 1
|
||||
# Assume this becomes bd-10
|
||||
|
||||
bd create "Test for X" -d "Tests bd-10 functionality" -t task -p 2
|
||||
# This references bd-10 in the description
|
||||
|
||||
bd dep add bd-11 bd-10 --type blocks
|
||||
# Dependencies are created
|
||||
|
||||
# After merge with collision resolution
|
||||
bd import -i .beads/issues.jsonl --resolve-collisions
|
||||
|
||||
# If bd-10 collided and was remapped to bd-15:
|
||||
# - bd-11's description becomes: "Tests bd-15 functionality"
|
||||
# - Dependency becomes: bd-11 → bd-15
|
||||
```
|
||||
|
||||
## When to Use This
|
||||
|
||||
1. **Feature branches** - Long-lived branches that create issues independently
|
||||
2. **Parallel development** - Multiple developers working on separate branches
|
||||
3. **Stale branches** - Old branches that need to be merged but have ID conflicts
|
||||
4. **Distributed teams** - Teams that work offline and sync via git
|
||||
|
||||
## Safety Notes
|
||||
|
||||
- `--resolve-collisions` preserves your existing database (current branch's issues never change IDs)
|
||||
- Only the incoming colliding issues get new IDs
|
||||
- Use `--dry-run` first to preview what will happen
|
||||
- All text references use word-boundary matching (bd-10 won't match bd-100)
|
||||
- The collision resolution is deterministic (same input = same output)
|
||||
|
||||
## Alternative: Manual Resolution
|
||||
|
||||
If you prefer manual control:
|
||||
|
||||
1. Don't use `--resolve-collisions`
|
||||
2. Manually edit the JSONL file before import
|
||||
3. Rename colliding IDs to unique values
|
||||
4. Manually update any cross-references
|
||||
5. Import normally
|
||||
|
||||
This gives you complete control but is more error-prone and time-consuming.
|
||||
|
||||
## See Also
|
||||
|
||||
- [Git Hooks Example](../git-hooks/) - Automate export/import with git hooks
|
||||
- [README.md](../../README.md) - Full collision resolution documentation
|
||||
- [TEXT_FORMATS.md](../../TEXT_FORMATS.md) - JSONL merge strategies
|
||||
@@ -1,145 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Demo script for branch merge collision resolution workflow
|
||||
# This script simulates a branch merge with ID collisions
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
echo "=== Branch Merge Collision Resolution Demo ==="
|
||||
echo ""
|
||||
|
||||
# Check if bd is available
|
||||
if ! command -v bd &> /dev/null; then
|
||||
echo "Error: bd command not found. Please install bd first."
|
||||
echo "Run: go install github.com/steveyegge/beads/cmd/bd@latest"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create a temporary directory for the demo
|
||||
DEMO_DIR=$(mktemp -d -t bd-merge-demo-XXXXXX)
|
||||
echo "Demo directory: $DEMO_DIR"
|
||||
cd "$DEMO_DIR"
|
||||
|
||||
# Initialize git repo
|
||||
echo ""
|
||||
echo "Step 1: Initialize git repo and bd database"
|
||||
git init
|
||||
git config user.name "Demo User"
|
||||
git config user.email "demo@example.com"
|
||||
bd init --prefix demo
|
||||
|
||||
# Create initial commit
|
||||
echo "Initial project" > README.txt
|
||||
git add README.txt .beads/
|
||||
git commit -m "Initial commit"
|
||||
|
||||
# Create issues on main branch
|
||||
echo ""
|
||||
echo "Step 2: Create issues on main branch"
|
||||
bd create "Implement login" -d "User authentication system" -t feature -p 1 --json
|
||||
bd create "Fix memory leak" -d "Memory leak in parser" -t bug -p 0 --json
|
||||
bd create "Update docs" -d "Document new API" -t task -p 2 --json
|
||||
|
||||
echo ""
|
||||
echo "Main branch issues:"
|
||||
bd list
|
||||
|
||||
# Export and commit
|
||||
bd export -o .beads/issues.jsonl
|
||||
git add .beads/issues.jsonl
|
||||
git commit -m "Add main branch issues (bd-1, bd-2, bd-3)"
|
||||
|
||||
# Create feature branch from earlier point
|
||||
echo ""
|
||||
echo "Step 3: Create feature branch"
|
||||
git checkout -b feature-branch HEAD~1
|
||||
|
||||
# Reimport to get clean state
|
||||
bd import -i .beads/issues.jsonl
|
||||
|
||||
# Create overlapping issues on feature branch
|
||||
echo ""
|
||||
echo "Step 4: Create different issues with same IDs on feature branch"
|
||||
bd create "Add dashboard" -d "Admin dashboard feature" -t feature -p 2 --json
|
||||
bd create "Improve performance" -d "Optimize queries" -t task -p 1 --json
|
||||
bd create "Add metrics" -d "Monitoring and metrics" -t feature -p 1 --json
|
||||
|
||||
echo ""
|
||||
echo "Feature branch issues:"
|
||||
bd list
|
||||
|
||||
# Export and commit
|
||||
bd export -o .beads/issues.jsonl
|
||||
git add .beads/issues.jsonl
|
||||
git commit -m "Add feature branch issues (bd-1, bd-2, bd-3)"
|
||||
|
||||
# Merge back to main
|
||||
echo ""
|
||||
echo "Step 5: Merge feature branch into main"
|
||||
git checkout main
|
||||
|
||||
# Attempt merge (will conflict)
|
||||
if git merge feature-branch --no-edit; then
|
||||
echo "Merge succeeded without conflicts"
|
||||
else
|
||||
echo "Merge conflict detected - resolving..."
|
||||
# Keep both versions by accepting both sides
|
||||
# In a real scenario, you'd resolve this more carefully
|
||||
git checkout --ours .beads/issues.jsonl
|
||||
git checkout --theirs .beads/issues.jsonl --patch || true
|
||||
# For demo purposes, accept theirs
|
||||
git checkout --theirs .beads/issues.jsonl
|
||||
git add .beads/issues.jsonl
|
||||
git commit -m "Merge feature-branch"
|
||||
fi
|
||||
|
||||
# Detect collisions
|
||||
echo ""
|
||||
echo "Step 6: Detect ID collisions"
|
||||
echo "Running: bd import -i .beads/issues.jsonl --dry-run"
|
||||
echo ""
|
||||
|
||||
if bd import -i .beads/issues.jsonl --dry-run; then
|
||||
echo "No collisions detected!"
|
||||
else
|
||||
echo ""
|
||||
echo "Collisions detected (expected)!"
|
||||
fi
|
||||
|
||||
# Resolve collisions
|
||||
echo ""
|
||||
echo "Step 7: Resolve collisions automatically"
|
||||
echo "Running: bd import -i .beads/issues.jsonl --resolve-collisions"
|
||||
echo ""
|
||||
|
||||
bd import -i .beads/issues.jsonl --resolve-collisions
|
||||
|
||||
# Show final state
|
||||
echo ""
|
||||
echo "Step 8: Final issue list after resolution"
|
||||
bd list
|
||||
|
||||
# Show remapping details
|
||||
echo ""
|
||||
echo "Step 9: Show how dependencies and references are maintained"
|
||||
echo "All text references like 'see bd-1' and dependencies were automatically updated!"
|
||||
|
||||
# Export final state
|
||||
bd export -o .beads/issues.jsonl
|
||||
git add .beads/issues.jsonl
|
||||
git commit -m "Resolve collisions and finalize merge"
|
||||
|
||||
echo ""
|
||||
echo "=== Demo Complete ==="
|
||||
echo ""
|
||||
echo "Summary:"
|
||||
echo "- Created issues on main branch (bd-1, bd-2, bd-3)"
|
||||
echo "- Created different issues on feature branch (also bd-1, bd-2, bd-3)"
|
||||
echo "- Merged branches with Git"
|
||||
echo "- Detected collisions with --dry-run"
|
||||
echo "- Resolved collisions with --resolve-collisions"
|
||||
echo "- Feature branch issues were renumbered to avoid conflicts"
|
||||
echo ""
|
||||
echo "Demo directory: $DEMO_DIR"
|
||||
echo "You can explore the git history: cd $DEMO_DIR && git log --oneline"
|
||||
echo ""
|
||||
echo "To clean up: rm -rf $DEMO_DIR"
|
||||
@@ -312,6 +312,11 @@ func handleRename(ctx context.Context, s *sqlite.SQLiteStorage, existing *types.
|
||||
// The rename is already complete in the database
|
||||
return deletedID, nil
|
||||
}
|
||||
// REMOVED (bd-8e05): Sequential ID collision handling during rename
|
||||
// With hash-based IDs, rename collisions should not occur
|
||||
return "", fmt.Errorf("rename collision handling removed - should not occur with hash IDs")
|
||||
|
||||
/* OLD CODE REMOVED (bd-8e05)
|
||||
// Different content - this is a collision during rename
|
||||
// Allocate a new ID for the incoming issue instead of using the desired ID
|
||||
prefix, err := s.GetConfig(ctx, "issue_prefix")
|
||||
@@ -324,13 +329,6 @@ func handleRename(ctx context.Context, s *sqlite.SQLiteStorage, existing *types.
|
||||
// Retry up to 3 times to handle concurrent ID allocation
|
||||
const maxRetries = 3
|
||||
for attempt := 0; attempt < maxRetries; attempt++ {
|
||||
// Sync counters before allocation to avoid collisions
|
||||
if attempt > 0 {
|
||||
if syncErr := s.SyncAllCounters(ctx); syncErr != nil {
|
||||
return "", fmt.Errorf("failed to sync counters on retry %d: %w", attempt, syncErr)
|
||||
}
|
||||
}
|
||||
|
||||
newID, err := s.AllocateNextID(ctx, prefix)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate new ID for rename collision: %w", err)
|
||||
@@ -372,6 +370,7 @@ func handleRename(ctx context.Context, s *sqlite.SQLiteStorage, existing *types.
|
||||
// This is acceptable because the old ID no longer exists in the system.
|
||||
|
||||
return oldID, nil
|
||||
*/
|
||||
}
|
||||
|
||||
// Check if old ID still exists (it might have been deleted by another clone)
|
||||
@@ -563,10 +562,7 @@ func upsertIssues(ctx context.Context, sqliteStore *sqlite.SQLiteStorage, issues
|
||||
result.Created += len(newIssues)
|
||||
}
|
||||
|
||||
// Sync counters after batch import
|
||||
if err := sqliteStore.SyncAllCounters(ctx); err != nil {
|
||||
return fmt.Errorf("error syncing counters: %w", err)
|
||||
}
|
||||
// REMOVED (bd-c7af): Counter sync after import - no longer needed with hash IDs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -942,12 +942,7 @@ func (m *MemoryStorage) UnderlyingConn(ctx context.Context) (*sql.Conn, error) {
|
||||
return nil, fmt.Errorf("UnderlyingConn not available in memory storage")
|
||||
}
|
||||
|
||||
// SyncAllCounters is a no-op now that sequential IDs are removed (bd-aa744b).
|
||||
// Kept for backward compatibility with existing code that calls it.
|
||||
func (m *MemoryStorage) SyncAllCounters(ctx context.Context) error {
|
||||
// No-op: hash IDs don't use counters
|
||||
return nil
|
||||
}
|
||||
// REMOVED (bd-c7af): SyncAllCounters - no longer needed with hash IDs
|
||||
|
||||
// MarkIssueDirty marks an issue as dirty for export
|
||||
func (m *MemoryStorage) MarkIssueDirty(ctx context.Context, issueID string) error {
|
||||
|
||||
@@ -353,11 +353,7 @@ func RemapCollisions(ctx context.Context, s *SQLiteStorage, collisions []*Collis
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if attempt < maxRetries-1 {
|
||||
if syncErr := s.SyncAllCounters(ctx); syncErr != nil {
|
||||
return nil, fmt.Errorf("retry %d: UNIQUE constraint error, counter sync failed: %w (original error: %v)", attempt+1, syncErr, err)
|
||||
}
|
||||
}
|
||||
// REMOVED (bd-c7af): Counter sync on retry - no longer needed with hash IDs
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed after %d retries due to UNIQUE constraint violations: %w", maxRetries, lastErr)
|
||||
@@ -365,44 +361,27 @@ func RemapCollisions(ctx context.Context, s *SQLiteStorage, collisions []*Collis
|
||||
|
||||
// remapCollisionsOnce performs a single attempt at collision resolution.
|
||||
// This is the actual implementation that RemapCollisions wraps with retry logic.
|
||||
// REMOVED (bd-8e05): With hash-based IDs, collision remapping is no longer needed.
|
||||
func remapCollisionsOnce(ctx context.Context, s *SQLiteStorage, collisions []*CollisionDetail, _ []*types.Issue) (map[string]string, error) {
|
||||
idMapping := make(map[string]string)
|
||||
|
||||
// Sync counters before remapping to avoid ID collisions
|
||||
if err := s.SyncAllCounters(ctx); err != nil {
|
||||
return nil, fmt.Errorf("failed to sync ID counters: %w", err)
|
||||
// With hash-based IDs, collisions should not occur. If they do, it's a bug.
|
||||
if len(collisions) > 0 {
|
||||
return nil, fmt.Errorf("collision remapping no longer supported with hash IDs - %d collisions detected", len(collisions))
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Step 1: Collect ALL dependencies before any modifications
|
||||
// This prevents CASCADE DELETE from losing dependency information
|
||||
allDepsBeforeRemap, err := s.GetAllDependencyRecords(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get all dependencies: %w", err)
|
||||
}
|
||||
// OLD IMPLEMENTATION REMOVED (bd-8e05) - retained for reference during migration
|
||||
// The original 250+ line function implemented sequential ID-based collision remapping
|
||||
// which is obsolete with hash-based IDs
|
||||
|
||||
// Step 2: Process each collision based on which version should be remapped
|
||||
for _, collision := range collisions {
|
||||
// Skip collisions with nil issues (shouldn't happen but be defensive)
|
||||
if collision.IncomingIssue == nil {
|
||||
return nil, fmt.Errorf("collision %s has nil IncomingIssue", collision.ID)
|
||||
}
|
||||
if collision.ExistingIssue == nil {
|
||||
return nil, fmt.Errorf("collision %s has nil ExistingIssue", collision.ID)
|
||||
}
|
||||
|
||||
oldID := collision.ID
|
||||
|
||||
// Allocate new ID using atomic counter
|
||||
prefix, err := s.GetConfig(ctx, "issue_prefix")
|
||||
if err != nil || prefix == "" {
|
||||
prefix = "bd"
|
||||
}
|
||||
nextID, err := s.getNextIDForPrefix(ctx, prefix)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate new ID for collision %s: %w", oldID, err)
|
||||
}
|
||||
newID := fmt.Sprintf("%s-%d", prefix, nextID)
|
||||
// Stub out the old implementation to avoid compile errors
|
||||
// The actual 250+ line implementation has been removed (bd-8e05)
|
||||
func _OLD_remapCollisionsOnce_REMOVED(ctx context.Context, s *SQLiteStorage, collisions []*CollisionDetail, _ []*types.Issue) (map[string]string, error) {
|
||||
// Original implementation removed - see git history before bd-8e05
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
/* OLD CODE REMOVED (bd-8e05) - kept for git history reference
|
||||
if collision.RemapIncoming {
|
||||
// Incoming has higher hash -> remap incoming, keep existing
|
||||
// Record mapping
|
||||
@@ -498,6 +477,7 @@ func remapCollisionsOnce(ctx context.Context, s *SQLiteStorage, collisions []*Co
|
||||
|
||||
return idMapping, nil
|
||||
}
|
||||
END OF REMOVED CODE */
|
||||
|
||||
// updateReferences updates all text field references and dependency records
|
||||
// to point to new IDs based on the idMapping
|
||||
|
||||
@@ -556,32 +556,8 @@ func migrateContentHashColumn(db *sql.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// getNextIDForPrefix atomically generates the next ID for a given prefix
|
||||
// Uses the issue_counters table for atomic, cross-process ID generation
|
||||
func (s *SQLiteStorage) getNextIDForPrefix(ctx context.Context, prefix string) (int, error) {
|
||||
var nextID int
|
||||
err := s.db.QueryRowContext(ctx, `
|
||||
INSERT INTO issue_counters (prefix, last_id)
|
||||
VALUES (?, 1)
|
||||
ON CONFLICT(prefix) DO UPDATE SET
|
||||
last_id = last_id + 1
|
||||
RETURNING last_id
|
||||
`, prefix).Scan(&nextID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to generate next ID for prefix %s: %w", prefix, err)
|
||||
}
|
||||
return nextID, nil
|
||||
}
|
||||
|
||||
// AllocateNextID generates the next issue ID for a given prefix.
|
||||
// This is a public wrapper around getNextIDForPrefix for use by other packages.
|
||||
func (s *SQLiteStorage) AllocateNextID(ctx context.Context, prefix string) (string, error) {
|
||||
nextID, err := s.getNextIDForPrefix(ctx, prefix)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s-%d", prefix, nextID), nil
|
||||
}
|
||||
// REMOVED (bd-8e05): getNextIDForPrefix and AllocateNextID - sequential ID generation
|
||||
// no longer needed with hash-based IDs
|
||||
|
||||
// getNextChildNumber atomically generates the next child number for a parent ID
|
||||
// Uses the child_counters table for atomic, cross-process child ID generation
|
||||
@@ -631,12 +607,7 @@ func (s *SQLiteStorage) GetNextChildID(ctx context.Context, parentID string) (st
|
||||
return childID, nil
|
||||
}
|
||||
|
||||
// SyncAllCounters is a no-op now that sequential IDs are removed (bd-aa744b).
|
||||
// Kept for backward compatibility with existing code that calls it.
|
||||
func (s *SQLiteStorage) SyncAllCounters(ctx context.Context) error {
|
||||
// No-op: hash IDs don't use counters
|
||||
return nil
|
||||
}
|
||||
// REMOVED (bd-c7af): SyncAllCounters - no longer needed with hash IDs
|
||||
|
||||
// REMOVED (bd-166): derivePrefixFromPath was causing duplicate issues with wrong prefix
|
||||
// The database should ALWAYS have issue_prefix config set explicitly (by 'bd init' or auto-import)
|
||||
@@ -1081,9 +1052,7 @@ func bulkMarkDirty(ctx context.Context, conn *sql.Conn, issues []*types.Issue) e
|
||||
// }
|
||||
//
|
||||
// // After importing with explicit IDs, sync counters to prevent collisions
|
||||
// if err := store.SyncAllCounters(ctx); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// REMOVED (bd-c7af): SyncAllCounters example - no longer needed with hash IDs
|
||||
//
|
||||
// Performance:
|
||||
// - 100 issues: ~30ms (vs ~900ms with CreateIssue loop)
|
||||
@@ -1727,8 +1696,8 @@ func (s *SQLiteStorage) DeleteIssue(ctx context.Context, id string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Sync counters after deletion to keep them accurate
|
||||
return s.SyncAllCounters(ctx)
|
||||
// REMOVED (bd-c7af): Counter sync after deletion - no longer needed with hash IDs
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteIssuesResult contains statistics about a batch deletion operation
|
||||
@@ -1781,9 +1750,7 @@ func (s *SQLiteStorage) DeleteIssues(ctx context.Context, ids []string, cascade
|
||||
return nil, fmt.Errorf("failed to commit transaction: %w", err)
|
||||
}
|
||||
|
||||
if err := s.SyncAllCounters(ctx); err != nil {
|
||||
return nil, fmt.Errorf("failed to sync counters after deletion: %w", err)
|
||||
}
|
||||
// REMOVED (bd-c7af): Counter sync after deletion - no longer needed with hash IDs
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user