Operations: - Clone, Checkout, Fetch, Pull, Push - Add, Commit, CommitAll - Status, CurrentBranch, HasUncommittedChanges, RemoteURL - Merge, Rebase, AbortMerge, AbortRebase - CreateBranch, DeleteBranch, Rev, IsAncestor Error handling: - Detects ErrNotARepo, ErrMergeConflict, ErrAuthFailure, ErrRebaseConflict - Wraps git stderr in error messages Closes gt-u1j.3 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
189 lines
3.8 KiB
Go
189 lines
3.8 KiB
Go
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))
|
|
}
|
|
}
|