package git import ( "os" "os/exec" "path/filepath" "strings" "testing" ) func setupTestRepo(t *testing.T) string { t.Helper() dir := t.TempDir() cmds := [][]string{ {"git", "init"}, {"git", "config", "user.email", "test@example.com"}, {"git", "config", "user.name", "Test"}, {"git", "checkout", "-b", "main"}, } for _, args := range cmds { cmd := exec.Command(args[0], args[1:]...) cmd.Dir = dir if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("%v: %s", err, out) } } // Create initial commit if err := os.WriteFile(filepath.Join(dir, "README.md"), []byte("# test\n"), 0o644); err != nil { t.Fatal(err) } for _, args := range [][]string{ {"git", "add", "README.md"}, {"git", "commit", "-m", "initial commit"}, } { cmd := exec.Command(args[0], args[1:]...) cmd.Dir = dir if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("%v: %s", err, out) } } return dir } func TestCurrentBranch(t *testing.T) { dir := setupTestRepo(t) c := &Client{GitPath: "git", RepoDir: dir} branch, err := c.CurrentBranch() if err != nil { t.Fatalf("unexpected error: %v", err) } if branch != "main" { t.Errorf("got branch %q, want 'main'", branch) } } func TestIsInsideWorkTree(t *testing.T) { dir := setupTestRepo(t) c := &Client{GitPath: "git", RepoDir: dir} if !c.IsInsideWorkTree() { t.Error("expected to be inside work tree") } c2 := &Client{GitPath: "git", RepoDir: t.TempDir()} if c2.IsInsideWorkTree() { t.Error("expected not to be inside work tree") } } func TestTopLevelDir(t *testing.T) { dir := setupTestRepo(t) c := &Client{GitPath: "git", RepoDir: dir} top, err := c.TopLevelDir() if err != nil { t.Fatalf("unexpected error: %v", err) } // Resolve symlinks for macOS /private/tmp vs /tmp resolved, _ := filepath.EvalSymlinks(dir) if top != resolved { t.Errorf("got %q, want %q", top, resolved) } } func TestRevParse(t *testing.T) { dir := setupTestRepo(t) c := &Client{GitPath: "git", RepoDir: dir} sha, err := c.RevParse("HEAD") if err != nil { t.Fatalf("unexpected error: %v", err) } if len(sha) != 40 { t.Errorf("expected 40 char SHA, got %d chars: %q", len(sha), sha) } } func TestFormatPatch(t *testing.T) { dir := setupTestRepo(t) // Create a feature branch with a commit for _, args := range [][]string{ {"git", "checkout", "-b", "feature"}, } { cmd := exec.Command(args[0], args[1:]...) cmd.Dir = dir if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("%v: %s", err, out) } } if err := os.WriteFile(filepath.Join(dir, "new-file.txt"), []byte("hello\n"), 0o644); err != nil { t.Fatal(err) } for _, args := range [][]string{ {"git", "add", "new-file.txt"}, {"git", "commit", "-m", "add new file"}, } { cmd := exec.Command(args[0], args[1:]...) cmd.Dir = dir if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("%v: %s", err, out) } } c := &Client{GitPath: "git", RepoDir: dir} patch, err := c.FormatPatch("main") if err != nil { t.Fatalf("unexpected error: %v", err) } if len(patch) == 0 { t.Fatal("expected non-empty patch") } if !strings.Contains(string(patch), "add new file") { t.Error("patch should contain commit message") } if !strings.Contains(string(patch), "new-file.txt") { t.Error("patch should reference the changed file") } } func TestFormatPatch_NoDiff(t *testing.T) { dir := setupTestRepo(t) c := &Client{GitPath: "git", RepoDir: dir} // Format-patch from HEAD to HEAD should produce empty patch patch, err := c.FormatPatch("HEAD") if err != nil { t.Fatalf("unexpected error: %v", err) } if len(patch) != 0 { t.Errorf("expected empty patch, got %d bytes", len(patch)) } } func TestConfig(t *testing.T) { dir := setupTestRepo(t) c := &Client{GitPath: "git", RepoDir: dir} cmd := exec.Command("git", "config", "tng.test-key", "test-value") cmd.Dir = dir if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("%v: %s", err, out) } val, err := c.Config("tng.test-key") if err != nil { t.Fatalf("Config: %v", err) } if val != "test-value" { t.Errorf("got %q, want 'test-value'", val) } } func TestRemotes(t *testing.T) { dir := setupTestRepo(t) // Add a remote cmd := exec.Command("git", "remote", "add", "origin", "git@tangled.sh:alice/repo") cmd.Dir = dir if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("%v: %s", err, out) } c := &Client{GitPath: "git", RepoDir: dir} remotes, err := c.Remotes() if err != nil { t.Fatalf("unexpected error: %v", err) } if len(remotes) != 1 { t.Fatalf("got %d remotes, want 1", len(remotes)) } if remotes[0].Name != "origin" { t.Errorf("remote name = %q, want 'origin'", remotes[0].Name) } }