fix(sync): use inline import for --import-only with redirect (bd-ysal)
When running `bd sync --import-only` from a directory with `.beads/redirect`, the subprocess-based import could fail to update staleness metadata correctly because the subprocess might resolve paths differently than the parent process. The fix uses inline import (calling importIssuesCore directly) instead of spawning a subprocess. This ensures: 1. The same store and dbPath are used throughout 2. Path resolution is consistent with the parent process 3. Staleness metadata is updated correctly in the redirected database 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Steve Yegge
parent
cca2016376
commit
94997bd619
@@ -110,12 +110,13 @@ Use --merge to merge the sync branch back to main branch.`,
|
||||
}
|
||||
|
||||
// If import-only mode, just import and exit
|
||||
// Use inline import to avoid subprocess path resolution issues with .beads/redirect (bd-ysal)
|
||||
if importOnly {
|
||||
if dryRun {
|
||||
fmt.Println("→ [DRY RUN] Would import from JSONL")
|
||||
} else {
|
||||
fmt.Println("→ Importing from JSONL...")
|
||||
if err := importFromJSONL(ctx, jsonlPath, renameOnImport, noGitHistory); err != nil {
|
||||
if err := importFromJSONLInline(ctx, jsonlPath, renameOnImport, noGitHistory); err != nil {
|
||||
FatalError("importing: %v", err)
|
||||
}
|
||||
fmt.Println("✓ Import complete")
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/steveyegge/beads/internal/debug"
|
||||
"github.com/steveyegge/beads/internal/types"
|
||||
)
|
||||
|
||||
// importFromJSONL imports the JSONL file by running the import command
|
||||
@@ -55,6 +61,91 @@ func importFromJSONL(ctx context.Context, jsonlPath string, renameOnImport bool,
|
||||
return nil
|
||||
}
|
||||
|
||||
// importFromJSONLInline imports the JSONL file directly without spawning a subprocess.
|
||||
// This avoids path resolution issues when running from directories with .beads/redirect.
|
||||
// The parent process's store and dbPath are used, ensuring consistent path resolution.
|
||||
// (bd-ysal fix)
|
||||
func importFromJSONLInline(ctx context.Context, jsonlPath string, renameOnImport bool, noGitHistory bool) error {
|
||||
// Verify we have an active store
|
||||
if store == nil {
|
||||
return fmt.Errorf("no database store available for inline import")
|
||||
}
|
||||
|
||||
// Read and parse the JSONL file
|
||||
// #nosec G304 - jsonlPath is from findJSONLPath() which uses trusted paths
|
||||
f, err := os.Open(jsonlPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open JSONL file: %w", err)
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
var allIssues []*types.Issue
|
||||
scanner := bufio.NewScanner(f)
|
||||
lineNum := 0
|
||||
|
||||
for scanner.Scan() {
|
||||
lineNum++
|
||||
line := scanner.Text()
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var issue types.Issue
|
||||
if err := json.Unmarshal([]byte(line), &issue); err != nil {
|
||||
return fmt.Errorf("error parsing line %d: %w", lineNum, err)
|
||||
}
|
||||
issue.SetDefaults()
|
||||
allIssues = append(allIssues, &issue)
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return fmt.Errorf("error reading JSONL: %w", err)
|
||||
}
|
||||
|
||||
// Import using shared logic
|
||||
opts := ImportOptions{
|
||||
RenameOnImport: renameOnImport,
|
||||
}
|
||||
result, err := importIssuesCore(ctx, dbPath, store, allIssues, opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("import failed: %w", err)
|
||||
}
|
||||
|
||||
// Update staleness metadata (same as import.go lines 386-411)
|
||||
// This is critical: without this, CheckStaleness will still report stale
|
||||
if currentHash, hashErr := computeJSONLHash(jsonlPath); hashErr == nil {
|
||||
if err := store.SetMetadata(ctx, "jsonl_content_hash", currentHash); err != nil {
|
||||
debug.Logf("Warning: failed to update jsonl_content_hash: %v", err)
|
||||
}
|
||||
if err := store.SetJSONLFileHash(ctx, currentHash); err != nil {
|
||||
debug.Logf("Warning: failed to update jsonl_file_hash: %v", err)
|
||||
}
|
||||
importTime := time.Now().Format(time.RFC3339Nano)
|
||||
if err := store.SetMetadata(ctx, "last_import_time", importTime); err != nil {
|
||||
debug.Logf("Warning: failed to update last_import_time: %v", err)
|
||||
}
|
||||
} else {
|
||||
debug.Logf("Warning: failed to compute JSONL hash: %v", hashErr)
|
||||
}
|
||||
|
||||
// Update database mtime
|
||||
if err := TouchDatabaseFile(dbPath, jsonlPath); err != nil {
|
||||
debug.Logf("Warning: failed to update database mtime: %v", err)
|
||||
}
|
||||
|
||||
// Print summary
|
||||
fmt.Fprintf(os.Stderr, "Import complete: %d created, %d updated", result.Created, result.Updated)
|
||||
if result.Unchanged > 0 {
|
||||
fmt.Fprintf(os.Stderr, ", %d unchanged", result.Unchanged)
|
||||
}
|
||||
if result.Skipped > 0 {
|
||||
fmt.Fprintf(os.Stderr, ", %d skipped", result.Skipped)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveNoGitHistoryForFromMain returns the resolved noGitHistory value for sync operations.
|
||||
// When syncing from main (--from-main), noGitHistory is forced to true to prevent creating
|
||||
// incorrect deletion records for locally-created beads that don't exist on main.
|
||||
|
||||
Reference in New Issue
Block a user