package git import ( "context" "os" "strings" "testing" ) func setupTestRepo(t *testing.T) (string, func()) { t.Helper() // Create a temporary directory for the test repository tempDir, err := os.MkdirTemp("", "git-test-") if err != nil { t.Fatalf("Failed to create temp directory: %v", err) } // Change to the temp directory oldDir, err := os.Getwd() if err != nil { os.RemoveAll(tempDir) t.Fatalf("Failed to get current directory: %v", err) } if err := os.Chdir(tempDir); err != nil { os.RemoveAll(tempDir) t.Fatalf("Failed to change to temp directory: %v", err) } // Initialize a git repository ctx := context.Background() if _, err := Run(ctx, "init"); err != nil { os.RemoveAll(tempDir) t.Fatalf("Failed to initialize git repo: %v", err) } // Configure git user if _, err := Run(ctx, "config", "user.name", "Test User"); err != nil { os.RemoveAll(tempDir) t.Fatalf("Failed to configure git user: %v", err) } if _, err := Run(ctx, "config", "user.email", "test@example.com"); err != nil { os.RemoveAll(tempDir) t.Fatalf("Failed to configure git email: %v", err) } // Create an initial commit createTestFile(t, "README.md", "# Test Repository") if _, err := Run(ctx, "add", "."); err != nil { os.RemoveAll(tempDir) t.Fatalf("Failed to add files: %v", err) } if _, err := Run(ctx, "commit", "-m", "Initial commit"); err != nil { os.RemoveAll(tempDir) t.Fatalf("Failed to make initial commit: %v", err) } // Create a cleanup function cleanup := func() { os.Chdir(oldDir) os.RemoveAll(tempDir) } return tempDir, cleanup } func createTestFile(t *testing.T, name, content string) { t.Helper() if err := os.WriteFile(name, []byte(content), 0644); err != nil { t.Fatalf("Failed to create test file: %v", err) } } func TestCurrentBranch(t *testing.T) { _, cleanup := setupTestRepo(t) defer cleanup() ctx := context.Background() // Initially should be on main branch branch, err := CurrentBranch(ctx) if err != nil { t.Fatalf("CurrentBranch failed: %v", err) } // The default branch name could be "main" or "master" depending on git version if branch != "main" && branch != "master" { t.Errorf("Expected branch to be 'main' or 'master', got '%s'", branch) } // Create a new branch if _, err := Run(ctx, "checkout", "-b", "feature"); err != nil { t.Fatalf("Failed to create feature branch: %v", err) } // Check current branch again branch, err = CurrentBranch(ctx) if err != nil { t.Fatalf("CurrentBranch failed: %v", err) } if branch != "feature" { t.Errorf("Expected branch to be 'feature', got '%s'", branch) } } func TestCreateBranch(t *testing.T) { _, cleanup := setupTestRepo(t) defer cleanup() ctx := context.Background() // Create a new branch err := CreateBranch(ctx, "test-branch") if err != nil { t.Fatalf("CreateBranch failed: %v", err) } // Check current branch branch, err := CurrentBranch(ctx) if err != nil { t.Fatalf("CurrentBranch failed: %v", err) } if branch != "test-branch" { t.Errorf("Expected branch to be 'test-branch', got '%s'", branch) } } func TestSwitchBranch(t *testing.T) { _, cleanup := setupTestRepo(t) defer cleanup() ctx := context.Background() // Create a new branch if _, err := Run(ctx, "checkout", "-b", "feature"); err != nil { t.Fatalf("Failed to create feature branch: %v", err) } // Get the current branch to determine if it's main or master currentBranch, err := CurrentBranch(ctx) if err != nil { t.Fatalf("CurrentBranch failed: %v", err) } // Switch back to main/master var targetBranch string if currentBranch == "feature" { // We need to determine what the original branch was // Let's check if main exists if _, err := Run(ctx, "rev-parse", "--verify", "main"); err == nil { targetBranch = "main" } else if _, err := Run(ctx, "rev-parse", "--verify", "master"); err == nil { targetBranch = "master" } else { t.Fatalf("Neither main nor master branch exists") } } err = SwitchBranch(ctx, targetBranch) if err != nil { t.Fatalf("SwitchBranch failed: %v", err) } // Check current branch branch, err := CurrentBranch(ctx) if err != nil { t.Fatalf("CurrentBranch failed: %v", err) } if branch != targetBranch { t.Errorf("Expected branch to be '%s', got '%s'", targetBranch, branch) } } func TestLocalBranches(t *testing.T) { _, cleanup := setupTestRepo(t) defer cleanup() ctx := context.Background() // Initially should have just one branch branches, err := LocalBranches(ctx) if err != nil { t.Fatalf("LocalBranches failed: %v", err) } if len(branches) != 1 { t.Errorf("Expected 1 branch, got %d", len(branches)) } // Create a new branch if _, err := Run(ctx, "checkout", "-b", "feature"); err != nil { t.Fatalf("Failed to create feature branch: %v", err) } // Should now have two branches branches, err = LocalBranches(ctx) if err != nil { t.Fatalf("LocalBranches failed: %v", err) } if len(branches) != 2 { t.Errorf("Expected 2 branches, got %d", len(branches)) } } func TestIsCleanWorkingDir(t *testing.T) { _, cleanup := setupTestRepo(t) defer cleanup() ctx := context.Background() // Initially should be clean clean, err := IsCleanWorkingDir(ctx) if err != nil { t.Fatalf("IsCleanWorkingDir failed: %v", err) } if !clean { t.Error("Expected working directory to be clean") } // Create a file createTestFile(t, "test.txt", "test content") // Should now be dirty clean, err = IsCleanWorkingDir(ctx) if err != nil { t.Fatalf("IsCleanWorkingDir failed: %v", err) } if clean { t.Error("Expected working directory to be dirty") } } func TestAddAll(t *testing.T) { _, cleanup := setupTestRepo(t) defer cleanup() ctx := context.Background() // Create a file createTestFile(t, "test.txt", "test content") // Add the file err := AddAll(ctx) if err != nil { t.Fatalf("AddAll failed: %v", err) } // Note: After adding files, the working directory is not necessarily clean // because the files are staged but not committed. This is normal git behavior. // So we'll just check that there are no unstaged changes. // Check if there are any untracked files output, err := Run(ctx, "status", "--porcelain") if err != nil { t.Fatalf("Failed to get git status: %v", err) } // If there are any untracked files, they would show up with ?? at the beginning for i := range output { line := string(output[i]) if len(line) > 2 && strings.HasPrefix(line, "??") { t.Errorf("Found untracked file: %s", line[3:]) } } } func TestCommit(t *testing.T) { _, cleanup := setupTestRepo(t) defer cleanup() ctx := context.Background() // Create and add a file createTestFile(t, "test.txt", "test content") if err := AddAll(ctx); err != nil { t.Fatalf("AddAll failed: %v", err) } // Commit the file err := Commit(ctx, "test commit") if err != nil { t.Fatalf("Commit failed: %v", err) } // Should now be clean clean, err := IsCleanWorkingDir(ctx) if err != nil { t.Fatalf("IsCleanWorkingDir failed: %v", err) } if !clean { t.Error("Expected working directory to be clean after commit") } } func TestDeleteBranch(t *testing.T) { _, cleanup := setupTestRepo(t) defer cleanup() ctx := context.Background() // Create a new branch if _, err := Run(ctx, "checkout", "-b", "feature"); err != nil { t.Fatalf("Failed to create feature branch: %v", err) } // Switch back to main/master var targetBranch string if _, err := Run(ctx, "rev-parse", "--verify", "main"); err == nil { targetBranch = "main" } else if _, err := Run(ctx, "rev-parse", "--verify", "master"); err == nil { targetBranch = "master" } else { t.Fatalf("Neither main nor master branch exists") } if _, err := Run(ctx, "checkout", targetBranch); err != nil { t.Fatalf("Failed to switch back to %s: %v", targetBranch, err) } // Delete the branch err := DeleteBranch(ctx, "feature") if err != nil { t.Fatalf("DeleteBranch failed: %v", err) } // Should now have just one branch branches, err := LocalBranches(ctx) if err != nil { t.Fatalf("LocalBranches failed: %v", err) } if len(branches) != 1 { t.Errorf("Expected 1 branch after delete, got %d", len(branches)) } } func TestGetRemoteURL(t *testing.T) { _, cleanup := setupTestRepo(t) defer cleanup() ctx := context.Background() // Add a remote if _, err := Run(ctx, "remote", "add", "origin", "https://github.com/user/repo.git"); err != nil { t.Fatalf("Failed to add remote: %v", err) } // Get the remote URL url, err := GetRemoteURL(ctx, "origin") if err != nil { t.Fatalf("GetRemoteURL failed: %v", err) } expectedURL := "https://github.com/user/repo.git" if url != expectedURL { t.Errorf("Expected URL to be '%s', got '%s'", expectedURL, url) } } func TestFetch(t *testing.T) { _, cleanup := setupTestRepo(t) defer cleanup() ctx := context.Background() // Add a remote if _, err := Run(ctx, "remote", "add", "origin", "https://github.com/user/repo.git"); err != nil { t.Fatalf("Failed to add remote: %v", err) } // Fetch from remote err := Fetch(ctx) if err != nil { // We expect this to fail since the remote URL is fake // But we want to test that the function is called correctly if !strings.Contains(err.Error(), "could not read Username") && !strings.Contains(err.Error(), "Could not resolve host") { t.Fatalf("Fetch failed with unexpected error: %v", err) } } } func TestRebaseOntoTracking(t *testing.T) { _, cleanup := setupTestRepo(t) defer cleanup() ctx := context.Background() // Add a remote if _, err := Run(ctx, "remote", "add", "origin", "https://github.com/user/repo.git"); err != nil { t.Fatalf("Failed to add remote: %v", err) } // Set up tracking branch branch, err := CurrentBranch(ctx) if err != nil { t.Fatalf("CurrentBranch failed: %v", err) } if _, err := Run(ctx, "push", "-u", "origin", branch); err != nil { // We expect this to fail since the remote URL is fake // But we want to test that the function is called correctly if !strings.Contains(err.Error(), "could not read Username") && !strings.Contains(err.Error(), "Could not resolve host") { t.Fatalf("Push failed with unexpected error: %v", err) } } // Test rebase err = RebaseOntoTracking(ctx) if err != nil { // We expect this to fail since the remote URL is fake or there's no upstream // But we want to test that the function is called correctly if !strings.Contains(err.Error(), "could not read Username") && !strings.Contains(err.Error(), "Could not resolve host") && !strings.Contains(err.Error(), "no upstream configured") { t.Fatalf("RebaseOntoTracking failed with unexpected error: %v", err) } } } func TestPushCurrent(t *testing.T) { _, cleanup := setupTestRepo(t) defer cleanup() ctx := context.Background() // Add a remote if _, err := Run(ctx, "remote", "add", "origin", "https://github.com/user/repo.git"); err != nil { t.Fatalf("Failed to add remote: %v", err) } // Test push without force err := PushCurrent(ctx, false) if err != nil { // We expect this to fail since the remote URL is fake // But we want to test that the function is called correctly if !strings.Contains(err.Error(), "could not read Username") && !strings.Contains(err.Error(), "Could not resolve host") { t.Fatalf("PushCurrent failed with unexpected error: %v", err) } } // Test push with force err = PushCurrent(ctx, true) if err != nil { // We expect this to fail since the remote URL is fake // But we want to test that the function is called correctly if !strings.Contains(err.Error(), "could not read Username") && !strings.Contains(err.Error(), "Could not resolve host") { t.Fatalf("PushCurrent failed with unexpected error: %v", err) } } } func TestUpstreamFor(t *testing.T) { _, cleanup := setupTestRepo(t) defer cleanup() ctx := context.Background() // Add a remote if _, err := Run(ctx, "remote", "add", "origin", "https://github.com/user/repo.git"); err != nil { t.Fatalf("Failed to add remote: %v", err) } // Get current branch branch, err := CurrentBranch(ctx) if err != nil { t.Fatalf("CurrentBranch failed: %v", err) } // Set up tracking branch if _, err := Run(ctx, "push", "-u", "origin", branch); err != nil { // We expect this to fail since the remote URL is fake // But we want to test that the function is called correctly if !strings.Contains(err.Error(), "could not read Username") && !strings.Contains(err.Error(), "Could not resolve host") { t.Fatalf("Push failed with unexpected error: %v", err) } } // Test UpstreamFor upstream, err := UpstreamFor(ctx, branch) if err != nil { // We expect this to fail since the remote URL is fake or there's no upstream // But we want to test that the function is called correctly if !strings.Contains(err.Error(), "no upstream configured") && !strings.Contains(err.Error(), "could not read Username") && !strings.Contains(err.Error(), "Could not resolve host") { t.Fatalf("UpstreamFor failed with unexpected error: %v", err) } } else { // If it succeeds, check the format expected := "origin/" + branch if upstream != expected { t.Errorf("Expected upstream to be '%s', got '%s'", expected, upstream) } } } func TestGitError(t *testing.T) { err := fmtError("test error") if err == nil { t.Fatal("Expected fmtError to return an error, got nil") } gitErr, ok := err.(*gitError) if !ok { t.Fatalf("Expected error to be of type *gitError, got %T", err) } if gitErr.msg != "test error" { t.Errorf("Expected error message to be 'test error', got '%s'", gitErr.msg) } if gitErr.Error() != "test error" { t.Errorf("Expected Error() to return 'test error', got '%s'", gitErr.Error()) } }