package git import ( "errors" "path/filepath" "testing" ) var testEnv = []string{ "GIT_AUTHOR_NAME=Test", "GIT_AUTHOR_EMAIL=test@byre.invalid", "GIT_COMMITTER_NAME=Test", "GIT_COMMITTER_EMAIL=test@byre.invalid", } func mustGit(t *testing.T, dir string, args ...string) string { t.Helper() out, err := runEnv(dir, testEnv, args...) if err != nil { t.Fatal(err) } return out } func pushFixture(t *testing.T) (string, string) { t.Helper() bare := filepath.Join(t.TempDir(), "remote.git") mustGit(t, t.TempDir(), "init", "--quiet", "--bare", "--initial-branch=main", bare) repo := t.TempDir() mustGit(t, repo, "init", "--quiet", "--initial-branch=main") mustGit(t, repo, "commit", "--quiet", "--allow-empty", "-m", "root") mustGit(t, repo, "remote", "add", Remote, bare) mustGit(t, repo, "push", "--quiet", Remote, "main") return repo, bare } func moveRemote(t *testing.T, bare string) string { t.Helper() other := t.TempDir() mustGit(t, other, "clone", "--quiet", bare, ".") mustGit(t, other, "commit", "--quiet", "--allow-empty", "-m", "elsewhere") mustGit(t, other, "push", "--quiet", Remote, "main") return mustGit(t, other, "rev-parse", "HEAD") } func squash(t *testing.T, repo, message string) string { t.Helper() tree := mustGit(t, repo, "rev-parse", "main^{tree}") return mustGit(t, repo, "commit-tree", tree, "-p", "main", "-m", message) } func TestPushClean(t *testing.T) { repo, bare := pushFixture(t) commit := squash(t, repo, "task") err := Push(repo, commit, "main") if err != nil { t.Fatal(err) } if got := mustGit(t, bare, "rev-parse", "main"); got != commit { t.Fatalf("remote main at %s, want %s", got, commit) } err = FastForward(repo, commit) if err != nil { t.Fatal(err) } if got := mustGit(t, repo, "rev-parse", "main"); got != commit { t.Fatalf("local main at %s, want %s", got, commit) } } func TestPushRemoteMoved(t *testing.T) { repo, bare := pushFixture(t) moved := moveRemote(t, bare) commit := squash(t, repo, "task") err := Push(repo, commit, "main") if !errors.Is(err, ErrRemoteMoved) { t.Fatalf("push onto a moved remote: %v, want ErrRemoteMoved", err) } if got := mustGit(t, bare, "rev-parse", "main"); got != moved { t.Fatalf("remote main at %s after a refused push, want %s", got, moved) } err = FetchFastForward(repo, "main") if err != nil { t.Fatal(err) } if got := mustGit(t, repo, "rev-parse", "main"); got != moved { t.Fatalf("local main at %s after fetch, want %s", got, moved) } commit = squash(t, repo, "task again") err = Push(repo, commit, "main") if err != nil { t.Fatal(err) } if got := mustGit(t, bare, "rev-parse", "main"); got != commit { t.Fatalf("remote main at %s, want %s", got, commit) } } func TestPushOtherFailure(t *testing.T) { repo, _ := pushFixture(t) mustGit(t, repo, "remote", "set-url", Remote, filepath.Join(t.TempDir(), "missing.git")) err := Push(repo, squash(t, repo, "task"), "main") if err == nil || errors.Is(err, ErrRemoteMoved) { t.Fatalf("push to a missing remote: %v, want an error other than ErrRemoteMoved", err) } t.Logf("push error: %v", err) }