package git_test import ( "context" "os" "os/exec" "path/filepath" "testing" "superwork-tui/internal/git" ) // initTestRepo creates a temporary git repo with one commit on the default branch. func initTestRepo(t *testing.T) string { t.Helper() dir := t.TempDir() run := func(args ...string) { t.Helper() cmd := exec.Command("git", args...) cmd.Dir = dir out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("git %v: %v\n%s", args, err, out) } } run("init") run("config", "user.email", "test@test.com") run("config", "user.name", "test") f := filepath.Join(dir, "file.txt") if err := os.WriteFile(f, []byte("hello"), 0o644); err != nil { t.Fatal(err) } run("add", ".") run("commit", "-m", "init") return dir } func TestHasChanges_Clean(t *testing.T) { dir := initTestRepo(t) has, err := git.HasChanges(context.Background(), dir) if err != nil { t.Fatalf("unexpected error: %v", err) } if has { t.Error("expected no changes on clean repo") } } func TestHasChanges_Dirty(t *testing.T) { dir := initTestRepo(t) f := filepath.Join(dir, "new.txt") if err := os.WriteFile(f, []byte("dirty"), 0o644); err != nil { t.Fatal(err) } has, err := git.HasChanges(context.Background(), dir) if err != nil { t.Fatalf("unexpected error: %v", err) } if !has { t.Error("expected changes with untracked file") } } func TestMergePreview_Clean(t *testing.T) { dir := initTestRepo(t) run := func(args ...string) { t.Helper() cmd := exec.Command("git", args...) cmd.Dir = dir out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("git %v: %v\n%s", args, err, out) } } // Create a branch with a non-conflicting new file. run("checkout", "-b", "feature") f := filepath.Join(dir, "feature.txt") if err := os.WriteFile(f, []byte("feature"), 0o644); err != nil { t.Fatal(err) } run("add", ".") run("commit", "-m", "feature commit") // Return to the original branch via checkout -. run("checkout", "-") result, err := git.MergePreview(context.Background(), git.MergePreviewOpts{ WorkspaceRoot: dir, Branch: "feature", }) // A fast-forward-prevented merge that succeeds may leave MERGE_HEAD; abort to clean up. defer func() { cmd := exec.Command("git", "-C", dir, "merge", "--abort") _ = cmd.Run() }() if err != nil { t.Fatalf("expected clean merge, got error: %v (output: %s)", err, result.Output) } if !result.Clean { t.Errorf("expected Clean=true, got false; conflicts: %v", result.Conflicts) } } func TestMergePreview_Conflict(t *testing.T) { dir := initTestRepo(t) run := func(args ...string) { t.Helper() cmd := exec.Command("git", args...) cmd.Dir = dir out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("git %v: %v\n%s", args, err, out) } } // Create a branch that modifies file.txt. run("checkout", "-b", "conflict-branch") f := filepath.Join(dir, "file.txt") if err := os.WriteFile(f, []byte("branch change"), 0o644); err != nil { t.Fatal(err) } run("add", ".") run("commit", "-m", "branch change") // Return to original branch and make a conflicting change to the same file. run("checkout", "-") if err := os.WriteFile(f, []byte("main change"), 0o644); err != nil { t.Fatal(err) } run("add", ".") run("commit", "-m", "main change") result, _ := git.MergePreview(context.Background(), git.MergePreviewOpts{ WorkspaceRoot: dir, Branch: "conflict-branch", }) // Abort the in-progress merge to leave repo clean for test teardown. defer func() { cmd := exec.Command("git", "-C", dir, "merge", "--abort") _ = cmd.Run() }() if result.Clean { t.Error("expected Clean=false for conflicting merge") } if len(result.Conflicts) == 0 { t.Errorf("expected at least one conflict entry, got none; output: %s", result.Output) } }