Files
2026-06-23 05:02:15 +08:00

205 lines
5.3 KiB
Go

package git_test
import (
"context"
"os"
"os/exec"
"path/filepath"
"testing"
"superwork-tui/internal/git"
)
// initBareAndClone creates a bare origin repo and a local clone with one commit.
func initBareAndClone(t *testing.T) (origin, clone string) {
t.Helper()
origin = t.TempDir()
clone = t.TempDir()
run := func(dir string, args ...string) {
t.Helper()
cmd := exec.Command("git", args...)
cmd.Dir = dir
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("git %v: %v\n%s", args, err, out)
}
}
run(origin, "init", "--bare")
run(clone, "init")
run(clone, "config", "user.email", "test@test.com")
run(clone, "config", "user.name", "test")
run(clone, "remote", "add", "origin", origin)
f := filepath.Join(clone, "README")
if err := os.WriteFile(f, []byte("init"), 0o644); err != nil {
t.Fatal(err)
}
run(clone, "add", ".")
run(clone, "commit", "-m", "init")
run(clone, "push", "origin", "HEAD:refs/heads/main")
run(clone, "push", "origin", "HEAD:refs/heads/build")
return origin, clone
}
func TestCheckBranchSync_SameBranch_Unavailable(t *testing.T) {
_, clone := initBareAndClone(t)
status, err := git.CheckBranchSync(context.Background(), git.BranchSyncOpts{
WorkspaceRoot: clone,
DevBranch: "main",
AutoBuildBranch: "main",
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !status.Unavailable {
t.Error("same branch should be Unavailable")
}
}
func TestCheckBranchSync_NotGitRepo_Unavailable(t *testing.T) {
dir := t.TempDir()
status, err := git.CheckBranchSync(context.Background(), git.BranchSyncOpts{
WorkspaceRoot: dir,
DevBranch: "main",
AutoBuildBranch: "build",
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !status.Unavailable {
t.Error("non-git-repo should be Unavailable")
}
}
func TestCheckBranchSync_ZeroBehind(t *testing.T) {
_, clone := initBareAndClone(t)
status, err := git.CheckBranchSync(context.Background(), git.BranchSyncOpts{
WorkspaceRoot: clone,
DevBranch: "main",
AutoBuildBranch: "build",
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if status.Unavailable {
t.Errorf("should be available, reason: %s", status.Reason)
}
if status.Behind != 0 {
t.Errorf("Behind = %d, want 0 (branches are in sync)", status.Behind)
}
}
func TestCheckBranchSync_NonZeroBehind(t *testing.T) {
_, clone := initBareAndClone(t)
run := func(args ...string) {
t.Helper()
cmd := exec.Command("git", args...)
cmd.Dir = clone
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("git %v: %v\n%s", args, err, out)
}
}
// Advance main by two commits; build stays behind.
for i := 0; i < 2; i++ {
f := filepath.Join(clone, "f")
if err := os.WriteFile(f, []byte{byte(i)}, 0o644); err != nil {
t.Fatal(err)
}
run("add", ".")
run("commit", "-m", "advance")
}
run("push", "origin", "HEAD:refs/heads/main")
status, err := git.CheckBranchSync(context.Background(), git.BranchSyncOpts{
WorkspaceRoot: clone,
DevBranch: "main",
AutoBuildBranch: "build",
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if status.Unavailable {
t.Fatalf("should be available, reason: %s", status.Reason)
}
if status.Behind != 2 {
t.Errorf("Behind = %d, want 2", status.Behind)
}
}
func TestRunBranchSync_AdvancesAutoBuild(t *testing.T) {
origin, clone := initBareAndClone(t)
run := func(args ...string) {
t.Helper()
cmd := exec.Command("git", args...)
cmd.Dir = clone
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("git %v: %v\n%s", args, err, out)
}
}
f := filepath.Join(clone, "f")
if err := os.WriteFile(f, []byte("x"), 0o644); err != nil {
t.Fatal(err)
}
run("add", ".")
run("commit", "-m", "advance")
run("push", "origin", "HEAD:refs/heads/main")
revOf := func(ref string) string {
t.Helper()
cmd := exec.Command("git", "-C", origin, "rev-parse", ref)
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("rev-parse %s: %v\n%s", ref, err, out)
}
return string(out)
}
if revOf("main") == revOf("build") {
t.Fatal("precondition: build should be behind main")
}
if err := git.RunBranchSync(context.Background(), git.BranchSyncOpts{
WorkspaceRoot: clone,
DevBranch: "main",
AutoBuildBranch: "build",
}); err != nil {
t.Fatalf("RunBranchSync: %v", err)
}
if revOf("main") != revOf("build") {
t.Errorf("build should have advanced to main; main=%q build=%q", revOf("main"), revOf("build"))
}
}
func TestDeleteLocalBranch_NonExistent_NoOp(t *testing.T) {
root := initRepo(t)
err := git.DeleteLocalBranch(context.Background(), root, "nonexistent-branch")
if err != nil {
t.Errorf("DeleteLocalBranch on nonexistent branch should be no-op, got: %v", err)
}
}
func TestDeleteLocalBranch_Exists_Deletes(t *testing.T) {
root := initRepo(t)
run := func(args ...string) {
cmd := exec.Command("git", args...)
cmd.Dir = root
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("git %v: %v\n%s", args, err, out)
}
}
run("branch", "temp-branch")
err := git.DeleteLocalBranch(context.Background(), root, "temp-branch")
if err != nil {
t.Errorf("DeleteLocalBranch: %v", err)
}
// Verify branch is gone.
cmd := exec.Command("git", "-C", root, "show-ref", "--verify", "refs/heads/temp-branch")
if cmd.Run() == nil {
t.Error("branch should have been deleted")
}
}