package git import ( "os" "os/exec" "path/filepath" "testing" ) func initTestRepo(t *testing.T) string { t.Helper() dir := t.TempDir() // Initialize repo cmd := exec.Command("git", "init") cmd.Dir = dir if err := cmd.Run(); err != nil { t.Fatalf("git init: %v", err) } // Configure 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() // Create initial commit testFile := filepath.Join(dir, "README.md") if err := os.WriteFile(testFile, []byte("# Test\n"), 0644); err != nil { t.Fatalf("write file: %v", err) } cmd = exec.Command("git", "add", ".") cmd.Dir = dir cmd.Run() cmd = exec.Command("git", "commit", "-m", "initial") cmd.Dir = dir cmd.Run() return dir } func TestCurrentBranch(t *testing.T) { dir := initTestRepo(t) g := NewGit(dir) branch, err := g.CurrentBranch() if err != nil { t.Fatalf("CurrentBranch: %v", err) } // Modern git uses "main", older uses "master" if branch != "main" && branch != "master" { t.Errorf("branch = %q, want main or master", branch) } } func TestStatus(t *testing.T) { dir := initTestRepo(t) g := NewGit(dir) // Should be clean initially status, err := g.Status() if err != nil { t.Fatalf("Status: %v", err) } if !status.Clean { t.Error("expected clean status") } // Add an untracked file testFile := filepath.Join(dir, "new.txt") if err := os.WriteFile(testFile, []byte("new"), 0644); err != nil { t.Fatalf("write file: %v", err) } status, err = g.Status() if err != nil { t.Fatalf("Status: %v", err) } if status.Clean { t.Error("expected dirty status") } if len(status.Untracked) != 1 { t.Errorf("untracked = %d, want 1", len(status.Untracked)) } } func TestAddAndCommit(t *testing.T) { dir := initTestRepo(t) g := NewGit(dir) // Create a new file testFile := filepath.Join(dir, "new.txt") if err := os.WriteFile(testFile, []byte("new content"), 0644); err != nil { t.Fatalf("write file: %v", err) } // Add and commit if err := g.Add("new.txt"); err != nil { t.Fatalf("Add: %v", err) } if err := g.Commit("add new file"); err != nil { t.Fatalf("Commit: %v", err) } // Should be clean status, err := g.Status() if err != nil { t.Fatalf("Status: %v", err) } if !status.Clean { t.Error("expected clean after commit") } } func TestHasUncommittedChanges(t *testing.T) { dir := initTestRepo(t) g := NewGit(dir) has, err := g.HasUncommittedChanges() if err != nil { t.Fatalf("HasUncommittedChanges: %v", err) } if has { t.Error("expected no changes initially") } // Modify a file testFile := filepath.Join(dir, "README.md") if err := os.WriteFile(testFile, []byte("modified"), 0644); err != nil { t.Fatalf("write file: %v", err) } has, err = g.HasUncommittedChanges() if err != nil { t.Fatalf("HasUncommittedChanges: %v", err) } if !has { t.Error("expected changes after modify") } } func TestCheckout(t *testing.T) { dir := initTestRepo(t) g := NewGit(dir) // Create a new branch if err := g.CreateBranch("feature"); err != nil { t.Fatalf("CreateBranch: %v", err) } // Checkout the new branch if err := g.Checkout("feature"); err != nil { t.Fatalf("Checkout: %v", err) } branch, _ := g.CurrentBranch() if branch != "feature" { t.Errorf("branch = %q, want feature", branch) } } func TestNotARepo(t *testing.T) { dir := t.TempDir() // Empty dir, not a git repo g := NewGit(dir) _, err := g.CurrentBranch() if err != ErrNotARepo { t.Errorf("expected ErrNotARepo, got %v", err) } } func TestRev(t *testing.T) { dir := initTestRepo(t) g := NewGit(dir) hash, err := g.Rev("HEAD") if err != nil { t.Fatalf("Rev: %v", err) } // Should be a 40-char hex string if len(hash) != 40 { t.Errorf("hash length = %d, want 40", len(hash)) } }