Fix auto-import git history backfill bug (bd-4pv)
Auto-import was allowing git history backfill to run, which could incorrectly purge issues. The fix adds NoGitHistory=true to all auto-import code paths: - autoimport.go (importFromGit) - autoflush.go (autoImportIfNewer) - daemon_sync.go (importToJSONLWithStore) Git history backfill is designed to detect deletions that happened after a local DB was created. During auto-import, there is no local work to protect - we are importing from the authoritative JSONL source. Also adds comprehensive tests for NoGitHistory behavior. Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
544
internal/importer/importer_githistory_test.go
Normal file
544
internal/importer/importer_githistory_test.go
Normal file
@@ -0,0 +1,544 @@
|
||||
package importer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/steveyegge/beads/internal/deletions"
|
||||
"github.com/steveyegge/beads/internal/storage/sqlite"
|
||||
"github.com/steveyegge/beads/internal/types"
|
||||
)
|
||||
|
||||
// TestAutoImportPurgesBugBd4pv tests that auto-import doesn't incorrectly purge
|
||||
// issues due to git history backfill finding them in old commits.
|
||||
// This is a reproduction test for bd-4pv.
|
||||
func TestAutoImportPurgesBugBd4pv(t *testing.T) {
|
||||
// Create a temp directory for a test git repo
|
||||
tmpDir := t.TempDir()
|
||||
repoDir := filepath.Join(tmpDir, "test-repo")
|
||||
beadsDir := filepath.Join(repoDir, ".beads")
|
||||
|
||||
// Initialize git repo
|
||||
if err := os.MkdirAll(repoDir, 0755); err != nil {
|
||||
t.Fatalf("failed to create repo dir: %v", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command("git", "init")
|
||||
cmd.Dir = repoDir
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("failed to init git repo: %v\n%s", err, out)
|
||||
}
|
||||
|
||||
// Configure git user for commits
|
||||
cmd = exec.Command("git", "config", "user.email", "test@test.com")
|
||||
cmd.Dir = repoDir
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Fatalf("failed to config git email: %v", err)
|
||||
}
|
||||
cmd = exec.Command("git", "config", "user.name", "Test User")
|
||||
cmd.Dir = repoDir
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Fatalf("failed to config git name: %v", err)
|
||||
}
|
||||
|
||||
// Create .beads directory
|
||||
if err := os.MkdirAll(beadsDir, 0755); err != nil {
|
||||
t.Fatalf("failed to create .beads dir: %v", err)
|
||||
}
|
||||
|
||||
// Create initial issues.jsonl with 5 issues
|
||||
jsonlPath := filepath.Join(beadsDir, "issues.jsonl")
|
||||
initialContent := `{"id":"bd-abc1","title":"Issue 1","status":"open","priority":1,"issue_type":"task","created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}
|
||||
{"id":"bd-abc2","title":"Issue 2","status":"open","priority":1,"issue_type":"task","created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}
|
||||
{"id":"bd-abc3","title":"Issue 3","status":"open","priority":1,"issue_type":"task","created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}
|
||||
{"id":"bd-abc4","title":"Issue 4","status":"open","priority":1,"issue_type":"task","created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}
|
||||
{"id":"bd-abc5","title":"Issue 5","status":"open","priority":1,"issue_type":"task","created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}
|
||||
`
|
||||
if err := os.WriteFile(jsonlPath, []byte(initialContent), 0644); err != nil {
|
||||
t.Fatalf("failed to write initial JSONL: %v", err)
|
||||
}
|
||||
|
||||
// Commit the initial state
|
||||
cmd = exec.Command("git", "add", ".beads/issues.jsonl")
|
||||
cmd.Dir = repoDir
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("failed to git add: %v\n%s", err, out)
|
||||
}
|
||||
cmd = exec.Command("git", "commit", "-m", "Initial issues")
|
||||
cmd.Dir = repoDir
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("failed to git commit: %v\n%s", err, out)
|
||||
}
|
||||
|
||||
// Now simulate what happens during auto-import:
|
||||
// 1. Database is empty
|
||||
// 2. Auto-import detects issues in git and imports them
|
||||
|
||||
ctx := context.Background()
|
||||
dbPath := filepath.Join(beadsDir, "beads.db")
|
||||
store, err := sqlite.New(ctx, dbPath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create store: %v", err)
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
// Set up prefix
|
||||
if err := store.SetConfig(ctx, "issue_prefix", "bd"); err != nil {
|
||||
t.Fatalf("failed to set prefix: %v", err)
|
||||
}
|
||||
|
||||
// Parse the JSONL issues
|
||||
now := time.Now()
|
||||
issues := []*types.Issue{
|
||||
{ID: "bd-abc1", Title: "Issue 1", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, CreatedAt: now, UpdatedAt: now},
|
||||
{ID: "bd-abc2", Title: "Issue 2", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, CreatedAt: now, UpdatedAt: now},
|
||||
{ID: "bd-abc3", Title: "Issue 3", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, CreatedAt: now, UpdatedAt: now},
|
||||
{ID: "bd-abc4", Title: "Issue 4", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, CreatedAt: now, UpdatedAt: now},
|
||||
{ID: "bd-abc5", Title: "Issue 5", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, CreatedAt: now, UpdatedAt: now},
|
||||
}
|
||||
|
||||
// Do the import WITHOUT NoGitHistory (the buggy behavior)
|
||||
opts := Options{
|
||||
DryRun: false,
|
||||
SkipUpdate: false,
|
||||
SkipPrefixValidation: true,
|
||||
NoGitHistory: false, // Bug: should be true for auto-import
|
||||
}
|
||||
|
||||
result, err := ImportIssues(ctx, dbPath, store, issues, opts)
|
||||
if err != nil {
|
||||
t.Fatalf("import failed: %v", err)
|
||||
}
|
||||
|
||||
// Check how many issues are in the database
|
||||
allIssues, err := store.SearchIssues(ctx, "", types.IssueFilter{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to search issues: %v", err)
|
||||
}
|
||||
|
||||
// With the bug, some or all issues might be purged
|
||||
// because git history finds them in the commit and thinks they were "deleted"
|
||||
t.Logf("Import result: created=%d, updated=%d, purged=%d, purgedIDs=%v",
|
||||
result.Created, result.Updated, result.Purged, result.PurgedIDs)
|
||||
t.Logf("Issues in DB after import: %d", len(allIssues))
|
||||
|
||||
// The correct behavior is 5 issues in DB
|
||||
// The bug would result in fewer (potentially 0) due to incorrect purging
|
||||
if len(allIssues) != 5 {
|
||||
t.Errorf("Expected 5 issues in DB, got %d. This is the bd-4pv bug!", len(allIssues))
|
||||
t.Logf("Purged IDs: %v", result.PurgedIDs)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGitHistoryBackfillPurgesLocalIssues tests the scenario where git history
|
||||
// backfill incorrectly purges issues that exist locally but were never in the remote JSONL.
|
||||
// This is another aspect of the bd-4pv bug.
|
||||
func TestGitHistoryBackfillPurgesLocalIssues(t *testing.T) {
|
||||
// Create a temp directory for a test git repo
|
||||
tmpDir := t.TempDir()
|
||||
repoDir := filepath.Join(tmpDir, "test-repo")
|
||||
beadsDir := filepath.Join(repoDir, ".beads")
|
||||
|
||||
// Initialize git repo
|
||||
if err := os.MkdirAll(repoDir, 0755); err != nil {
|
||||
t.Fatalf("failed to create repo dir: %v", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command("git", "init")
|
||||
cmd.Dir = repoDir
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("failed to init git repo: %v\n%s", err, out)
|
||||
}
|
||||
|
||||
// Configure git user for commits
|
||||
cmd = exec.Command("git", "config", "user.email", "test@test.com")
|
||||
cmd.Dir = repoDir
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Fatalf("failed to config git email: %v", err)
|
||||
}
|
||||
cmd = exec.Command("git", "config", "user.name", "Test User")
|
||||
cmd.Dir = repoDir
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Fatalf("failed to config git name: %v", err)
|
||||
}
|
||||
|
||||
// Create .beads directory
|
||||
if err := os.MkdirAll(beadsDir, 0755); err != nil {
|
||||
t.Fatalf("failed to create .beads dir: %v", err)
|
||||
}
|
||||
|
||||
// Create initial issues.jsonl with 1 issue
|
||||
jsonlPath := filepath.Join(beadsDir, "issues.jsonl")
|
||||
initialContent := `{"id":"bd-shared1","title":"Shared Issue","status":"open","priority":1,"issue_type":"task","created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}
|
||||
`
|
||||
if err := os.WriteFile(jsonlPath, []byte(initialContent), 0644); err != nil {
|
||||
t.Fatalf("failed to write initial JSONL: %v", err)
|
||||
}
|
||||
|
||||
// Create empty deletions.jsonl
|
||||
deletionsPath := deletions.DefaultPath(beadsDir)
|
||||
if err := os.WriteFile(deletionsPath, []byte(""), 0644); err != nil {
|
||||
t.Fatalf("failed to write deletions: %v", err)
|
||||
}
|
||||
|
||||
// Commit the initial state
|
||||
cmd = exec.Command("git", "add", ".beads/")
|
||||
cmd.Dir = repoDir
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("failed to git add: %v\n%s", err, out)
|
||||
}
|
||||
cmd = exec.Command("git", "commit", "-m", "Initial issues")
|
||||
cmd.Dir = repoDir
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("failed to git commit: %v\n%s", err, out)
|
||||
}
|
||||
|
||||
// Create database with the shared issue AND local issues
|
||||
ctx := context.Background()
|
||||
dbPath := filepath.Join(beadsDir, "beads.db")
|
||||
store, err := sqlite.New(ctx, dbPath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create store: %v", err)
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
// Set up prefix
|
||||
if err := store.SetConfig(ctx, "issue_prefix", "bd"); err != nil {
|
||||
t.Fatalf("failed to set prefix: %v", err)
|
||||
}
|
||||
|
||||
// Create issues in DB: 1 shared (in JSONL) + 4 local-only
|
||||
now := time.Now()
|
||||
dbIssues := []*types.Issue{
|
||||
{ID: "bd-shared1", Title: "Shared Issue", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, CreatedAt: now, UpdatedAt: now},
|
||||
{ID: "bd-local1", Title: "Local Issue 1", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, CreatedAt: now, UpdatedAt: now},
|
||||
{ID: "bd-local2", Title: "Local Issue 2", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, CreatedAt: now, UpdatedAt: now},
|
||||
{ID: "bd-local3", Title: "Local Issue 3", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, CreatedAt: now, UpdatedAt: now},
|
||||
{ID: "bd-local4", Title: "Local Issue 4", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, CreatedAt: now, UpdatedAt: now},
|
||||
}
|
||||
for _, issue := range dbIssues {
|
||||
issue.ContentHash = issue.ComputeContentHash()
|
||||
if err := store.CreateIssue(ctx, issue, "test"); err != nil {
|
||||
t.Fatalf("failed to create issue %s: %v", issue.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify DB has 5 issues
|
||||
allBefore, err := store.SearchIssues(ctx, "", types.IssueFilter{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to search issues: %v", err)
|
||||
}
|
||||
if len(allBefore) != 5 {
|
||||
t.Fatalf("Expected 5 issues before import, got %d", len(allBefore))
|
||||
}
|
||||
|
||||
// Now import from JSONL (only has 1 issue: bd-shared1)
|
||||
// WITHOUT NoGitHistory - this is the bug
|
||||
incomingIssues := []*types.Issue{
|
||||
{ID: "bd-shared1", Title: "Shared Issue", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, CreatedAt: now, UpdatedAt: now},
|
||||
}
|
||||
|
||||
opts := Options{
|
||||
DryRun: false,
|
||||
SkipUpdate: false,
|
||||
SkipPrefixValidation: true,
|
||||
NoGitHistory: false, // Bug: local issues might be purged if they appear in git history
|
||||
}
|
||||
|
||||
result, err := ImportIssues(ctx, dbPath, store, incomingIssues, opts)
|
||||
if err != nil {
|
||||
t.Fatalf("import failed: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("Import result: created=%d, updated=%d, unchanged=%d, purged=%d, purgedIDs=%v",
|
||||
result.Created, result.Updated, result.Unchanged, result.Purged, result.PurgedIDs)
|
||||
|
||||
// Check how many issues are in the database
|
||||
allAfter, err := store.SearchIssues(ctx, "", types.IssueFilter{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to search issues: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("Issues in DB after import: %d", len(allAfter))
|
||||
for _, issue := range allAfter {
|
||||
t.Logf(" - %s: %s", issue.ID, issue.Title)
|
||||
}
|
||||
|
||||
// Expected: bd-shared1 + bd-local1..4 = 5 issues
|
||||
// The local issues should NOT be purged because:
|
||||
// 1. They're not in the deletions manifest
|
||||
// 2. They were never in git history (they're local-only)
|
||||
// 3. NoGitHistory=false but git history check shouldn't find bd-local* in history
|
||||
if len(allAfter) != 5 {
|
||||
t.Errorf("Expected 5 issues in DB, got %d. Local issues may have been incorrectly purged!", len(allAfter))
|
||||
}
|
||||
|
||||
// Should have no purges (bd-local* were never in git history)
|
||||
if result.Purged != 0 {
|
||||
t.Errorf("Expected 0 purged issues, got %d (IDs: %v)", result.Purged, result.PurgedIDs)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNoGitHistoryPreventsIncorrectPurge tests that setting NoGitHistory prevents
|
||||
// the purge of issues that exist in the DB but not in JSONL during auto-import.
|
||||
// This is the fix for bd-4pv - auto-import should NOT run git history backfill.
|
||||
func TestNoGitHistoryPreventsIncorrectPurge(t *testing.T) {
|
||||
// Create a temp directory for a test git repo
|
||||
tmpDir := t.TempDir()
|
||||
repoDir := filepath.Join(tmpDir, "test-repo")
|
||||
beadsDir := filepath.Join(repoDir, ".beads")
|
||||
|
||||
// Initialize git repo
|
||||
if err := os.MkdirAll(repoDir, 0755); err != nil {
|
||||
t.Fatalf("failed to create repo dir: %v", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command("git", "init")
|
||||
cmd.Dir = repoDir
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("failed to init git repo: %v\n%s", err, out)
|
||||
}
|
||||
|
||||
// Configure git user for commits
|
||||
cmd = exec.Command("git", "config", "user.email", "test@test.com")
|
||||
cmd.Dir = repoDir
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Fatalf("failed to config git email: %v", err)
|
||||
}
|
||||
cmd = exec.Command("git", "config", "user.name", "Test User")
|
||||
cmd.Dir = repoDir
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Fatalf("failed to config git name: %v", err)
|
||||
}
|
||||
|
||||
// Create .beads directory
|
||||
if err := os.MkdirAll(beadsDir, 0755); err != nil {
|
||||
t.Fatalf("failed to create .beads dir: %v", err)
|
||||
}
|
||||
|
||||
// Create issues.jsonl with 1 issue
|
||||
jsonlPath := filepath.Join(beadsDir, "issues.jsonl")
|
||||
initialContent := `{"id":"bd-shared1","title":"Shared Issue","status":"open","priority":1,"issue_type":"task","created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}
|
||||
`
|
||||
if err := os.WriteFile(jsonlPath, []byte(initialContent), 0644); err != nil {
|
||||
t.Fatalf("failed to write initial JSONL: %v", err)
|
||||
}
|
||||
|
||||
// Create empty deletions.jsonl
|
||||
deletionsPath := deletions.DefaultPath(beadsDir)
|
||||
if err := os.WriteFile(deletionsPath, []byte(""), 0644); err != nil {
|
||||
t.Fatalf("failed to write deletions: %v", err)
|
||||
}
|
||||
|
||||
// Commit
|
||||
cmd = exec.Command("git", "add", ".beads/")
|
||||
cmd.Dir = repoDir
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("failed to git add: %v\n%s", err, out)
|
||||
}
|
||||
cmd = exec.Command("git", "commit", "-m", "Initial issues")
|
||||
cmd.Dir = repoDir
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("failed to git commit: %v\n%s", err, out)
|
||||
}
|
||||
|
||||
// Create database with 5 issues (1 shared + 4 local-only)
|
||||
ctx := context.Background()
|
||||
dbPath := filepath.Join(beadsDir, "beads.db")
|
||||
store, err := sqlite.New(ctx, dbPath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create store: %v", err)
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
// Set up prefix
|
||||
if err := store.SetConfig(ctx, "issue_prefix", "bd"); err != nil {
|
||||
t.Fatalf("failed to set prefix: %v", err)
|
||||
}
|
||||
|
||||
// Create all 5 issues in DB
|
||||
now := time.Now()
|
||||
dbIssues := []*types.Issue{
|
||||
{ID: "bd-shared1", Title: "Shared Issue", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, CreatedAt: now, UpdatedAt: now},
|
||||
{ID: "bd-local1", Title: "Local Issue 1", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, CreatedAt: now, UpdatedAt: now},
|
||||
{ID: "bd-local2", Title: "Local Issue 2", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, CreatedAt: now, UpdatedAt: now},
|
||||
{ID: "bd-local3", Title: "Local Issue 3", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, CreatedAt: now, UpdatedAt: now},
|
||||
{ID: "bd-local4", Title: "Local Issue 4", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, CreatedAt: now, UpdatedAt: now},
|
||||
}
|
||||
for _, issue := range dbIssues {
|
||||
issue.ContentHash = issue.ComputeContentHash()
|
||||
if err := store.CreateIssue(ctx, issue, "test"); err != nil {
|
||||
t.Fatalf("failed to create issue %s: %v", issue.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Import from JSONL (only has 1 issue) WITH NoGitHistory=true (the fix)
|
||||
incomingIssues := []*types.Issue{
|
||||
{ID: "bd-shared1", Title: "Shared Issue", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, CreatedAt: now, UpdatedAt: now},
|
||||
}
|
||||
|
||||
opts := Options{
|
||||
DryRun: false,
|
||||
SkipUpdate: false,
|
||||
SkipPrefixValidation: true,
|
||||
NoGitHistory: true, // Fix: skip git history backfill during auto-import
|
||||
}
|
||||
|
||||
result, err := ImportIssues(ctx, dbPath, store, incomingIssues, opts)
|
||||
if err != nil {
|
||||
t.Fatalf("import failed: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("Import result: created=%d, updated=%d, unchanged=%d, purged=%d, purgedIDs=%v",
|
||||
result.Created, result.Updated, result.Unchanged, result.Purged, result.PurgedIDs)
|
||||
|
||||
// Check how many issues are in the database
|
||||
allAfter, err := store.SearchIssues(ctx, "", types.IssueFilter{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to search issues: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("Issues in DB after import: %d", len(allAfter))
|
||||
for _, issue := range allAfter {
|
||||
t.Logf(" - %s: %s", issue.ID, issue.Title)
|
||||
}
|
||||
|
||||
// With NoGitHistory=true, the 4 local issues should NOT be purged
|
||||
// because we skip git history backfill entirely during auto-import.
|
||||
// This is the correct behavior for auto-import - local work should be preserved.
|
||||
// Expected: all 5 issues remain
|
||||
if len(allAfter) != 5 {
|
||||
t.Errorf("Expected 5 issues in DB (local work preserved), got %d", len(allAfter))
|
||||
}
|
||||
|
||||
// Should have no purges
|
||||
if result.Purged != 0 {
|
||||
t.Errorf("Expected 0 purged issues (NoGitHistory prevents purge), got %d (IDs: %v)", result.Purged, result.PurgedIDs)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAutoImportWithNoGitHistoryFlag tests the fix for bd-4pv
|
||||
func TestAutoImportWithNoGitHistoryFlag(t *testing.T) {
|
||||
// Create a temp directory for a test git repo
|
||||
tmpDir := t.TempDir()
|
||||
repoDir := filepath.Join(tmpDir, "test-repo")
|
||||
beadsDir := filepath.Join(repoDir, ".beads")
|
||||
|
||||
// Initialize git repo
|
||||
if err := os.MkdirAll(repoDir, 0755); err != nil {
|
||||
t.Fatalf("failed to create repo dir: %v", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command("git", "init")
|
||||
cmd.Dir = repoDir
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("failed to init git repo: %v\n%s", err, out)
|
||||
}
|
||||
|
||||
// Configure git user for commits
|
||||
cmd = exec.Command("git", "config", "user.email", "test@test.com")
|
||||
cmd.Dir = repoDir
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Fatalf("failed to config git email: %v", err)
|
||||
}
|
||||
cmd = exec.Command("git", "config", "user.name", "Test User")
|
||||
cmd.Dir = repoDir
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Fatalf("failed to config git name: %v", err)
|
||||
}
|
||||
|
||||
// Create .beads directory
|
||||
if err := os.MkdirAll(beadsDir, 0755); err != nil {
|
||||
t.Fatalf("failed to create .beads dir: %v", err)
|
||||
}
|
||||
|
||||
// Create initial issues.jsonl with 5 issues
|
||||
jsonlPath := filepath.Join(beadsDir, "issues.jsonl")
|
||||
initialContent := `{"id":"bd-xyz1","title":"Issue 1","status":"open","priority":1,"issue_type":"task","created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}
|
||||
{"id":"bd-xyz2","title":"Issue 2","status":"open","priority":1,"issue_type":"task","created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}
|
||||
{"id":"bd-xyz3","title":"Issue 3","status":"open","priority":1,"issue_type":"task","created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}
|
||||
{"id":"bd-xyz4","title":"Issue 4","status":"open","priority":1,"issue_type":"task","created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}
|
||||
{"id":"bd-xyz5","title":"Issue 5","status":"open","priority":1,"issue_type":"task","created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}
|
||||
`
|
||||
if err := os.WriteFile(jsonlPath, []byte(initialContent), 0644); err != nil {
|
||||
t.Fatalf("failed to write initial JSONL: %v", err)
|
||||
}
|
||||
|
||||
// Also create a deletions.jsonl (empty)
|
||||
deletionsPath := deletions.DefaultPath(beadsDir)
|
||||
if err := os.WriteFile(deletionsPath, []byte(""), 0644); err != nil {
|
||||
t.Fatalf("failed to write deletions: %v", err)
|
||||
}
|
||||
|
||||
// Commit the initial state
|
||||
cmd = exec.Command("git", "add", ".beads/")
|
||||
cmd.Dir = repoDir
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("failed to git add: %v\n%s", err, out)
|
||||
}
|
||||
cmd = exec.Command("git", "commit", "-m", "Initial issues")
|
||||
cmd.Dir = repoDir
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("failed to git commit: %v\n%s", err, out)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
dbPath := filepath.Join(beadsDir, "beads.db")
|
||||
store, err := sqlite.New(ctx, dbPath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create store: %v", err)
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
// Set up prefix
|
||||
if err := store.SetConfig(ctx, "issue_prefix", "bd"); err != nil {
|
||||
t.Fatalf("failed to set prefix: %v", err)
|
||||
}
|
||||
|
||||
// Parse the JSONL issues
|
||||
now := time.Now()
|
||||
issues := []*types.Issue{
|
||||
{ID: "bd-xyz1", Title: "Issue 1", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, CreatedAt: now, UpdatedAt: now},
|
||||
{ID: "bd-xyz2", Title: "Issue 2", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, CreatedAt: now, UpdatedAt: now},
|
||||
{ID: "bd-xyz3", Title: "Issue 3", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, CreatedAt: now, UpdatedAt: now},
|
||||
{ID: "bd-xyz4", Title: "Issue 4", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, CreatedAt: now, UpdatedAt: now},
|
||||
{ID: "bd-xyz5", Title: "Issue 5", Status: types.StatusOpen, Priority: 1, IssueType: types.TypeTask, CreatedAt: now, UpdatedAt: now},
|
||||
}
|
||||
|
||||
// Do the import WITH NoGitHistory (the fix)
|
||||
opts := Options{
|
||||
DryRun: false,
|
||||
SkipUpdate: false,
|
||||
SkipPrefixValidation: true,
|
||||
NoGitHistory: true, // Fix: skip git history backfill during auto-import
|
||||
}
|
||||
|
||||
result, err := ImportIssues(ctx, dbPath, store, issues, opts)
|
||||
if err != nil {
|
||||
t.Fatalf("import failed: %v", err)
|
||||
}
|
||||
|
||||
// Check how many issues are in the database
|
||||
allIssues, err := store.SearchIssues(ctx, "", types.IssueFilter{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to search issues: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("Import result: created=%d, updated=%d, purged=%d",
|
||||
result.Created, result.Updated, result.Purged)
|
||||
t.Logf("Issues in DB after import: %d", len(allIssues))
|
||||
|
||||
// With the fix, all 5 issues should be in DB
|
||||
if len(allIssues) != 5 {
|
||||
t.Errorf("Expected 5 issues in DB, got %d", len(allIssues))
|
||||
}
|
||||
|
||||
// Should have no purges
|
||||
if result.Purged != 0 {
|
||||
t.Errorf("Expected 0 purged issues, got %d", result.Purged)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user