feat: add git wrapper package for subprocess operations
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>
This commit is contained in:
188
internal/git/git_test.go
Normal file
188
internal/git/git_test.go
Normal file
@@ -0,0 +1,188 @@
|
||||
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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user