Files
beads/cmd/bd/migrate_sync_test.go
Steve Yegge 94105bd9e2 feat: add bd migrate-sync command for sync.branch workflow setup (bd-epn)
Add a new command that encapsulates all the work needed to migrate a clone
to use the sync.branch workflow for multi-clone setups like Gas Town:

- Validates current state (not on sync branch, not already configured)
- Creates sync branch if it doesn't exist (from remote or locally)
- Sets up git worktree for the sync branch
- Syncs current beads data to worktree
- Commits initial state to sync branch
- Sets sync.branch configuration
- Pushes sync branch to remote

Usage:
  bd migrate-sync beads-sync           # Basic migration
  bd migrate-sync beads-sync --dry-run # Preview changes
  bd migrate-sync beads-sync --force   # Reconfigure even if set
2025-12-01 20:07:27 -08:00

181 lines
4.8 KiB
Go

package main
import (
"context"
"os"
"os/exec"
"path/filepath"
"testing"
)
func TestMigrateSyncValidation(t *testing.T) {
// Test invalid branch names
tests := []struct {
name string
branch string
wantErr bool
}{
{"valid simple", "beads-sync", false},
{"valid with slash", "beads/sync", false},
{"valid with dots", "beads.sync", false},
{"invalid empty", "", true},
{"invalid HEAD", "HEAD", true},
{"invalid dots", "..", true},
{"invalid leading slash", "/beads", true},
{"invalid trailing slash", "beads/", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// We can't easily test the full command without a git repo,
// but we can test branch validation indirectly
if tt.branch == "" {
// Empty branch should fail at args validation level
return
}
})
}
}
func TestMigrateSyncDryRun(t *testing.T) {
// Create a temp directory with a git repo
tmpDir, err := os.MkdirTemp("", "bd-migrate-sync-test")
if err != nil {
t.Fatalf("failed to create temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
// Initialize git repo
cmd := exec.Command("git", "init")
cmd.Dir = tmpDir
if err := cmd.Run(); err != nil {
t.Fatalf("failed to init git: %v", err)
}
// Configure git user for commits
cmd = exec.Command("git", "config", "user.email", "test@test.com")
cmd.Dir = tmpDir
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 = tmpDir
if err := cmd.Run(); err != nil {
t.Fatalf("failed to config git name: %v", err)
}
// Create initial commit
testFile := filepath.Join(tmpDir, "test.txt")
if err := os.WriteFile(testFile, []byte("test"), 0644); err != nil {
t.Fatalf("failed to create test file: %v", err)
}
cmd = exec.Command("git", "add", ".")
cmd.Dir = tmpDir
if err := cmd.Run(); err != nil {
t.Fatalf("failed to git add: %v", err)
}
cmd = exec.Command("git", "commit", "-m", "initial")
cmd.Dir = tmpDir
if err := cmd.Run(); err != nil {
t.Fatalf("failed to git commit: %v", err)
}
// Create .beads directory and initialize
beadsDir := filepath.Join(tmpDir, ".beads")
if err := os.MkdirAll(beadsDir, 0750); err != nil {
t.Fatalf("failed to create .beads dir: %v", err)
}
// Create minimal issues.jsonl
jsonlPath := filepath.Join(beadsDir, "issues.jsonl")
if err := os.WriteFile(jsonlPath, []byte(""), 0644); err != nil {
t.Fatalf("failed to create issues.jsonl: %v", err)
}
// Test that branchExistsLocal returns false for non-existent branch
// Note: We need to run this from tmpDir context since branchExistsLocal uses git in cwd
ctx := context.Background()
origDir, _ := os.Getwd()
if err := os.Chdir(tmpDir); err != nil {
t.Fatalf("failed to chdir: %v", err)
}
defer os.Chdir(origDir)
if branchExistsLocal(ctx, "beads-sync") {
t.Error("branchExistsLocal should return false for non-existent branch")
}
// Create the branch
cmd = exec.Command("git", "branch", "beads-sync")
cmd.Dir = tmpDir
if err := cmd.Run(); err != nil {
t.Fatalf("failed to create branch: %v", err)
}
// Now it should exist
if !branchExistsLocal(ctx, "beads-sync") {
t.Error("branchExistsLocal should return true for existing branch")
}
}
func TestHasChangesInWorktreeDir(t *testing.T) {
// Create a temp directory with a git repo
tmpDir, err := os.MkdirTemp("", "bd-worktree-changes-test")
if err != nil {
t.Fatalf("failed to create temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
// Initialize git repo
cmd := exec.Command("git", "init")
cmd.Dir = tmpDir
if err := cmd.Run(); err != nil {
t.Fatalf("failed to init git: %v", err)
}
// Configure git user
cmd = exec.Command("git", "config", "user.email", "test@test.com")
cmd.Dir = tmpDir
_ = cmd.Run()
cmd = exec.Command("git", "config", "user.name", "Test User")
cmd.Dir = tmpDir
_ = cmd.Run()
// Create and commit initial file
testFile := filepath.Join(tmpDir, "test.txt")
if err := os.WriteFile(testFile, []byte("test"), 0644); err != nil {
t.Fatalf("failed to create test file: %v", err)
}
cmd = exec.Command("git", "add", ".")
cmd.Dir = tmpDir
_ = cmd.Run()
cmd = exec.Command("git", "commit", "-m", "initial")
cmd.Dir = tmpDir
_ = cmd.Run()
ctx := context.Background()
// No changes initially
hasChanges, err := hasChangesInWorktreeDir(ctx, tmpDir)
if err != nil {
t.Fatalf("hasChangesInWorktreeDir failed: %v", err)
}
if hasChanges {
t.Error("should have no changes initially")
}
// Add uncommitted file
newFile := filepath.Join(tmpDir, "new.txt")
if err := os.WriteFile(newFile, []byte("new"), 0644); err != nil {
t.Fatalf("failed to create new file: %v", err)
}
hasChanges, err = hasChangesInWorktreeDir(ctx, tmpDir)
if err != nil {
t.Fatalf("hasChangesInWorktreeDir failed: %v", err)
}
if !hasChanges {
t.Error("should have changes after adding file")
}
}