224 lines
8.1 KiB
Go
224 lines
8.1 KiB
Go
package git
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestResolveUsesPushURLAndQueriesWithoutMutation(t *testing.T) {
|
|
repo := newRemoteFixture(t)
|
|
configBefore := gitText(t, repo.work, "config", "--get-all", "remote.origin.url") + "\x00" + gitText(t, repo.work, "config", "--get-all", "remote.origin.pushurl")
|
|
refsBefore := gitText(t, repo.bare, "show-ref", "--heads", "--tags")
|
|
|
|
endpoint, err := Resolve(context.Background(), repo.work, "origin")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if endpoint.PushURL != repo.bare {
|
|
t.Fatalf("push URL = %q, want %q", endpoint.PushURL, repo.bare)
|
|
}
|
|
if err := endpoint.Recheck(context.Background(), repo.work); err != nil {
|
|
t.Fatalf("Recheck() error = %v", err)
|
|
}
|
|
snapshot, err := endpoint.RequireCandidate(context.Background(), repo.work, "v1.2.3", repo.commit)
|
|
if err != nil {
|
|
t.Fatalf("RequireCandidate() error = %v", err)
|
|
}
|
|
if snapshot.Main.State != RefPresent || snapshot.Main.Object != repo.commit || snapshot.Tag.State != RefAbsent {
|
|
t.Fatalf("snapshot = %#v", snapshot)
|
|
}
|
|
configAfter := gitText(t, repo.work, "config", "--get-all", "remote.origin.url") + "\x00" + gitText(t, repo.work, "config", "--get-all", "remote.origin.pushurl")
|
|
refsAfter := gitText(t, repo.bare, "show-ref", "--heads", "--tags")
|
|
if configAfter != configBefore {
|
|
t.Fatalf("persistent remote config changed: before %q, after %q", configBefore, configAfter)
|
|
}
|
|
if refsAfter != refsBefore {
|
|
t.Fatalf("remote refs changed: before %q, after %q", refsBefore, refsAfter)
|
|
}
|
|
}
|
|
|
|
func TestResolveAppliesGitURLRewrites(t *testing.T) {
|
|
for _, kind := range []string{"insteadOf", "pushInsteadOf"} {
|
|
t.Run(kind, func(t *testing.T) {
|
|
repo := newRemoteFixture(t)
|
|
alias := "releasekit-test:"
|
|
if kind == "insteadOf" {
|
|
gitRun(t, repo.work, "config", "url."+repo.bare+".insteadOf", alias)
|
|
gitRun(t, repo.work, "remote", "set-url", "origin", alias)
|
|
gitRun(t, repo.work, "config", "--unset-all", "remote.origin.pushurl")
|
|
} else {
|
|
gitRun(t, repo.work, "config", "url."+repo.bare+".pushInsteadOf", alias)
|
|
gitRun(t, repo.work, "remote", "set-url", "origin", alias)
|
|
gitRun(t, repo.work, "config", "--unset-all", "remote.origin.pushurl")
|
|
}
|
|
endpoint, err := Resolve(context.Background(), repo.work, "origin")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if endpoint.PushURL != repo.bare {
|
|
t.Fatalf("effective push URL = %q, want %q", endpoint.PushURL, repo.bare)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestResolveRejectsMultipleDistinctPushDestinations(t *testing.T) {
|
|
repo := newRemoteFixture(t)
|
|
second := filepath.Join(t.TempDir(), "second.git")
|
|
gitRun(t, t.TempDir(), "init", "--bare", second)
|
|
gitRun(t, repo.work, "config", "--add", "remote.origin.pushurl", second)
|
|
_, err := Resolve(context.Background(), repo.work, "origin")
|
|
if !errors.Is(err, ErrMultiplePushURLs) {
|
|
t.Fatalf("Resolve() error = %v, want ErrMultiplePushURLs", err)
|
|
}
|
|
}
|
|
|
|
func TestRequireCandidateDistinguishesMissingMainTagConflictAndMainMismatch(t *testing.T) {
|
|
t.Run("missing main", func(t *testing.T) {
|
|
repo := newRemoteFixture(t)
|
|
gitRun(t, repo.bare, "update-ref", "-d", "refs/heads/main")
|
|
endpoint := resolveFixture(t, repo)
|
|
snapshot, err := endpoint.RequireCandidate(context.Background(), repo.work, "v1.2.3", repo.commit)
|
|
if !errors.Is(err, ErrRemoteMainMissing) || snapshot.Main.State != RefAbsent {
|
|
t.Fatalf("snapshot = %#v, error = %v", snapshot, err)
|
|
}
|
|
})
|
|
|
|
t.Run("existing tag", func(t *testing.T) {
|
|
repo := newRemoteFixture(t)
|
|
gitRun(t, repo.work, "tag", "v1.2.3", repo.commit)
|
|
gitRun(t, repo.work, "push", repo.bare, "refs/tags/v1.2.3:refs/tags/v1.2.3")
|
|
endpoint := resolveFixture(t, repo)
|
|
snapshot, err := endpoint.RequireCandidate(context.Background(), repo.work, "v1.2.3", repo.commit)
|
|
if !errors.Is(err, ErrRemoteTagConflict) || snapshot.Tag.State != RefConflict {
|
|
t.Fatalf("snapshot = %#v, error = %v", snapshot, err)
|
|
}
|
|
})
|
|
|
|
for _, relation := range []string{"ahead", "behind"} {
|
|
t.Run("main "+relation, func(t *testing.T) {
|
|
repo := newRemoteFixture(t)
|
|
candidate := repo.commit
|
|
remoteMain := repo.commit
|
|
if relation == "ahead" {
|
|
writeRepoFile(t, repo.work, "ahead.txt", "ahead")
|
|
gitRun(t, repo.work, "add", "ahead.txt")
|
|
gitRun(t, repo.work, "commit", "-m", "Advance main")
|
|
remoteMain = gitText(t, repo.work, "rev-parse", "HEAD")
|
|
gitRun(t, repo.work, "push", repo.bare, "HEAD:refs/heads/main")
|
|
} else {
|
|
writeRepoFile(t, repo.work, "behind.txt", "candidate is ahead")
|
|
gitRun(t, repo.work, "add", "behind.txt")
|
|
gitRun(t, repo.work, "commit", "-m", "Candidate ahead of main")
|
|
candidate = gitText(t, repo.work, "rev-parse", "HEAD")
|
|
}
|
|
if relation == "behind" {
|
|
gitRun(t, repo.bare, "update-ref", "refs/heads/main", remoteMain)
|
|
}
|
|
endpoint := resolveFixture(t, repo)
|
|
snapshot, err := endpoint.RequireCandidate(context.Background(), repo.work, "v1.2.3", candidate)
|
|
if !errors.Is(err, ErrRemoteMainConflict) || snapshot.Main.State != RefConflict {
|
|
t.Fatalf("snapshot = %#v, error = %v", snapshot, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestQueryFailureIsUnreadableAndEndpointChangesAreDetected(t *testing.T) {
|
|
repo := newRemoteFixture(t)
|
|
endpoint := resolveFixture(t, repo)
|
|
missing := filepath.Join(t.TempDir(), "missing.git")
|
|
gitRun(t, repo.work, "remote", "set-url", "--push", "origin", missing)
|
|
if err := endpoint.Recheck(context.Background(), repo.work); !errors.Is(err, ErrEndpointChanged) {
|
|
t.Fatalf("Recheck() error = %v, want ErrEndpointChanged", err)
|
|
}
|
|
gitRun(t, repo.work, "remote", "set-url", "--push", "origin", missing)
|
|
broken, err := Resolve(context.Background(), repo.work, "origin")
|
|
if err != nil {
|
|
t.Fatalf("Resolve(missing bare path) error = %v", err)
|
|
}
|
|
snapshot, err := broken.Query(context.Background(), repo.work, "v1.2.3")
|
|
if !errors.Is(err, ErrRemoteStateUnreadable) || snapshot.Main.State != RefUnreadable || snapshot.Tag.State != RefUnreadable {
|
|
t.Fatalf("snapshot = %#v, error = %v", snapshot, err)
|
|
}
|
|
}
|
|
|
|
func TestMergeRefMarksDuplicateExactResultsConflicting(t *testing.T) {
|
|
first := strings.Repeat("a", 40)
|
|
second := strings.Repeat("b", 40)
|
|
ref := mergeRef(RemoteRef{State: RefAbsent}, first, false)
|
|
ref = mergeRef(ref, second, false)
|
|
if ref.State != RefConflict {
|
|
t.Fatalf("state = %v, want RefConflict", ref.State)
|
|
}
|
|
}
|
|
|
|
type remoteFixture struct {
|
|
work string
|
|
bare string
|
|
commit string
|
|
}
|
|
|
|
func newRemoteFixture(t *testing.T) remoteFixture {
|
|
t.Helper()
|
|
base := t.TempDir()
|
|
work := filepath.Join(base, "work")
|
|
bare := filepath.Join(base, "authoritative.git")
|
|
decoy := filepath.Join(base, "fetch-only.git")
|
|
gitRun(t, base, "init", "-b", "main", work)
|
|
gitRun(t, work, "config", "user.name", "ReleaseKit Test")
|
|
gitRun(t, work, "config", "user.email", "releasekit@example.invalid")
|
|
gitRun(t, work, "init", "--bare", bare)
|
|
gitRun(t, work, "init", "--bare", decoy)
|
|
writeRepoFile(t, work, "source.txt", "candidate")
|
|
gitRun(t, work, "add", "source.txt")
|
|
gitRun(t, work, "commit", "-m", "Candidate")
|
|
commit := gitText(t, work, "rev-parse", "HEAD")
|
|
gitRun(t, work, "remote", "add", "origin", decoy)
|
|
gitRun(t, work, "config", "--add", "remote.origin.pushurl", bare)
|
|
gitRun(t, work, "push", bare, "HEAD:refs/heads/main")
|
|
return remoteFixture{work: work, bare: bare, commit: commit}
|
|
}
|
|
|
|
func resolveFixture(t *testing.T, repo remoteFixture) Endpoint {
|
|
t.Helper()
|
|
endpoint, err := Resolve(context.Background(), repo.work, "origin")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return endpoint
|
|
}
|
|
|
|
func gitRun(t *testing.T, dir string, args ...string) {
|
|
t.Helper()
|
|
cmd := exec.Command("git", args...)
|
|
cmd.Dir = dir
|
|
if output, err := cmd.CombinedOutput(); err != nil {
|
|
t.Fatalf("git %s: %v\n%s", strings.Join(args, " "), err, output)
|
|
}
|
|
}
|
|
|
|
func gitText(t *testing.T, dir string, args ...string) string {
|
|
t.Helper()
|
|
cmd := exec.Command("git", args...)
|
|
cmd.Dir = dir
|
|
outputBytes, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
t.Fatalf("git %s: %v\n%s", strings.Join(args, " "), err, outputBytes)
|
|
}
|
|
return strings.TrimSuffix(string(outputBytes), "\n")
|
|
}
|
|
|
|
func writeRepoFile(t *testing.T, repoDir, name, contents string) {
|
|
t.Helper()
|
|
path := filepath.Join(repoDir, name)
|
|
if err := os.WriteFile(path, []byte(contents), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|