Luigit
repositories / bugabinga.net

bugabinga.net

personal infrastructure for bugabinga!

owned by admin

services/luci/internal/gitrepo/gitrepo_test.go

Raw
package gitrepo

import (
	"errors"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
)

func TestResolveAndCheckout(t *testing.T) {
	root := t.TempDir()
	bare := createBareRepo(t, root, "repo")
	path, err := RepoPath([]string{root}, "repo")
	if err != nil {
		t.Fatalf("RepoPath() error = %v", err)
	}
	if path != bare {
		t.Fatalf("RepoPath() = %q, want %q", path, bare)
	}
	rev, err := Resolve(path, "refs/heads/main", "")
	if err != nil {
		t.Fatalf("Resolve() error = %v", err)
	}
	workspace := filepath.Join(root, "workspace")
	if err := os.MkdirAll(workspace, 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(workspace, "stale"), []byte("old"), 0o644); err != nil {
		t.Fatal(err)
	}
	if err := Checkout(path, rev, workspace); err != nil {
		t.Fatalf("Checkout() error = %v", err)
	}
	data, err := os.ReadFile(filepath.Join(workspace, "README.md"))
	if err != nil {
		t.Fatalf("checked out file missing: %v", err)
	}
	if string(data) != "hello\n" {
		t.Fatalf("README = %q", data)
	}
	if _, err := os.Stat(filepath.Join(workspace, "stale")); !os.IsNotExist(err) {
		t.Fatalf("stale file survived: %v", err)
	}
	for _, check := range []struct {
		args []string
		want string
	}{
		{[]string{"rev-parse", "HEAD"}, rev},
		{[]string{"rev-list", "--count", "HEAD"}, "1"},
		{[]string{"remote"}, ""},
		{[]string{"ls-files"}, "README.md"},
		{[]string{"status", "--porcelain"}, ""},
	} {
		cmd := exec.Command("git", append([]string{"-C", workspace}, check.args...)...)
		out, err := cmd.CombinedOutput()
		if err != nil || strings.TrimSpace(string(out)) != check.want {
			t.Fatalf("git %v = %q, %v; want %q", check.args, out, err, check.want)
		}
	}
}

func TestCheckoutKeepsBareRepoReadOnly(t *testing.T) {
	root := t.TempDir()
	bare := createBareRepo(t, root, "repo")
	rev, err := Resolve(bare, "refs/heads/main", "")
	if err != nil {
		t.Fatal(err)
	}
	if err := os.Chmod(bare, 0o555); err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { _ = os.Chmod(bare, 0o755) })
	if err := Checkout(bare, rev, filepath.Join(root, "workspace")); err != nil {
		t.Fatalf("Checkout() against read-only repo error = %v", err)
	}
	if _, err := os.Stat(filepath.Join(bare, "index")); !os.IsNotExist(err) {
		t.Fatalf("checkout wrote bare repo index: %v", err)
	}
}

func TestDefaultBranchAndChangedPaths(t *testing.T) {
	root := t.TempDir()
	bare := createBareRepo(t, root, "repo")
	ref, first, err := DefaultBranch(bare)
	if err != nil || ref != "refs/heads/main" || first == "" {
		t.Fatalf("ref=%q rev=%q err=%v", ref, first, err)
	}
	all, err := ChangedPaths(bare, "0000000000000000000000000000000000000000", first)
	if err != nil || len(all) != 1 || all[0] != "README.md" {
		t.Fatalf("initial paths=%q err=%v", all, err)
	}
	work := filepath.Join(root, "work")
	if err := os.MkdirAll(filepath.Join(work, "src", "nested"), 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(work, "src", "nested", "app.go"), []byte("package app\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	runGit(t, work, "add", ".")
	runGit(t, work, "commit", "-m", "add source")
	runGit(t, work, "remote", "add", "origin", bare)
	runGit(t, work, "push", "origin", "main")
	second, err := Resolve(bare, "refs/heads/main", "")
	if err != nil {
		t.Fatal(err)
	}
	changed, err := ChangedPaths(bare, first, second)
	if err != nil || len(changed) != 1 || changed[0] != "src/nested/app.go" {
		t.Fatalf("changed=%q err=%v", changed, err)
	}
	workspace := filepath.Join(root, "old-revision")
	if err := Checkout(bare, first, workspace); err != nil {
		t.Fatalf("Checkout() old revision error = %v", err)
	}
	if got := strings.TrimSpace(runGit(t, workspace, "rev-parse", "HEAD")); got != first {
		t.Fatalf("old checkout HEAD = %q, want %q", got, first)
	}
}

func TestChangedPathsRejectsOptionLikeRevisions(t *testing.T) {
	if _, err := ChangedPaths(t.TempDir(), "--output=/tmp/x", strings.Repeat("a", 40)); err == nil {
		t.Fatal("option-like old revision accepted")
	}
	if _, err := ChangedPaths(t.TempDir(), strings.Repeat("0", 40), "--help"); err == nil {
		t.Fatal("option-like new revision accepted")
	}
}

func TestResolvePrefersRevAndRejectsMissingRefOrBadRev(t *testing.T) {
	root := t.TempDir()
	bare := createBareRepo(t, root, "repo")
	rev, err := Resolve(bare, "refs/heads/missing", "main")
	if err != nil {
		t.Fatalf("Resolve rev error = %v", err)
	}
	if rev == "" {
		t.Fatal("empty rev")
	}
	if _, err := Resolve(bare, "", ""); err == nil {
		t.Fatal("empty ref/rev accepted")
	}
	if _, err := Resolve(bare, "", "missing"); err == nil {
		t.Fatal("bad rev accepted")
	}
}

func TestCheckoutRejectsEmptyBadRevAndBadWorkspace(t *testing.T) {
	root := t.TempDir()
	bare := createBareRepo(t, root, "repo")
	if err := Checkout(bare, "", filepath.Join(root, "workspace")); err == nil {
		t.Fatal("empty rev checkout accepted")
	}
	if err := Checkout(bare, "missing", filepath.Join(root, "workspace")); err == nil {
		t.Fatal("bad rev checkout accepted")
	}
	file := filepath.Join(root, "file")
	if err := os.WriteFile(file, []byte("x"), 0o644); err != nil {
		t.Fatal(err)
	}
	rev, err := Resolve(bare, "refs/heads/main", "")
	if err != nil {
		t.Fatal(err)
	}
	if err := Checkout(bare, rev, filepath.Join(file, "workspace")); err == nil {
		t.Fatal("bad workspace accepted")
	}
}

func TestCheckoutPropagatesMkdirFailure(t *testing.T) {
	root := t.TempDir()
	bare := createBareRepo(t, root, "repo")
	rev, err := Resolve(bare, "refs/heads/main", "")
	if err != nil {
		t.Fatal(err)
	}
	boom := errors.New("boom")
	old := mkdirAll
	mkdirAll = func(string, os.FileMode) error { return boom }
	defer func() { mkdirAll = old }()
	if err := Checkout(bare, rev, filepath.Join(root, "workspace")); !errors.Is(err, boom) {
		t.Fatalf("err = %v", err)
	}
}

func TestRepoPathFindsPlainRepoDirAndRejectsMissing(t *testing.T) {
	root := t.TempDir()
	bare := createBareRepo(t, root, "plain")
	plain := filepath.Join(root, "plain")
	if err := os.Rename(bare, plain); err != nil {
		t.Fatal(err)
	}
	path, err := RepoPath([]string{root}, "plain")
	if err != nil {
		t.Fatalf("RepoPath plain error = %v", err)
	}
	if path != plain {
		t.Fatalf("path = %q, want %q", path, plain)
	}
	if _, err := RepoPath([]string{root}, "missing"); err == nil {
		t.Fatal("missing repo accepted")
	}
}

func TestRepoPathRejectsTraversal(t *testing.T) {
	if _, err := RepoPath([]string{t.TempDir()}, "../repo"); err == nil {
		t.Fatal("traversal repo accepted")
	}
}

func createBareRepo(t *testing.T, root string, name string) string {
	t.Helper()
	work := filepath.Join(root, "work")
	runGit(t, root, "init", work)
	runGit(t, work, "config", "user.email", "test@example.invalid")
	runGit(t, work, "config", "user.name", "Test")
	if err := os.WriteFile(filepath.Join(work, "README.md"), []byte("hello\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	runGit(t, work, "add", "README.md")
	runGit(t, work, "commit", "-m", "initial")
	runGit(t, work, "branch", "-M", "main")
	bare := filepath.Join(root, name+".git")
	runGit(t, root, "clone", "--bare", work, bare)
	return bare
}

func runGit(t *testing.T, dir string, args ...string) string {
	t.Helper()
	cmd := exec.Command("git", args...)
	cmd.Dir = dir
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("git %v failed: %v: %s", args, err, out)
	}
	return string(out)
}