git@cosmocr.at/gitweb.git @ main · summary refs log
main / internal / gitrepo / materialize_test.go /
internal/gitrepo/materialize_test.go · 5.5 KiB · raw
view: SourceMarkdown ☑ force

package gitrepo

import ( "errors" "os" "os/exec" "path/filepath" "testing" "time"

"github.com/go-git/go-billy/v5/osfs"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object"

"gitweb/internal/store"

)

// makeBareRepo builds a real bare repo at // via the git CLI // and returns the store-relative repo path "/". Skips the test if // git is unavailable. func makeBareRepo(t *testing.T, root, user, name string) string { t.Helper() if _, err := exec.LookPath("git"); err != nil { t.Skip("git not available") }

work := t.TempDir()
run := func(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: %v\n%s", args, err, out)
	}
}

run(work, "init", "-b", "main")
if err := os.WriteFile(filepath.Join(work, "README.md"), []byte("# Demo\n\nhi\n"), 0o644); err != nil {
	t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(work, "main.go"), []byte("package main\n\nfunc main() {}\n"), 0o644); err != nil {
	t.Fatal(err)
}
run(work, "add", ".")
run(work, "commit", "-m", "initial commit")
run(work, "checkout", "-b", "feature/x")
if err := os.WriteFile(filepath.Join(work, "main.go"), []byte("package main\n\nfunc main() { _ = 1 }\n"), 0o644); err != nil {
	t.Fatal(err)
}
run(work, "commit", "-am", "tweak main")
run(work, "tag", "v1.0.0")
run(work, "checkout", "main")

bare := filepath.Join(root, user, name)
if err := os.MkdirAll(filepath.Dir(bare), 0o755); err != nil {
	t.Fatal(err)
}
run(work, "clone", "--bare", work, bare)
return user + "/" + name

}

// newTestCache builds a Cache over st rooted at a test tempdir. func newTestCache(t *testing.T, st store.Store, opts CacheOptions) *Cache { t.Helper() if opts.Dir == "" { opts.Dir = t.TempDir() } c, err := NewCache(st, opts) if err != nil { t.Fatalf("NewCache: %v", err) } return c }

func TestMaterializeAndOpen(t *testing.T) { root := t.TempDir() repoPath := makeBareRepo(t, root, "u@d.com", "demo.git") st := store.NewLocalStore(root)

c := newTestCache(t, st, CacheOptions{})
repo, release, err := c.Get(repoPath)
if err != nil {
	t.Fatalf("Get: %v", err)
}
defer release()

head, err := repo.Head()
if err != nil {
	t.Fatalf("Head: %v", err)
}
if head.Name().Short() != "main" {
	t.Fatalf("head = %q, want main", head.Name().Short())
}

// Log walks.
iter, err := repo.Log(&git.LogOptions{From: head.Hash()})
if err != nil {
	t.Fatalf("Log: %v", err)
}
var n int
err = iter.ForEach(func(*object.Commit) error { n++; return nil })
if err != nil {
	t.Fatalf("Log iter: %v", err)
}
if n == 0 {
	t.Fatal("expected at least one commit")
}

// Tree walks: README.md present at HEAD.
commit, err := repo.CommitObject(head.Hash())
if err != nil {
	t.Fatalf("CommitObject: %v", err)
}
tree, err := commit.Tree()
if err != nil {
	t.Fatalf("Tree: %v", err)
}
if _, err := tree.File("README.md"); err != nil {
	t.Fatalf("README.md not in tree: %v", err)
}

// Branch + tag are visible.
if _, err := repo.Reference("refs/heads/feature/x", false); err != nil {
	t.Fatalf("feature/x branch missing: %v", err)
}
if _, err := repo.Tag("v1.0.0"); err != nil {
	t.Fatalf("v1.0.0 tag missing: %v", err)
}

}

func TestMaterializeToDisk(t *testing.T) { root := t.TempDir() repoPath := makeBareRepo(t, root, "u@d.com", "demo.git") st := store.NewLocalStore(root)

dir := t.TempDir()
man, err := materialize(st, repoPath, osfs.New(dir), defaultMaxRepoBytes)
if err != nil {
	t.Fatalf("materialize: %v", err)
}
// The bare-repo layout lands on the real filesystem.
if _, err := os.Stat(filepath.Join(dir, "HEAD")); err != nil {
	t.Fatalf("HEAD not on disk: %v", err)
}
if fi, err := os.Stat(filepath.Join(dir, "objects")); err != nil || !fi.IsDir() {
	t.Fatalf("objects/ not a dir on disk: %v", err)
}
// Manifest entries match what is on disk.
for name, info := range man {
	fi, err := os.Stat(filepath.Join(dir, filepath.FromSlash(name)))
	if err != nil {
		t.Fatalf("manifest file %s missing on disk: %v", name, err)
	}
	if fi.Size() != info.Size {
		t.Errorf("%s: size %d on disk, %d in manifest", name, fi.Size(), info.Size)
	}
}

}

func TestMaterializeTooLarge(t *testing.T) { root := t.TempDir() repoPath := makeBareRepo(t, root, "u@d.com", "demo.git") st := store.NewLocalStore(root)

_, err := materialize(st, repoPath, osfs.New(t.TempDir()), 10)
if !errors.Is(err, ErrRepoTooLarge) {
	t.Fatalf("err = %v, want ErrRepoTooLarge", err)
}

}

func TestCacheReusesAndInvalidates(t *testing.T) { root := t.TempDir() repoPath := makeBareRepo(t, root, "u@d.com", "demo.git") st := store.NewLocalStore(root) c := newTestCache(t, st, CacheOptions{})

r1, release1, err := c.Get(repoPath)
if err != nil {
	t.Fatalf("Get: %v", err)
}
defer release1()
r2, release2, err := c.Get(repoPath)
if err != nil {
	t.Fatalf("Get: %v", err)
}
defer release2()
if r1 != r2 {
	t.Fatal("expected cached repo to be reused (same pointer)")
}

// Bump the repo's mtime so LocalStore.Seq changes -> re-materialization.
later := time.Now().Add(2 * time.Second)
if err := os.Chtimes(filepath.Join(root, repoPath, "HEAD"), later, later); err != nil {
	t.Fatal(err)
}
r3, release3, err := c.Get(repoPath)
if err != nil {
	t.Fatalf("Get: %v", err)
}
defer release3()
if r3 == r1 {
	t.Fatal("expected re-materialization after edit (different pointer)")
}

}