Merge branch 'main' of https://github.com/steveyegge/beads
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -353,6 +353,7 @@ With --no-db: creates .beads/ directory and issues.jsonl file instead of SQLite
|
|||||||
|
|
||||||
green := color.New(color.FgGreen).SprintFunc()
|
green := color.New(color.FgGreen).SprintFunc()
|
||||||
cyan := color.New(color.FgCyan).SprintFunc()
|
cyan := color.New(color.FgCyan).SprintFunc()
|
||||||
|
yellow := color.New(color.FgYellow).SprintFunc()
|
||||||
|
|
||||||
fmt.Printf("\n%s bd initialized successfully!\n\n", green("✓"))
|
fmt.Printf("\n%s bd initialized successfully!\n\n", green("✓"))
|
||||||
fmt.Printf(" Database: %s\n", cyan(initDBPath))
|
fmt.Printf(" Database: %s\n", cyan(initDBPath))
|
||||||
@@ -752,7 +753,7 @@ exit 0
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// mergeDriverInstalled checks if bd merge driver is configured
|
// mergeDriverInstalled checks if bd merge driver is configured correctly
|
||||||
func mergeDriverInstalled() bool {
|
func mergeDriverInstalled() bool {
|
||||||
// Check git config for merge driver
|
// Check git config for merge driver
|
||||||
cmd := exec.Command("git", "config", "merge.beads.driver")
|
cmd := exec.Command("git", "config", "merge.beads.driver")
|
||||||
@@ -761,6 +762,14 @@ func mergeDriverInstalled() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if using old invalid placeholders (%L/%R from versions <0.24.0)
|
||||||
|
// Git only supports %O (base), %A (current), %B (other)
|
||||||
|
driverConfig := strings.TrimSpace(string(output))
|
||||||
|
if strings.Contains(driverConfig, "%L") || strings.Contains(driverConfig, "%R") {
|
||||||
|
// Stale config with invalid placeholders - needs repair
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Check if .gitattributes has the merge driver configured
|
// Check if .gitattributes has the merge driver configured
|
||||||
gitattributesPath := ".gitattributes"
|
gitattributesPath := ".gitattributes"
|
||||||
content, err := os.ReadFile(gitattributesPath)
|
content, err := os.ReadFile(gitattributesPath)
|
||||||
@@ -768,9 +777,12 @@ func mergeDriverInstalled() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for beads JSONL merge attribute
|
// Look for beads JSONL merge attribute (either canonical or legacy filename)
|
||||||
return strings.Contains(string(content), ".beads/beads.jsonl") &&
|
hasCanonical := strings.Contains(string(content), ".beads/issues.jsonl") &&
|
||||||
strings.Contains(string(content), "merge=beads")
|
strings.Contains(string(content), "merge=beads")
|
||||||
|
hasLegacy := strings.Contains(string(content), ".beads/beads.jsonl") &&
|
||||||
|
strings.Contains(string(content), "merge=beads")
|
||||||
|
return hasCanonical || hasLegacy
|
||||||
}
|
}
|
||||||
|
|
||||||
// installMergeDriver configures git to use bd merge for JSONL files
|
// installMergeDriver configures git to use bd merge for JSONL files
|
||||||
|
|||||||
@@ -791,6 +791,123 @@ func TestInitMergeDriverAutoConfiguration(t *testing.T) {
|
|||||||
t.Errorf("Expected merge.beads.name to contain 'bd', got %q", name)
|
t.Errorf("Expected merge.beads.name to contain 'bd', got %q", name)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("auto-repair stale merge driver with invalid placeholders", func(t *testing.T) {
|
||||||
|
// Reset global state
|
||||||
|
origDBPath := dbPath
|
||||||
|
defer func() { dbPath = origDBPath }()
|
||||||
|
dbPath = ""
|
||||||
|
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
originalWd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get working directory: %v", err)
|
||||||
|
}
|
||||||
|
defer os.Chdir(originalWd)
|
||||||
|
|
||||||
|
if err := os.Chdir(tmpDir); err != nil {
|
||||||
|
t.Fatalf("Failed to change to temp directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize git repo
|
||||||
|
if err := runCommandInDir(tmpDir, "git", "init"); err != nil {
|
||||||
|
t.Fatalf("Failed to init git: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure stale merge driver with old invalid placeholders (%L/%R)
|
||||||
|
// This simulates a user who initialized with bd version <0.24.0
|
||||||
|
if err := runCommandInDir(tmpDir, "git", "config", "merge.beads.driver", "bd merge %L %R"); err != nil {
|
||||||
|
t.Fatalf("Failed to set stale git config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create .gitattributes with merge driver
|
||||||
|
gitattrsPath := filepath.Join(tmpDir, ".gitattributes")
|
||||||
|
if err := os.WriteFile(gitattrsPath, []byte(".beads/beads.jsonl merge=beads\n"), 0644); err != nil {
|
||||||
|
t.Fatalf("Failed to create .gitattributes: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run bd init - should detect stale config and repair it
|
||||||
|
rootCmd.SetArgs([]string{"init", "--prefix", "test", "--quiet"})
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
t.Fatalf("Init failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify merge driver was updated to correct placeholders
|
||||||
|
driver, err := runCommandInDirWithOutput(tmpDir, "git", "config", "merge.beads.driver")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get merge.beads.driver: %v", err)
|
||||||
|
}
|
||||||
|
driver = strings.TrimSpace(driver)
|
||||||
|
expected := "bd merge %A %O %A %B"
|
||||||
|
if driver != expected {
|
||||||
|
t.Errorf("Expected merge driver to be repaired to %q, got %q", expected, driver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify it no longer contains invalid placeholders
|
||||||
|
if strings.Contains(driver, "%L") || strings.Contains(driver, "%R") {
|
||||||
|
t.Errorf("Merge driver should not contain invalid %%L or %%R placeholders, got %q", driver)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("detect canonical issues.jsonl filename in gitattributes", func(t *testing.T) {
|
||||||
|
// Reset global state
|
||||||
|
origDBPath := dbPath
|
||||||
|
defer func() { dbPath = origDBPath }()
|
||||||
|
dbPath = ""
|
||||||
|
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
originalWd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get working directory: %v", err)
|
||||||
|
}
|
||||||
|
defer os.Chdir(originalWd)
|
||||||
|
|
||||||
|
if err := os.Chdir(tmpDir); err != nil {
|
||||||
|
t.Fatalf("Failed to change to temp directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize git repo
|
||||||
|
if err := runCommandInDir(tmpDir, "git", "init"); err != nil {
|
||||||
|
t.Fatalf("Failed to init git: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-configure correct merge driver and canonical filename in .gitattributes
|
||||||
|
if err := runCommandInDir(tmpDir, "git", "config", "merge.beads.driver", "bd merge %A %O %A %B"); err != nil {
|
||||||
|
t.Fatalf("Failed to set git config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create .gitattributes with canonical filename (issues.jsonl, not beads.jsonl)
|
||||||
|
gitattrsPath := filepath.Join(tmpDir, ".gitattributes")
|
||||||
|
if err := os.WriteFile(gitattrsPath, []byte(".beads/issues.jsonl merge=beads\n"), 0644); err != nil {
|
||||||
|
t.Fatalf("Failed to create .gitattributes: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run bd init - should detect existing correct config and NOT reinstall
|
||||||
|
rootCmd.SetArgs([]string{"init", "--prefix", "test", "--quiet"})
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
t.Fatalf("Init failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify merge driver is still correct (not reinstalled unnecessarily)
|
||||||
|
driver, err := runCommandInDirWithOutput(tmpDir, "git", "config", "merge.beads.driver")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get merge.beads.driver: %v", err)
|
||||||
|
}
|
||||||
|
driver = strings.TrimSpace(driver)
|
||||||
|
expected := "bd merge %A %O %A %B"
|
||||||
|
if driver != expected {
|
||||||
|
t.Errorf("Expected merge driver to remain %q, got %q", expected, driver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify .gitattributes still has canonical filename (not overwritten)
|
||||||
|
content, err := os.ReadFile(gitattrsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to read .gitattributes: %v", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(content), ".beads/issues.jsonl merge=beads") {
|
||||||
|
t.Errorf(".gitattributes should still contain canonical filename pattern")
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestReadFirstIssueFromJSONL_ValidFile verifies reading first issue from valid JSONL
|
// TestReadFirstIssueFromJSONL_ValidFile verifies reading first issue from valid JSONL
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ func atomicWriteFile(path string, data []byte) error {
|
|||||||
return fmt.Errorf("close temp file: %w", err)
|
return fmt.Errorf("close temp file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set permissions to 0644
|
// Set permissions to 0600 (owner read/write only)
|
||||||
if err := os.Chmod(tmpPath, 0644); err != nil {
|
if err := os.Chmod(tmpPath, 0600); err != nil {
|
||||||
_ = os.Remove(tmpPath) // Best effort cleanup
|
_ = os.Remove(tmpPath) // Best effort cleanup
|
||||||
return fmt.Errorf("set permissions: %w", err)
|
return fmt.Errorf("set permissions: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ func TestAtomicWriteFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mode := info.Mode()
|
mode := info.Mode()
|
||||||
if mode.Perm() != 0644 {
|
if mode.Perm() != 0600 {
|
||||||
t.Errorf("file permissions mismatch: got %o, want %o", mode.Perm(), 0644)
|
t.Errorf("file permissions mismatch: got %o, want %o", mode.Perm(), 0600)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test overwriting existing file
|
// Test overwriting existing file
|
||||||
|
|||||||
Reference in New Issue
Block a user