//go:build integration package fix import ( "os" "os/exec" "path/filepath" "strings" "testing" ) // setupTestGitRepoIntegration creates a temporary git repository with a .beads directory func setupTestGitRepoIntegration(t *testing.T) string { t.Helper() dir := t.TempDir() beadsDir := filepath.Join(dir, ".beads") if err := os.MkdirAll(beadsDir, 0755); err != nil { t.Fatalf("failed to create .beads directory: %v", err) } // Initialize git repo cmd := exec.Command("git", "init") cmd.Dir = dir if err := cmd.Run(); err != nil { t.Fatalf("failed to init git repo: %v", err) } // Configure git user for commits cmd = exec.Command("git", "config", "user.email", "test@test.com") cmd.Dir = dir _ = cmd.Run() cmd = exec.Command("git", "config", "user.name", "Test User") cmd.Dir = dir _ = cmd.Run() return dir } // runGitIntegration runs a git command and returns output func runGitIntegration(t *testing.T, dir string, args ...string) string { t.Helper() cmd := exec.Command("git", args...) cmd.Dir = dir output, err := cmd.CombinedOutput() if err != nil { t.Logf("git %v: %s", args, output) } return string(output) } // TestSyncBranchHealth_LocalAndRemoteDiverged tests fix when branches diverged func TestSyncBranchHealth_LocalAndRemoteDiverged(t *testing.T) { // Setup bare remote repo remoteDir := t.TempDir() runGitIntegration(t, remoteDir, "init", "--bare") // Setup local repo dir := setupTestGitRepoIntegration(t) runGitIntegration(t, dir, "remote", "add", "origin", remoteDir) // Create main branch with initial commit testFile := filepath.Join(dir, "test.txt") if err := os.WriteFile(testFile, []byte("main content"), 0600); err != nil { t.Fatalf("failed to create test file: %v", err) } runGitIntegration(t, dir, "add", "test.txt") runGitIntegration(t, dir, "commit", "-m", "initial commit") runGitIntegration(t, dir, "branch", "-M", "main") runGitIntegration(t, dir, "push", "-u", "origin", "main") // Create sync branch runGitIntegration(t, dir, "checkout", "-b", "beads-sync") syncFile := filepath.Join(dir, "sync.txt") if err := os.WriteFile(syncFile, []byte("sync content"), 0600); err != nil { t.Fatalf("failed to create sync file: %v", err) } runGitIntegration(t, dir, "add", "sync.txt") runGitIntegration(t, dir, "commit", "-m", "sync commit") runGitIntegration(t, dir, "push", "-u", "origin", "beads-sync") // Simulate divergence: update main runGitIntegration(t, dir, "checkout", "main") if err := os.WriteFile(testFile, []byte("updated main content"), 0600); err != nil { t.Fatalf("failed to update test file: %v", err) } runGitIntegration(t, dir, "add", "test.txt") runGitIntegration(t, dir, "commit", "-m", "update main") runGitIntegration(t, dir, "push", "origin", "main") // Now beads-sync is behind main - fix it err := SyncBranchHealth(dir, "beads-sync") if err != nil { t.Fatalf("unexpected error: %v", err) } // Verify beads-sync was reset to main runGitIntegration(t, dir, "checkout", "beads-sync") runGitIntegration(t, dir, "pull", "origin", "beads-sync") // Check that beads-sync now has main's content content, err := os.ReadFile(testFile) if err != nil { t.Fatalf("failed to read test file: %v", err) } if string(content) != "updated main content" { t.Errorf("expected beads-sync to have main's content, got: %s", content) } // Check that sync.txt no longer exists (branch was reset) if _, err := os.Stat(syncFile); !os.IsNotExist(err) { t.Error("sync.txt should not exist after reset to main") } } // TestSyncBranchHealth_UncommittedChanges tests fix with uncommitted changes func TestSyncBranchHealth_UncommittedChanges(t *testing.T) { // Setup bare remote repo remoteDir := t.TempDir() runGitIntegration(t, remoteDir, "init", "--bare") // Setup local repo dir := setupTestGitRepoIntegration(t) runGitIntegration(t, dir, "remote", "add", "origin", remoteDir) // Create main branch with initial commit testFile := filepath.Join(dir, "test.txt") if err := os.WriteFile(testFile, []byte("main content"), 0600); err != nil { t.Fatalf("failed to create test file: %v", err) } runGitIntegration(t, dir, "add", "test.txt") runGitIntegration(t, dir, "commit", "-m", "initial commit") runGitIntegration(t, dir, "branch", "-M", "main") runGitIntegration(t, dir, "push", "-u", "origin", "main") // Create sync branch and push it runGitIntegration(t, dir, "checkout", "-b", "beads-sync") runGitIntegration(t, dir, "push", "-u", "origin", "beads-sync") // Add uncommitted changes to sync branch dirtyFile := filepath.Join(dir, "dirty.txt") if err := os.WriteFile(dirtyFile, []byte("uncommitted"), 0600); err != nil { t.Fatalf("failed to create dirty file: %v", err) } // Checkout main to allow sync branch reset runGitIntegration(t, dir, "checkout", "main") // Fix should succeed - it resets the branch, not the working tree err := SyncBranchHealth(dir, "beads-sync") if err != nil { t.Fatalf("unexpected error: %v", err) } // Verify sync branch was reset output := runGitIntegration(t, dir, "log", "--oneline", "beads-sync") if !strings.Contains(output, "initial commit") { t.Errorf("beads-sync should be reset to main, got log: %s", output) } } // TestSyncBranchHealth_RemoteUnreachable tests fix when remote is unreachable func TestSyncBranchHealth_RemoteUnreachable(t *testing.T) { dir := setupTestGitRepoIntegration(t) // Add unreachable remote runGitIntegration(t, dir, "remote", "add", "origin", "https://nonexistent.example.com/repo.git") // Create main branch with initial commit testFile := filepath.Join(dir, "test.txt") if err := os.WriteFile(testFile, []byte("main content"), 0600); err != nil { t.Fatalf("failed to create test file: %v", err) } runGitIntegration(t, dir, "add", "test.txt") runGitIntegration(t, dir, "commit", "-m", "initial commit") runGitIntegration(t, dir, "branch", "-M", "main") // Create local sync branch runGitIntegration(t, dir, "checkout", "-b", "beads-sync") runGitIntegration(t, dir, "checkout", "main") // Fix should fail when trying to fetch err := SyncBranchHealth(dir, "beads-sync") if err == nil { t.Error("expected error when remote is unreachable") } if err != nil && !strings.Contains(err.Error(), "failed to fetch") { t.Errorf("expected fetch error, got: %v", err) } } // TestSyncBranchHealth_CurrentlyOnSyncBranch tests error when on sync branch func TestSyncBranchHealth_CurrentlyOnSyncBranch(t *testing.T) { // Setup bare remote repo remoteDir := t.TempDir() runGitIntegration(t, remoteDir, "init", "--bare") // Setup local repo dir := setupTestGitRepoIntegration(t) runGitIntegration(t, dir, "remote", "add", "origin", remoteDir) // Create main branch with initial commit testFile := filepath.Join(dir, "test.txt") if err := os.WriteFile(testFile, []byte("main content"), 0600); err != nil { t.Fatalf("failed to create test file: %v", err) } runGitIntegration(t, dir, "add", "test.txt") runGitIntegration(t, dir, "commit", "-m", "initial commit") runGitIntegration(t, dir, "branch", "-M", "main") runGitIntegration(t, dir, "push", "-u", "origin", "main") // Create and checkout sync branch runGitIntegration(t, dir, "checkout", "-b", "beads-sync") runGitIntegration(t, dir, "push", "-u", "origin", "beads-sync") // Try to fix while on sync branch err := SyncBranchHealth(dir, "beads-sync") if err == nil { t.Error("expected error when currently on sync branch") } if err != nil && !strings.Contains(err.Error(), "currently on beads-sync branch") { t.Errorf("expected 'currently on branch' error, got: %v", err) } }