fix(init): ensure sync branch persistence on init

Problem:
- Sync branch setup occurred before the config file was initialized
- Persistence only targeted the database, leading to loss on re-init

Solution:
- Reorder initialization to create the config file before sync setup
- Synchronize sync branch state to both config file and database

Impact:
- Settings are preserved across re-initialization and DB clears
- Better consistency between file and database state

Fixes: #927 (Bug 3)
This commit is contained in:
Peter Chanthamynavong
2026-01-06 15:22:15 -08:00
parent c1d3644b9a
commit 362b07d74f
4 changed files with 168 additions and 19 deletions

View File

@@ -193,13 +193,29 @@ func getConfigFromDB(dbPath string, key string) string {
return value
}
// Set stores the sync branch configuration in the database
// Set stores the sync branch configuration in both config.yaml AND the database.
// GH#909: Writing to both ensures bd doctor and migrate detection work correctly.
//
// Config precedence on read (from Get function):
// 1. BEADS_SYNC_BRANCH env var
// 2. sync-branch in config.yaml (recommended, version controlled)
// 3. sync.branch in database (legacy, for backward compatibility)
func Set(ctx context.Context, store storage.Storage, branch string) error {
// GH#807: Use sync-specific validation that rejects main/master
if err := ValidateSyncBranchName(branch); err != nil {
return err
}
// GH#909: Write to config.yaml first (primary source for doctor/migration checks)
// This also handles uncommenting if the key was commented out
if err := config.SetYamlConfig(ConfigYAMLKey, branch); err != nil {
// Log warning but don't fail - database write is still valuable
// This can fail if config.yaml doesn't exist yet (pre-init state)
// In that case, the database config still works for backward compatibility
fmt.Fprintf(os.Stderr, "Warning: could not update config.yaml: %v\n", err)
}
// Write to database for backward compatibility
return store.SetConfig(ctx, ConfigKey, branch)
}

View File

@@ -252,6 +252,78 @@ func TestSet(t *testing.T) {
})
}
// TestSetUpdatesConfigYAML verifies GH#909 fix: Set() writes to config.yaml
func TestSetUpdatesConfigYAML(t *testing.T) {
ctx := context.Background()
t.Run("updates config.yaml when it exists", func(t *testing.T) {
// Create temp directory with .beads/config.yaml
tmpDir, err := os.MkdirTemp("", "test-syncbranch-yaml-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
beadsDir := tmpDir + "/.beads"
if err := os.MkdirAll(beadsDir, 0750); err != nil {
t.Fatalf("Failed to create .beads dir: %v", err)
}
// Create initial config.yaml with sync-branch commented out
configPath := beadsDir + "/config.yaml"
initialConfig := `# beads configuration
# sync-branch: ""
auto-start-daemon: true
`
if err := os.WriteFile(configPath, []byte(initialConfig), 0600); err != nil {
t.Fatalf("Failed to create config.yaml: %v", err)
}
// Change to temp dir so findProjectConfigYaml can find it
origWd, _ := os.Getwd()
if err := os.Chdir(tmpDir); err != nil {
t.Fatalf("Failed to chdir: %v", err)
}
defer os.Chdir(origWd)
// Create test store
store := newTestStore(t)
defer store.Close()
// Call Set() which should update both database and config.yaml
if err := Set(ctx, store, "beads-sync"); err != nil {
t.Fatalf("Set() error = %v", err)
}
// Verify database was updated
dbValue, err := store.GetConfig(ctx, ConfigKey)
if err != nil {
t.Fatalf("GetConfig() error = %v", err)
}
if dbValue != "beads-sync" {
t.Errorf("Database value = %q, want %q", dbValue, "beads-sync")
}
// Verify config.yaml was updated (key uncommented and value set)
yamlContent, err := os.ReadFile(configPath)
if err != nil {
t.Fatalf("Failed to read config.yaml: %v", err)
}
yamlStr := string(yamlContent)
if !strings.Contains(yamlStr, "sync-branch:") {
t.Error("config.yaml should contain 'sync-branch:' (uncommented)")
}
if !strings.Contains(yamlStr, "beads-sync") {
t.Errorf("config.yaml should contain 'beads-sync', got:\n%s", yamlStr)
}
// Should NOT contain the commented version anymore
if strings.Contains(yamlStr, "# sync-branch:") {
t.Error("config.yaml still has commented '# sync-branch:', should be uncommented")
}
})
}
func TestUnset(t *testing.T) {
ctx := context.Background()