aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/testgit/algorithms.go25
-rw-r--r--internal/testgit/repo.go10
-rw-r--r--internal/testgit/repo_cat_file.go13
-rw-r--r--internal/testgit/repo_commit_tree.go23
-rw-r--r--internal/testgit/repo_hash_object.go18
-rw-r--r--internal/testgit/repo_make_commit.go15
-rw-r--r--internal/testgit/repo_make_single_file_tree.go17
-rw-r--r--internal/testgit/repo_mktree.go18
-rw-r--r--internal/testgit/repo_new.go56
-rw-r--r--internal/testgit/repo_properties.go13
-rw-r--r--internal/testgit/repo_rev_parse.go18
-rw-r--r--internal/testgit/repo_run.go49
-rw-r--r--internal/testgit/repo_tag_annotated.go15
13 files changed, 290 insertions, 0 deletions
diff --git a/internal/testgit/algorithms.go b/internal/testgit/algorithms.go
new file mode 100644
index 00000000..833b846b
--- /dev/null
+++ b/internal/testgit/algorithms.go
@@ -0,0 +1,25 @@
+package testgit
+
+import (
+ "testing"
+
+ "codeberg.org/lindenii/furgit/oid"
+)
+
+// SupportedAlgorithms returns all object ID algorithms supported by furgit.
+func SupportedAlgorithms() []oid.Algorithm {
+ return []oid.Algorithm{
+ oid.AlgorithmSHA1,
+ oid.AlgorithmSHA256,
+ }
+}
+
+// ForEachAlgorithm runs a subtest for every supported algorithm.
+func ForEachAlgorithm(t *testing.T, fn func(t *testing.T, algo oid.Algorithm)) {
+ t.Helper()
+ for _, algo := range SupportedAlgorithms() {
+ t.Run(algo.String(), func(t *testing.T) {
+ fn(t, algo)
+ })
+ }
+}
diff --git a/internal/testgit/repo.go b/internal/testgit/repo.go
new file mode 100644
index 00000000..c2d8b088
--- /dev/null
+++ b/internal/testgit/repo.go
@@ -0,0 +1,10 @@
+package testgit
+
+import "codeberg.org/lindenii/furgit/oid"
+
+// TestRepo is a temporary git repository harness for integration tests.
+type TestRepo struct {
+ dir string
+ algo oid.Algorithm
+ env []string
+}
diff --git a/internal/testgit/repo_cat_file.go b/internal/testgit/repo_cat_file.go
new file mode 100644
index 00000000..7533e484
--- /dev/null
+++ b/internal/testgit/repo_cat_file.go
@@ -0,0 +1,13 @@
+package testgit
+
+import (
+ "testing"
+
+ "codeberg.org/lindenii/furgit/oid"
+)
+
+// CatFile returns raw output from git cat-file.
+func (repo *TestRepo) CatFile(tb testing.TB, mode string, id oid.ObjectID) []byte {
+ tb.Helper()
+ return repo.RunBytes(tb, "cat-file", mode, id.String())
+}
diff --git a/internal/testgit/repo_commit_tree.go b/internal/testgit/repo_commit_tree.go
new file mode 100644
index 00000000..b5f2f587
--- /dev/null
+++ b/internal/testgit/repo_commit_tree.go
@@ -0,0 +1,23 @@
+package testgit
+
+import (
+ "testing"
+
+ "codeberg.org/lindenii/furgit/oid"
+)
+
+// CommitTree creates a commit from a tree and message, optionally with parents.
+func (repo *TestRepo) CommitTree(tb testing.TB, tree oid.ObjectID, message string, parents ...oid.ObjectID) oid.ObjectID {
+ tb.Helper()
+ args := []string{"commit-tree", tree.String()}
+ for _, p := range parents {
+ args = append(args, "-p", p.String())
+ }
+ args = append(args, "-m", message)
+ hex := repo.Run(tb, args...)
+ id, err := oid.ParseHex(repo.algo, hex)
+ if err != nil {
+ tb.Fatalf("parse commit-tree output %q: %v", hex, err)
+ }
+ return id
+}
diff --git a/internal/testgit/repo_hash_object.go b/internal/testgit/repo_hash_object.go
new file mode 100644
index 00000000..97a86be5
--- /dev/null
+++ b/internal/testgit/repo_hash_object.go
@@ -0,0 +1,18 @@
+package testgit
+
+import (
+ "testing"
+
+ "codeberg.org/lindenii/furgit/oid"
+)
+
+// HashObject hashes and writes an object and returns its object ID.
+func (repo *TestRepo) HashObject(tb testing.TB, objType string, body []byte) oid.ObjectID {
+ tb.Helper()
+ hex := repo.RunInput(tb, body, "hash-object", "-t", objType, "-w", "--stdin")
+ id, err := oid.ParseHex(repo.algo, hex)
+ if err != nil {
+ tb.Fatalf("parse git hash-object output %q: %v", hex, err)
+ }
+ return id
+}
diff --git a/internal/testgit/repo_make_commit.go b/internal/testgit/repo_make_commit.go
new file mode 100644
index 00000000..a329ee8a
--- /dev/null
+++ b/internal/testgit/repo_make_commit.go
@@ -0,0 +1,15 @@
+package testgit
+
+import (
+ "testing"
+
+ "codeberg.org/lindenii/furgit/oid"
+)
+
+// MakeCommit creates a commit over a single-file tree and returns (blobID, treeID, commitID).
+func (repo *TestRepo) MakeCommit(tb testing.TB, message string) (oid.ObjectID, oid.ObjectID, oid.ObjectID) {
+ tb.Helper()
+ blobID, treeID := repo.MakeSingleFileTree(tb, "file.txt", []byte("commit-body\n"))
+ commitID := repo.CommitTree(tb, treeID, message)
+ return blobID, treeID, commitID
+}
diff --git a/internal/testgit/repo_make_single_file_tree.go b/internal/testgit/repo_make_single_file_tree.go
new file mode 100644
index 00000000..69b2362e
--- /dev/null
+++ b/internal/testgit/repo_make_single_file_tree.go
@@ -0,0 +1,17 @@
+package testgit
+
+import (
+ "fmt"
+ "testing"
+
+ "codeberg.org/lindenii/furgit/oid"
+)
+
+// MakeSingleFileTree writes one blob and one tree entry for it and returns (blobID, treeID).
+func (repo *TestRepo) MakeSingleFileTree(tb testing.TB, fileName string, fileContent []byte) (oid.ObjectID, oid.ObjectID) {
+ tb.Helper()
+ blobID := repo.HashObject(tb, "blob", fileContent)
+ treeInput := fmt.Sprintf("100644 blob %s\t%s\n", blobID.String(), fileName)
+ treeID := repo.Mktree(tb, treeInput)
+ return blobID, treeID
+}
diff --git a/internal/testgit/repo_mktree.go b/internal/testgit/repo_mktree.go
new file mode 100644
index 00000000..bd2785e2
--- /dev/null
+++ b/internal/testgit/repo_mktree.go
@@ -0,0 +1,18 @@
+package testgit
+
+import (
+ "testing"
+
+ "codeberg.org/lindenii/furgit/oid"
+)
+
+// Mktree creates a tree from textual mktree input and returns its ID.
+func (repo *TestRepo) Mktree(tb testing.TB, input string) oid.ObjectID {
+ tb.Helper()
+ hex := repo.RunInput(tb, []byte(input), "mktree")
+ id, err := oid.ParseHex(repo.algo, hex)
+ if err != nil {
+ tb.Fatalf("parse mktree output %q: %v", hex, err)
+ }
+ return id
+}
diff --git a/internal/testgit/repo_new.go b/internal/testgit/repo_new.go
new file mode 100644
index 00000000..51277f78
--- /dev/null
+++ b/internal/testgit/repo_new.go
@@ -0,0 +1,56 @@
+package testgit
+
+import (
+ "os"
+ "testing"
+
+ "codeberg.org/lindenii/furgit/oid"
+)
+
+// NewBareRepo creates a temporary bare repository initialized with the requested algorithm.
+func NewBareRepo(tb testing.TB, algo oid.Algorithm) *TestRepo {
+ tb.Helper()
+ return newRepo(tb, algo, true)
+}
+
+// NewWorkRepo creates a temporary non-bare repository initialized with the requested algorithm.
+func NewWorkRepo(tb testing.TB, algo oid.Algorithm) *TestRepo {
+ tb.Helper()
+ return newRepo(tb, algo, false)
+}
+
+func newRepo(tb testing.TB, algo oid.Algorithm, bare bool) *TestRepo {
+ tb.Helper()
+ if algo.Size() == 0 {
+ tb.Fatalf("invalid algorithm: %v", algo)
+ }
+
+ dir, err := os.MkdirTemp("", "furgit-testgit-*")
+ if err != nil {
+ tb.Fatalf("create temp dir: %v", err)
+ }
+ tb.Cleanup(func() { _ = os.RemoveAll(dir) })
+
+ repo := &TestRepo{
+ dir: dir,
+ algo: algo,
+ env: append(os.Environ(),
+ "GIT_CONFIG_GLOBAL=/dev/null",
+ "GIT_CONFIG_SYSTEM=/dev/null",
+ "GIT_AUTHOR_NAME=Test Author",
+ "GIT_AUTHOR_EMAIL=test@example.org",
+ "GIT_COMMITTER_NAME=Test Committer",
+ "GIT_COMMITTER_EMAIL=committer@example.org",
+ "GIT_AUTHOR_DATE=1234567890 +0000",
+ "GIT_COMMITTER_DATE=1234567890 +0000",
+ ),
+ }
+
+ args := []string{"init", "--object-format=" + algo.String()}
+ if bare {
+ args = append(args, "--bare")
+ }
+ args = append(args, dir)
+ repo.runBytes(tb, nil, "", args...)
+ return repo
+}
diff --git a/internal/testgit/repo_properties.go b/internal/testgit/repo_properties.go
new file mode 100644
index 00000000..9a48a43b
--- /dev/null
+++ b/internal/testgit/repo_properties.go
@@ -0,0 +1,13 @@
+package testgit
+
+import "codeberg.org/lindenii/furgit/oid"
+
+// Dir returns the repository directory path.
+func (repo *TestRepo) Dir() string {
+ return repo.dir
+}
+
+// Algorithm returns the object ID algorithm configured for this repository.
+func (repo *TestRepo) Algorithm() oid.Algorithm {
+ return repo.algo
+}
diff --git a/internal/testgit/repo_rev_parse.go b/internal/testgit/repo_rev_parse.go
new file mode 100644
index 00000000..7c3365a1
--- /dev/null
+++ b/internal/testgit/repo_rev_parse.go
@@ -0,0 +1,18 @@
+package testgit
+
+import (
+ "testing"
+
+ "codeberg.org/lindenii/furgit/oid"
+)
+
+// RevParse resolves rev expressions to object IDs.
+func (repo *TestRepo) RevParse(tb testing.TB, spec string) oid.ObjectID {
+ tb.Helper()
+ hex := repo.Run(tb, "rev-parse", spec)
+ id, err := oid.ParseHex(repo.algo, hex)
+ if err != nil {
+ tb.Fatalf("parse rev-parse output %q: %v", hex, err)
+ }
+ return id
+}
diff --git a/internal/testgit/repo_run.go b/internal/testgit/repo_run.go
new file mode 100644
index 00000000..66222569
--- /dev/null
+++ b/internal/testgit/repo_run.go
@@ -0,0 +1,49 @@
+package testgit
+
+import (
+ "bytes"
+ "os/exec"
+ "strings"
+ "testing"
+)
+
+// Run executes git and returns trimmed textual output.
+func (repo *TestRepo) Run(tb testing.TB, args ...string) string {
+ tb.Helper()
+ out := repo.runBytes(tb, nil, repo.dir, args...)
+ return strings.TrimSpace(string(out))
+}
+
+// RunBytes executes git and returns raw output bytes.
+func (repo *TestRepo) RunBytes(tb testing.TB, args ...string) []byte {
+ tb.Helper()
+ return repo.runBytes(tb, nil, repo.dir, args...)
+}
+
+// RunInput executes git with stdin and returns trimmed textual output.
+func (repo *TestRepo) RunInput(tb testing.TB, stdin []byte, args ...string) string {
+ tb.Helper()
+ out := repo.runBytes(tb, stdin, repo.dir, args...)
+ return strings.TrimSpace(string(out))
+}
+
+// RunInputBytes executes git with stdin and returns raw output bytes.
+func (repo *TestRepo) RunInputBytes(tb testing.TB, stdin []byte, args ...string) []byte {
+ tb.Helper()
+ return repo.runBytes(tb, stdin, repo.dir, args...)
+}
+
+func (repo *TestRepo) runBytes(tb testing.TB, stdin []byte, dir string, args ...string) []byte {
+ tb.Helper()
+ cmd := exec.Command("git", args...)
+ cmd.Dir = dir
+ cmd.Env = repo.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
+}
diff --git a/internal/testgit/repo_tag_annotated.go b/internal/testgit/repo_tag_annotated.go
new file mode 100644
index 00000000..167aaa96
--- /dev/null
+++ b/internal/testgit/repo_tag_annotated.go
@@ -0,0 +1,15 @@
+package testgit
+
+import (
+ "fmt"
+ "testing"
+
+ "codeberg.org/lindenii/furgit/oid"
+)
+
+// TagAnnotated creates an annotated tag object and returns the resulting tag object ID.
+func (repo *TestRepo) TagAnnotated(tb testing.TB, name string, target oid.ObjectID, message string) oid.ObjectID {
+ tb.Helper()
+ repo.Run(tb, "tag", "-a", name, target.String(), "-m", message)
+ return repo.RevParse(tb, fmt.Sprintf("refs/tags/%s", name))
+}