aboutsummaryrefslogtreecommitdiff
path: root/internal/testgit
diff options
context:
space:
mode:
Diffstat (limited to 'internal/testgit')
-rw-r--r--internal/testgit/repo_commit_tree_env.go51
-rw-r--r--internal/testgit/repo_fs.go86
-rw-r--r--internal/testgit/repo_open_commit_graph.go26
-rw-r--r--internal/testgit/repo_open_object_store.go29
-rw-r--r--internal/testgit/repo_open_repository.go25
-rw-r--r--internal/testgit/repo_open_root.go87
-rw-r--r--internal/testgit/repo_properties.go5
-rw-r--r--internal/testgit/repo_remove_loose_object.go22
-rw-r--r--internal/testgit/repo_run.go52
9 files changed, 371 insertions, 12 deletions
diff --git a/internal/testgit/repo_commit_tree_env.go b/internal/testgit/repo_commit_tree_env.go
new file mode 100644
index 00000000..ee949b5c
--- /dev/null
+++ b/internal/testgit/repo_commit_tree_env.go
@@ -0,0 +1,51 @@
+package testgit
+
+import (
+ "slices"
+ "strings"
+ "testing"
+
+ "codeberg.org/lindenii/furgit/objectid"
+)
+
+// CommitTreeWithEnv creates one commit from a tree and message, optionally with
+// parents, using additional environment variables for the git subprocess.
+func (testRepo *TestRepo) CommitTreeWithEnv(
+ tb testing.TB,
+ extraEnv []string,
+ tree objectid.ObjectID,
+ message string,
+ parents ...objectid.ObjectID,
+) objectid.ObjectID {
+ tb.Helper()
+
+ args := make([]string, 0, 2+2*len(parents)+2)
+
+ args = append(args, "commit-tree", tree.String())
+ for _, parent := range parents {
+ args = append(args, "-p", parent.String())
+ }
+
+ args = append(args, "-m", message)
+ hex := testRepo.runWithExtraEnv(tb, extraEnv, args...)
+
+ id, err := objectid.ParseHex(testRepo.algo, hex)
+ if err != nil {
+ tb.Fatalf("parse commit-tree output %q: %v", hex, err)
+ }
+
+ return id
+}
+
+func (testRepo *TestRepo) runWithExtraEnv(tb testing.TB, extraEnv []string, args ...string) string {
+ tb.Helper()
+
+ env := slices.Concat(testRepo.env, extraEnv)
+
+ out, err := testRepo.runBytesWithEnv(tb, nil, testRepo.dir, env, args...)
+ if err != nil {
+ tb.Fatalf("git %v failed: %v\n%s", args, err, out)
+ }
+
+ return strings.TrimSpace(string(out))
+}
diff --git a/internal/testgit/repo_fs.go b/internal/testgit/repo_fs.go
new file mode 100644
index 00000000..56acbfcf
--- /dev/null
+++ b/internal/testgit/repo_fs.go
@@ -0,0 +1,86 @@
+package testgit
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+// OpenFile opens one file relative to the repository root.
+func (testRepo *TestRepo) OpenFile(tb testing.TB, name string) *os.File {
+ tb.Helper()
+
+ root := testRepo.OpenRoot(tb)
+
+ file, err := root.Open(name)
+ if err != nil {
+ tb.Fatalf("Open(%q): %v", name, err)
+ }
+
+ return file
+}
+
+// ReadFile reads one file relative to the repository root.
+func (testRepo *TestRepo) ReadFile(tb testing.TB, name string) []byte {
+ tb.Helper()
+
+ root := testRepo.OpenRoot(tb)
+
+ data, err := root.ReadFile(name)
+ if err != nil {
+ tb.Fatalf("ReadFile(%q): %v", name, err)
+ }
+
+ return data
+}
+
+// WriteFile writes one file relative to the repository root.
+func (testRepo *TestRepo) WriteFile(tb testing.TB, name string, data []byte, perm os.FileMode) {
+ tb.Helper()
+
+ root := testRepo.OpenRoot(tb)
+
+ err := root.WriteFile(name, data, perm)
+ if err != nil {
+ tb.Fatalf("WriteFile(%q): %v", name, err)
+ }
+}
+
+// WriteFileAll writes one file relative to the repository root, creating any
+// missing parent directories first.
+func (testRepo *TestRepo) WriteFileAll(
+ tb testing.TB,
+ name string,
+ data []byte,
+ dirPerm os.FileMode,
+ filePerm os.FileMode,
+) {
+ tb.Helper()
+
+ root := testRepo.OpenRoot(tb)
+
+ dir := filepath.Dir(name)
+ if dir != "." {
+ err := root.MkdirAll(dir, dirPerm)
+ if err != nil {
+ tb.Fatalf("MkdirAll(%q): %v", dir, err)
+ }
+ }
+
+ err := root.WriteFile(name, data, filePerm)
+ if err != nil {
+ tb.Fatalf("WriteFile(%q): %v", name, err)
+ }
+}
+
+// Remove removes one path relative to the repository root.
+func (testRepo *TestRepo) Remove(tb testing.TB, name string) {
+ tb.Helper()
+
+ root := testRepo.OpenRoot(tb)
+
+ err := root.Remove(name)
+ if err != nil {
+ tb.Fatalf("Remove(%q): %v", name, err)
+ }
+}
diff --git a/internal/testgit/repo_open_commit_graph.go b/internal/testgit/repo_open_commit_graph.go
new file mode 100644
index 00000000..4db7261b
--- /dev/null
+++ b/internal/testgit/repo_open_commit_graph.go
@@ -0,0 +1,26 @@
+package testgit
+
+import (
+ "testing"
+
+ commitgraphread "codeberg.org/lindenii/furgit/format/commitgraph/read"
+)
+
+// OpenCommitGraph opens the repository commit-graph and registers cleanup on
+// the caller.
+func (testRepo *TestRepo) OpenCommitGraph(tb testing.TB) *commitgraphread.Reader {
+ tb.Helper()
+
+ objectsRoot := testRepo.OpenObjectsRoot(tb)
+
+ graph, err := commitgraphread.Open(objectsRoot, testRepo.Algorithm(), commitgraphread.OpenSingle)
+ if err != nil {
+ tb.Fatalf("commitgraphread.Open: %v", err)
+ }
+
+ tb.Cleanup(func() {
+ _ = graph.Close()
+ })
+
+ return graph
+}
diff --git a/internal/testgit/repo_open_object_store.go b/internal/testgit/repo_open_object_store.go
new file mode 100644
index 00000000..aac71d60
--- /dev/null
+++ b/internal/testgit/repo_open_object_store.go
@@ -0,0 +1,29 @@
+package testgit
+
+import (
+ "testing"
+
+ "codeberg.org/lindenii/furgit/objectstore"
+ "codeberg.org/lindenii/furgit/repository"
+)
+
+// OpenObjectStore opens the repository object store and registers cleanup on
+// the caller.
+//
+//nolint:ireturn
+func (testRepo *TestRepo) OpenObjectStore(tb testing.TB) objectstore.Store {
+ tb.Helper()
+
+ root := testRepo.OpenGitRoot(tb)
+
+ repo, err := repository.Open(root)
+ if err != nil {
+ tb.Fatalf("repository.Open: %v", err)
+ }
+
+ tb.Cleanup(func() {
+ _ = repo.Close()
+ })
+
+ return repo.Objects()
+}
diff --git a/internal/testgit/repo_open_repository.go b/internal/testgit/repo_open_repository.go
new file mode 100644
index 00000000..fbc98383
--- /dev/null
+++ b/internal/testgit/repo_open_repository.go
@@ -0,0 +1,25 @@
+package testgit
+
+import (
+ "testing"
+
+ "codeberg.org/lindenii/furgit/repository"
+)
+
+// OpenRepository opens the repository and registers cleanup on the caller.
+func (testRepo *TestRepo) OpenRepository(tb testing.TB) *repository.Repository {
+ tb.Helper()
+
+ root := testRepo.OpenGitRoot(tb)
+
+ repo, err := repository.Open(root)
+ if err != nil {
+ tb.Fatalf("repository.Open: %v", err)
+ }
+
+ tb.Cleanup(func() {
+ _ = repo.Close()
+ })
+
+ return repo
+}
diff --git a/internal/testgit/repo_open_root.go b/internal/testgit/repo_open_root.go
new file mode 100644
index 00000000..4530c604
--- /dev/null
+++ b/internal/testgit/repo_open_root.go
@@ -0,0 +1,87 @@
+package testgit
+
+import (
+ "errors"
+ "os"
+ "testing"
+)
+
+// OpenRoot opens the repository root directory and registers cleanup on the
+// caller.
+func (testRepo *TestRepo) OpenRoot(tb testing.TB) *os.Root {
+ tb.Helper()
+
+ root, err := os.OpenRoot(testRepo.dir)
+ if err != nil {
+ tb.Fatalf("os.OpenRoot: %v", err)
+ }
+
+ tb.Cleanup(func() {
+ _ = root.Close()
+ })
+
+ return root
+}
+
+// OpenGitRoot opens the repository gitdir and registers cleanup on the caller.
+//
+// For bare repositories, this is the repository root itself. For non-bare
+// repositories, this is the .git directory under the worktree root.
+func (testRepo *TestRepo) OpenGitRoot(tb testing.TB) *os.Root {
+ tb.Helper()
+
+ repoRoot := testRepo.OpenRoot(tb)
+
+ gitRoot, err := repoRoot.OpenRoot(".git")
+ if err == nil {
+ tb.Cleanup(func() {
+ _ = gitRoot.Close()
+ })
+
+ return gitRoot
+ }
+
+ if !errors.Is(err, os.ErrNotExist) {
+ tb.Fatalf("OpenRoot(.git): %v", err)
+ }
+
+ return repoRoot
+}
+
+// OpenObjectsRoot opens the objects directory and registers cleanup on the
+// caller.
+func (testRepo *TestRepo) OpenObjectsRoot(tb testing.TB) *os.Root {
+ tb.Helper()
+
+ gitRoot := testRepo.OpenGitRoot(tb)
+
+ objectsRoot, err := gitRoot.OpenRoot("objects")
+ if err != nil {
+ tb.Fatalf("OpenRoot(objects): %v", err)
+ }
+
+ tb.Cleanup(func() {
+ _ = objectsRoot.Close()
+ })
+
+ return objectsRoot
+}
+
+// OpenPackRoot opens the objects/pack directory and registers cleanup on the
+// caller.
+func (testRepo *TestRepo) OpenPackRoot(tb testing.TB) *os.Root {
+ tb.Helper()
+
+ objectsRoot := testRepo.OpenObjectsRoot(tb)
+
+ packRoot, err := objectsRoot.OpenRoot("pack")
+ if err != nil {
+ tb.Fatalf("OpenRoot(pack): %v", err)
+ }
+
+ tb.Cleanup(func() {
+ _ = packRoot.Close()
+ })
+
+ return packRoot
+}
diff --git a/internal/testgit/repo_properties.go b/internal/testgit/repo_properties.go
index 3a489124..703cef1c 100644
--- a/internal/testgit/repo_properties.go
+++ b/internal/testgit/repo_properties.go
@@ -2,11 +2,6 @@ package testgit
import "codeberg.org/lindenii/furgit/objectid"
-// Dir returns the repository directory path.
-func (testRepo *TestRepo) Dir() string {
- return testRepo.dir
-}
-
// Algorithm returns the object ID algorithm configured for this repository.
func (testRepo *TestRepo) Algorithm() objectid.Algorithm {
return testRepo.algo
diff --git a/internal/testgit/repo_remove_loose_object.go b/internal/testgit/repo_remove_loose_object.go
new file mode 100644
index 00000000..ec3d8c2d
--- /dev/null
+++ b/internal/testgit/repo_remove_loose_object.go
@@ -0,0 +1,22 @@
+package testgit
+
+import (
+ "fmt"
+ "testing"
+
+ "codeberg.org/lindenii/furgit/objectid"
+)
+
+// RemoveLooseObject removes one loose object file from the repository.
+func (testRepo *TestRepo) RemoveLooseObject(tb testing.TB, id objectid.ObjectID) {
+ tb.Helper()
+
+ root := testRepo.OpenObjectsRoot(tb)
+ hex := id.String()
+ path := fmt.Sprintf("%s/%s", hex[:2], hex[2:])
+
+ err := root.Remove(path)
+ if err != nil {
+ tb.Fatalf("remove loose object %s: %v", id, err)
+ }
+}
diff --git a/internal/testgit/repo_run.go b/internal/testgit/repo_run.go
index 162a0d72..448b88f0 100644
--- a/internal/testgit/repo_run.go
+++ b/internal/testgit/repo_run.go
@@ -22,6 +22,15 @@ func (testRepo *TestRepo) RunBytes(tb testing.TB, args ...string) []byte {
return testRepo.runBytes(tb, nil, testRepo.dir, args...)
}
+// RunE executes git and returns trimmed textual output plus any command error.
+func (testRepo *TestRepo) RunE(tb testing.TB, args ...string) (string, error) {
+ tb.Helper()
+
+ out, err := testRepo.runBytesE(nil, testRepo.dir, args...)
+
+ return strings.TrimSpace(string(out)), err
+}
+
// RunInput executes git with stdin and returns trimmed textual output.
func (testRepo *TestRepo) RunInput(tb testing.TB, stdin []byte, args ...string) string {
tb.Helper()
@@ -39,19 +48,48 @@ func (testRepo *TestRepo) RunInputBytes(tb testing.TB, stdin []byte, args ...str
func (testRepo *TestRepo) runBytes(tb testing.TB, stdin []byte, dir string, args ...string) []byte {
tb.Helper()
+
+ out, err := testRepo.runBytesE(stdin, dir, args...)
+ if err != nil {
+ tb.Fatalf("git %v failed: %v\n%s", args, err, out)
+ }
+
+ return out
+}
+
+func (testRepo *TestRepo) runBytesE(stdin []byte, dir string, args ...string) ([]byte, error) {
+ return testRepo.runBytesWithEnvNoHelper(stdin, dir, testRepo.env, args...)
+}
+
+// runBytesWithEnv executes git using the supplied environment.
+func (testRepo *TestRepo) runBytesWithEnv(
+ tb testing.TB,
+ stdin []byte,
+ dir string,
+ env []string,
+ args ...string,
+) ([]byte, error) {
+ tb.Helper()
+
+ return testRepo.runBytesWithEnvNoHelper(stdin, dir, env, args...)
+}
+
+// runBytesWithEnvNoHelper executes git using the supplied environment without
+// touching testing helper state.
+func (testRepo *TestRepo) runBytesWithEnvNoHelper(
+ stdin []byte,
+ dir string,
+ env []string,
+ args ...string,
+) ([]byte, error) {
//nolint:noctx
cmd := exec.Command("git", args...) //#nosec G204
cmd.Dir = dir
- cmd.Env = testRepo.env
+ cmd.Env = env
if stdin != nil {
cmd.Stdin = bytes.NewReader(stdin)
}
- out, err := cmd.CombinedOutput()
- if err != nil {
- tb.Fatalf("git %v failed: %v\n%s", args, err, out)
- }
-
- return out
+ return cmd.CombinedOutput()
}