golib/net/gitshallow/forcepush_test.go
AJ ONeal 0d9df94a24
test(gitshallow): regression test for upstream force-push recovery
Sets up a local bare upstream, clones via gitshallow, then rewrites
upstream history as an unrelated commit and force-pushes. A fresh
Repo instance's Fetch must succeed and install the new HEAD — the
old pull-based flow would fail with "refusing to merge unrelated
histories".

Runs under the default test build (no integration tag) because it
uses only a local bare repo; no network access required.
2026-04-20 20:05:52 -06:00

103 lines
3.3 KiB
Go

package gitshallow_test
import (
"os"
"os/exec"
"path/filepath"
"testing"
"github.com/therootcompany/golib/net/gitshallow"
)
// TestRepo_ForcePushRecovery verifies that Fetch transparently picks up an
// upstream history rewrite. The previous pull-based flow could fail with
// "refusing to merge unrelated histories"; the current fetch+reset must
// succeed and install the new HEAD.
func TestRepo_ForcePushRecovery(t *testing.T) {
if _, err := exec.LookPath("git"); err != nil {
t.Skip("git not available")
}
root := t.TempDir()
upstream := filepath.Join(root, "upstream.git") // bare
scratch := filepath.Join(root, "scratch") // working copy that rewrites history
clone := filepath.Join(root, "clone") // gitshallow clone under test
mustGit := func(t *testing.T, dir string, args ...string) {
t.Helper()
cmd := exec.Command("git", args...)
cmd.Dir = dir
cmd.Env = append(os.Environ(),
"GIT_AUTHOR_NAME=test", "GIT_AUTHOR_EMAIL=test@example.com",
"GIT_COMMITTER_NAME=test", "GIT_COMMITTER_EMAIL=test@example.com",
)
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("git %v (in %s): %v\n%s", args, dir, err, out)
}
}
// Bare upstream.
if err := os.MkdirAll(upstream, 0o755); err != nil {
t.Fatal(err)
}
mustGit(t, upstream, "init", "--bare", "--initial-branch=main")
// Scratch working copy with one commit, push to upstream.
if err := os.MkdirAll(scratch, 0o755); err != nil {
t.Fatal(err)
}
mustGit(t, scratch, "init", "--initial-branch=main")
mustGit(t, scratch, "remote", "add", "origin", upstream)
if err := os.WriteFile(filepath.Join(scratch, "data.txt"), []byte("v1\n"), 0o644); err != nil {
t.Fatal(err)
}
mustGit(t, scratch, "add", "data.txt")
mustGit(t, scratch, "commit", "-m", "v1")
mustGit(t, scratch, "push", "-u", "origin", "main")
// gitshallow clones, reads v1.
repo := gitshallow.New(upstream, clone, 1, "main")
if updated, err := repo.Fetch(); err != nil {
t.Fatalf("first Fetch: %v", err)
} else if !updated {
t.Fatal("first Fetch: expected updated=true")
}
got, err := os.ReadFile(filepath.Join(clone, "data.txt"))
if err != nil {
t.Fatalf("read v1: %v", err)
}
if string(got) != "v1\n" {
t.Fatalf("v1: got %q want %q", got, "v1\n")
}
// Rewrite upstream history with an unrelated commit and force-push.
mustGit(t, scratch, "checkout", "--orphan", "fresh")
mustGit(t, scratch, "rm", "-rf", ".")
if err := os.WriteFile(filepath.Join(scratch, "data.txt"), []byte("v2\n"), 0o644); err != nil {
t.Fatal(err)
}
mustGit(t, scratch, "add", "data.txt")
mustGit(t, scratch, "commit", "-m", "v2")
mustGit(t, scratch, "branch", "-M", "main")
mustGit(t, scratch, "push", "--force", "origin", "main")
// gitshallow must recover — fetch+reset, not pull --ff-only.
// Use a fresh instance: the same *Repo has a 1s debounce that would
// skip the follow-up fetch in this test's wall-clock window.
repo2 := gitshallow.New(upstream, clone, 1, "main")
updated, err := repo2.Fetch()
if err != nil {
t.Fatalf("post-force-push Fetch: %v", err)
}
if !updated {
t.Error("post-force-push Fetch: expected updated=true")
}
got, err = os.ReadFile(filepath.Join(clone, "data.txt"))
if err != nil {
t.Fatalf("read v2: %v", err)
}
if string(got) != "v2\n" {
t.Fatalf("v2: got %q want %q", got, "v2\n")
}
}