Personal Git forge.

Code git clone ssh://git.poy.ooo:6969/~poyo/farga ZIP
lib/git/repo_test.go / 15KiB #8487c69a
Raw
package gitimport (	"archive/zip"	"bytes"	"context"	"errors"	"os"	"os/exec"	"path/filepath"	"strings"	"testing"	git "github.com/go-git/go-git/v6"	"github.com/go-git/go-git/v6/plumbing"	"github.com/go-git/go-git/v6/plumbing/object"	"github.com/google/uuid")// RepoPath() resolves lib.GitReposDir against the process CWD, so point the// whole test binary at a throwaway directory to keep repos off the source tree.func TestMain(m *testing.M) {	origWD, err := os.Getwd()	if err != nil {		panic(err)	}	tmp, err := os.MkdirTemp("", "farga-git-test-*")	if err != nil {		panic(err)	}	if err := os.Chdir(tmp); err != nil {		panic(err)	}	code := m.Run()	os.Chdir(origWD)	os.RemoveAll(tmp)	os.Exit(code)}func runGit(t *testing.T, dir string, args ...string) {	t.Helper()	cmd := exec.Command("git", args...)	cmd.Dir = dir	if out, err := cmd.CombinedOutput(); err != nil {		t.Fatalf("git %s: %v\n%s", strings.Join(args, " "), err, out)	}}// builds a bare repo at target with the given files. Two commits are made; the// second one only touches root.txt, so file-scoped history has one commit.func makeBareRepo(t *testing.T, target string, content map[string]string) {	t.Helper()	// RepoPath() returns a trailing slash; filepath.Dir() keeps the last	// element when it ends with one, so normalize first.	target = strings.TrimSuffix(target, "/")	dir := filepath.Dir(target)	work := filepath.Join(dir, "work")	if err := os.MkdirAll(work, 0o755); err != nil {		t.Fatal(err)	}	runGit(t, work, "init", "-b", "master")	runGit(t, work, "config", "user.name", "Test Author")	runGit(t, work, "config", "user.email", "test@example.com")	for name, body := range content {		full := filepath.Join(work, filepath.FromSlash(name))		if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {			t.Fatalf("mkdir %s: %v", full, err)		}		if err := os.WriteFile(full, []byte(body), 0o644); err != nil {			t.Fatalf("write %s: %v", full, err)		}	}	runGit(t, work, "add", "-A")	runGit(t, work, "commit", "-m", "first commit")	if err := os.WriteFile(filepath.Join(work, "root.txt"), []byte("second\n"), 0o644); err != nil {		t.Fatal(err)	}	runGit(t, work, "add", "-A")	runGit(t, work, "commit", "-m", "second commit")	runGit(t, dir, "clone", "--bare", "work", filepath.Base(target))}// a fixture with a README, a plain file, and one subdirectory.func newFixtureRepo(t *testing.T) (path string) {	t.Helper()	path = filepath.Join(t.TempDir(), "bare")	makeBareRepo(t, path, map[string]string{		"README.md":     "# Farga test\n",		"root.txt":      "root\n",		"sub/hello.txt": "hello\n",	})	return path}func openBare(t *testing.T, path string) *git.Repository {	t.Helper()	repo, err := git.PlainOpen(path)	if err != nil {		t.Fatalf("PlainOpen(%q): %v", path, err)	}	return repo}func headTree(t *testing.T, repo *git.Repository) *object.Tree {	t.Helper()	head, err := repo.Head()	if err != nil {		t.Fatalf("Head(): %v", err)	}	commit, err := object.GetCommit(repo.Storer, head.Hash())	if err != nil {		t.Fatalf("GetCommit(%s): %v", head.Hash(), err)	}	tree, err := commit.Tree()	if err != nil {		t.Fatalf("Tree(): %v", err)	}	return tree}func TestResolveHead(t *testing.T) {	path := newFixtureRepo(t)	repo := openBare(t, path)	// autoselect HEAD	ref, err := ResolveHead(repo, "")	if err != nil {		t.Fatalf("ResolveHead(repo, \"\"): %v", err)	}	if ref.Name() != "refs/heads/master" {		t.Errorf("autoselected branch = %s, want refs/heads/master", ref.Name())	}	// explicit branch	ref2, err := ResolveHead(repo, "master")	if err != nil {		t.Fatalf("ResolveHead(repo, \"master\"): %v", err)	}	if ref2.Hash() != ref.Hash() {		t.Errorf("branch master resolved to %s, want %s", ref2.Hash(), ref.Hash())	}	// nonexistent branch	if _, err := ResolveHead(repo, "nope"); !errors.Is(err, ErrInvalidBranch) {		t.Errorf("ResolveHead(repo, \"nope\") = %v, want ErrInvalidBranch", err)	}}func TestResolveHeadEmptyRepo(t *testing.T) {	path := filepath.Join(t.TempDir(), "empty")	if err := os.MkdirAll(path, 0o755); err != nil {		t.Fatal(err)	}	runGit(t, path, "init", "--bare")	repo := openBare(t, path)	if _, err := ResolveHead(repo, ""); !errors.Is(err, ErrRepoHasNoHead) {		t.Errorf("ResolveHead(empty repo) = %v, want ErrRepoHasNoHead", err)	}}func TestInitRepo(t *testing.T) {	userID := uuid.Must(uuid.NewV7())	repoID := uuid.Must(uuid.NewV7())	path := RepoPath(userID, repoID)	repo, err := InitRepo(userID, repoID)	if err != nil {		t.Fatalf("InitRepo: %v", err)	}	defer repo.Close()	if _, err := os.Stat(path); err != nil {		t.Errorf("repo dir %s missing: %v", path, err)	}	if _, err := git.PlainOpen(path); err != nil {		t.Errorf("PlainOpen(%s): %v", path, err)	}}func TestRepo(t *testing.T) {	userID := uuid.Must(uuid.NewV7())	repoID := uuid.Must(uuid.NewV7())	path := RepoPath(userID, repoID)	makeBareRepo(t, path, map[string]string{		"README.md":   "# Hi\n",		"src/main.go": "package main\n",	})	r, err := Repo(userID, repoID, "")	if err != nil {		t.Fatalf("Repo: %v", err)	}	if r.Head == nil {		t.Fatal("Repo: Head is nil")	}	if len(r.ShortHash) != 8 {		t.Errorf("Repo: ShortHash = %q, want 8 chars", r.ShortHash)	}	if r.LatestCommit == nil {		t.Fatal("Repo: LatestCommit is nil")	}	if r.Readme.Name != "README.md" || !strings.Contains(r.Readme.Content, "# Hi") {		t.Errorf("Repo: Readme = %+v, want README.md with # Hi", r.Readme)	}	if len(r.Files) != 0 {		t.Errorf("Repo: Files should be unset, got %d", len(r.Files))	}	// explicit branch resolves to the same commit	r2, err := Repo(userID, repoID, "master")	if err != nil {		t.Fatalf("Repo(branch=master): %v", err)	}	if r2.Head.Hash() != r.Head.Hash() {		t.Errorf("Repo(branch=master): hash %s, want %s", r2.Head.Hash(), r.Head.Hash())	}}func TestRepoDoesNotExist(t *testing.T) {	userID := uuid.Must(uuid.NewV7())	repoID := uuid.Must(uuid.NewV7())	_, err := Repo(userID, repoID, "")	if !errors.Is(err, ErrRepoDoesNotExist) {		t.Errorf("Repo(missing) = %v, want ErrRepoDoesNotExist", err)	}}func TestDeleteRepo(t *testing.T) {	userID := uuid.Must(uuid.NewV7())	repoID := uuid.Must(uuid.NewV7())	path := RepoPath(userID, repoID)	makeBareRepo(t, path, map[string]string{"a.txt": "a\n"})	DeleteRepo(userID, repoID)	if _, err := os.Stat(path); !os.IsNotExist(err) {		t.Errorf("repo dir %s still exists after DeleteRepo", path)	}}func TestReadme(t *testing.T) {	repo := openBare(t, newFixtureRepo(t))	tree := headTree(t, repo)	readme := Readme(tree)	if readme.Name != "README.md" {		t.Errorf("Readme().Name = %q, want README.md", readme.Name)	}	if !strings.Contains(readme.Content, "# Farga test") {		t.Errorf("Readme().Content = %q, want it to contain # Farga test", readme.Content)	}	// a tree without readme files	sub, err := tree.Tree("sub")	if err != nil {		t.Fatal(err)	}	if empty := Readme(sub); empty.Name != "" || empty.Content != "" {		t.Errorf("Readme(no readme tree) = %+v, want empty", empty)	}}func TestReadmePrecedence(t *testing.T) {	path := filepath.Join(t.TempDir(), "bare")	makeBareRepo(t, path, map[string]string{		"README":    "plain\n",		"README.md": "markdown\n",	})	repo := openBare(t, path)	readme := Readme(headTree(t, repo))	// first candidate in the priority list wins	if readme.Name != "README" || readme.Content != "plain\n" {		t.Errorf("Readme() = %+v, want README (first candidate wins)", readme)	}}func TestDirFiles(t *testing.T) {	repo := openBare(t, newFixtureRepo(t))	tree := headTree(t, repo)	files, err := DirFiles(tree, "")	if err != nil {		t.Fatalf("DirFiles(tree, \"\"): %v", err)	}	// dirs are prepended, files appended.	if len(files) != 3 {		t.Fatalf("DirFiles root = %d entries, want 3", len(files))	}	if files[0].Name != "sub" || !files[0].IsDir || files[0].Path != "sub" {		t.Errorf("first entry = %+v, want dir sub", files[0])	}	if files[1].Name != "README.md" || files[1].IsDir {		t.Errorf("second entry = %+v, want file README.md", files[1])	}	if files[1].Path != "README.md" {		t.Errorf("README.md Path = %q, want README.md", files[1].Path)	}	if len(files[1].ShortHash) != 8 {		t.Errorf("README.md ShortHash = %q, want 8 chars", files[1].ShortHash)	}	if files[1].Hash.IsZero() {		t.Error("README.md Hash is zero")	}	// subdirectory listing	sub, err := DirFiles(tree, "sub")	if err != nil {		t.Fatalf("DirFiles(tree, \"sub\"): %v", err)	}	if len(sub) != 1 || sub[0].Name != "hello.txt" || sub[0].Path != "sub/hello.txt" {		t.Errorf("DirFiles(sub) = %+v, want [sub/hello.txt]", sub)	}	// nonexistent path	if _, err := DirFiles(tree, "nope"); err == nil {		t.Error("DirFiles(tree, \"nope\") = nil error, want error")	}}func TestGetLog(t *testing.T) {	path := newFixtureRepo(t)	repo := openBare(t, path)	log, err := GetLog(LogOptions{Repo: repo})	if err != nil {		t.Fatalf("GetLog: %v", err)	}	if len(log) != 2 {		t.Fatalf("GetLog returned %d commits, want 2", len(log))	}	if strings.TrimSpace(log[0].Message) != "second commit" || strings.TrimSpace(log[1].Message) != "first commit" {		t.Errorf("commit order = [%q, %q], want newest first", log[0].Message, log[1].Message)	}	if log[0].Author.Name != "Test Author" || log[0].Author.Email != "test@example.com" {		t.Errorf("author = %+v, want Test Author <test@example.com>", log[0].Author)	}	if len(log[0].ShortHash) != 8 {		t.Errorf("ShortHash = %q, want 8 chars", log[0].ShortHash)	}	if log[0].Time.IsZero() {		t.Error("commit Time is zero")	}	if len(log[0].Hash) != 40 {		t.Errorf("Hash = %q, want 40 chars", log[0].Hash)	}	// explicit branch	log2, err := GetLog(LogOptions{Repo: repo, Branch: "master"})	if err != nil {		t.Fatalf("GetLog(Branch=master): %v", err)	}	if len(log2) != 2 {		t.Errorf("GetLog(Branch=master) returned %d commits, want 2", len(log2))	}}func TestGetLogErrors(t *testing.T) {	path := newFixtureRepo(t)	repo := openBare(t, path)	if _, err := GetLog(LogOptions{}); err == nil {		t.Error("GetLog(nil repo) = nil error, want error")	}	if _, err := GetLog(LogOptions{Repo: repo, Branch: "nope"}); !errors.Is(err, ErrInvalidBranch) {		t.Errorf("GetLog(bad branch) = %v, want ErrInvalidBranch", err)	}	empty := filepath.Join(t.TempDir(), "empty")	if err := os.MkdirAll(empty, 0o755); err != nil {		t.Fatal(err)	}	runGit(t, empty, "init", "--bare")	emptyRepo := openBare(t, empty)	if _, err := GetLog(LogOptions{Repo: emptyRepo}); !errors.Is(err, ErrRepoHasNoHead) {		t.Errorf("GetLog(empty repo) = %v, want ErrRepoHasNoHead", err)	}}func TestShortenHash(t *testing.T) {	hash := "0123456789abcdef0123456789abcdef01234567"	short, err := ShortenHash(hash)	if err != nil {		t.Fatalf("ShortenHash: %v", err)	}	if short != hash[:8] {		t.Errorf("ShortenHash = %q, want %q", short, hash[:8])	}	// shorter than GitHashLength: returned in full	full, err := ShortenHash("abc")	if err != nil {		t.Fatalf("ShortenHash(short): %v", err)	}	if full != "abc" {		t.Errorf("ShortenHash(short) = %q, want %q", full, "abc")	}}func TestFindFileEntry(t *testing.T) {	repo := openBare(t, newFixtureRepo(t))	tree := headTree(t, repo)	entry, err := FindFileEntry(tree, "README.md")	if err != nil {		t.Fatalf("FindFileEntry(README.md): %v", err)	}	if !entry.Mode.IsFile() {		t.Errorf("README.md entry mode = %s, want file", entry.Mode)	}	// nested path	if _, err := FindFileEntry(tree, "sub/hello.txt"); err != nil {		t.Errorf("FindFileEntry(sub/hello.txt): %v", err)	}	// leading slash is trimmed	if _, err := FindFileEntry(tree, "/README.md"); err != nil {		t.Errorf("FindFileEntry(/README.md): %v", err)	}	if _, err := FindFileEntry(tree, ""); err == nil {		t.Error("FindFileEntry(\"\") = nil error, want error")	}	if _, err := FindFileEntry(tree, "nope"); err == nil {		t.Error("FindFileEntry(nope) = nil error, want error")	}	if _, err := FindFileEntry(tree, "sub"); err == nil {		t.Error("FindFileEntry(sub) = nil error, want not-a-file error")	}}func TestRefExists(t *testing.T) {	path := newFixtureRepo(t)	ctx := context.Background()	repo := openBare(t, path)	head, err := repo.Head()	if err != nil {		t.Fatal(err)	}	latest := head.Hash().String()	commit, err := object.GetCommit(repo.Storer, head.Hash())	if err != nil {		t.Fatal(err)	}	root := commit.ParentHashes[0].String() // the initial commit	// any existing commit resolves, including the root commit	if err := RefExists(ctx, path, latest); err != nil {		t.Errorf("RefExists(latest commit): %v", err)	}	if err := RefExists(ctx, path, root); err != nil {		t.Errorf("RefExists(root commit): %v", err)	}	if err := RefExists(ctx, path, "master"); err != nil {		t.Errorf("RefExists(master): %v", err)	}	if err := RefExists(ctx, path, "nope"); err == nil {		t.Error("RefExists(nope) = nil error, want error")	}	if err := RefExists(ctx, path, "evil ref"); !errors.Is(err, ErrInvalidRef) {		t.Errorf("RefExists(evil ref) = %v, want ErrInvalidRef", err)	}}func TestZipStream(t *testing.T) {	path := newFixtureRepo(t)	var buf bytes.Buffer	if err := ZipStream(context.Background(), path, "master", &buf); err != nil {		t.Fatalf("ZipStream: %v", err)	}	zr, err := zip.NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))	if err != nil {		t.Fatalf("archive is not a zip: %v", err)	}	var names []string	for _, f := range zr.File {		names = append(names, f.Name)	}	for _, want := range []string{"README.md", "root.txt", "sub/hello.txt"} {		found := false		for _, n := range names {			if n == want {				found = true				break			}		}		if !found {			t.Errorf("zip is missing %s; entries: %v", want, names)		}	}	if err := ZipStream(context.Background(), path, "evil ref", &buf); !errors.Is(err, ErrInvalidRef) {		t.Errorf("ZipStream(bad ref) = %v, want ErrInvalidRef", err)	}}func blobHash(t *testing.T, tree *object.Tree, name string) plumbing.Hash {	t.Helper()	entry, err := tree.FindEntry(name)	if err != nil {		t.Fatalf("FindEntry(%s): %v", name, err)	}	if !entry.Mode.IsFile() {		t.Fatalf("%s is not a file", name)	}	return entry.Hash}func TestBlobSize(t *testing.T) {	path := newFixtureRepo(t)	tree := headTree(t, openBare(t, path))	size, err := BlobSize(path, blobHash(t, tree, "README.md"))	if err != nil {		t.Fatalf("BlobSize: %v", err)	}	if size != int64(len("# Farga test\n")) {		t.Errorf("BlobSize = %d, want %d", size, len("# Farga test\n"))	}}func TestStreamBlob(t *testing.T) {	path := newFixtureRepo(t)	tree := headTree(t, openBare(t, path))	var buf bytes.Buffer	if err := StreamBlob(path, blobHash(t, tree, "sub/hello.txt"), &buf); err != nil {		t.Fatalf("StreamBlob: %v", err)	}	if buf.String() != "hello\n" {		t.Errorf("StreamBlob = %q, want %q", buf.String(), "hello\n")	}}func TestReadBlobPrefix(t *testing.T) {	path := newFixtureRepo(t)	tree := headTree(t, openBare(t, path))	hash := blobHash(t, tree, "README.md")	prefix, err := ReadBlobPrefix(path, hash, 7)	if err != nil {		t.Fatalf("ReadBlobPrefix: %v", err)	}	if string(prefix) != "# Farga" {		t.Errorf("ReadBlobPrefix(7) = %q, want %q", prefix, "# Farga")	}	// n larger than the blob returns the whole blob	whole, err := ReadBlobPrefix(path, hash, 1000)	if err != nil {		t.Fatalf("ReadBlobPrefix(1000): %v", err)	}	if string(whole) != "# Farga test\n" {		t.Errorf("ReadBlobPrefix(1000) = %q, want whole blob", whole)	}	empty, err := ReadBlobPrefix(path, hash, 0)	if err != nil {		t.Fatalf("ReadBlobPrefix(0): %v", err)	}	if len(empty) != 0 {		t.Errorf("ReadBlobPrefix(0) = %q, want empty", empty)	}}