aboutsummaryrefslogtreecommitdiff
path: root/internal/testgit
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-03-05 17:01:31 +0800
committerGravatar Runxi Yu2026-03-05 17:54:07 +0800
commitdfe20bce3551f4b53f97362b14bd55a7ba0e0de0 (patch)
treede38340bd587ed63b09022dcb4a38e08ac1b5491 /internal/testgit
parentREADME: implemented/planned (diff)
signatureNo signature
testgit: Add pack object reader and many object maker
Diffstat (limited to 'internal/testgit')
-rw-r--r--internal/testgit/repo_make_many_objects_history.go83
-rw-r--r--internal/testgit/repo_pack_objects_reader.go94
2 files changed, 177 insertions, 0 deletions
diff --git a/internal/testgit/repo_make_many_objects_history.go b/internal/testgit/repo_make_many_objects_history.go
new file mode 100644
index 00000000..17cce1ad
--- /dev/null
+++ b/internal/testgit/repo_make_many_objects_history.go
@@ -0,0 +1,83 @@
+package testgit
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+
+ "codeberg.org/lindenii/furgit/objectid"
+)
+
+const (
+ manyObjectsMainCommits = 640
+ manyObjectsDevCommits = 220
+)
+
+// MakeManyObjectsHistory creates a large commit graph.
+func (testRepo *TestRepo) MakeManyObjectsHistory(tb testing.TB) {
+ tb.Helper()
+
+ var (
+ mainTip objectid.ObjectID
+ devTip objectid.ObjectID
+ hasMain bool
+ hasDev bool
+ )
+
+ for i := range manyObjectsMainCommits {
+ tree := testRepo.makeManyObjectsTree(tb, "main", i, 3)
+
+ var commit objectid.ObjectID
+ if hasMain {
+ commit = testRepo.CommitTree(tb, tree, fmt.Sprintf("main-%04d", i), mainTip)
+ } else {
+ commit = testRepo.CommitTree(tb, tree, fmt.Sprintf("main-%04d", i))
+ hasMain = true
+ }
+
+ mainTip = commit
+ if i%64 == 0 {
+ testRepo.TagAnnotated(tb, fmt.Sprintf("main-v%04d", i), mainTip, fmt.Sprintf("tag-main-%04d", i))
+ }
+ }
+
+ devTip = mainTip
+ hasDev = true
+
+ for i := range manyObjectsDevCommits {
+ tree := testRepo.makeManyObjectsTree(tb, "dev", i, 4)
+ commit := testRepo.CommitTree(tb, tree, fmt.Sprintf("dev-%04d", i), devTip)
+ devTip = commit
+
+ if i > 0 && i%55 == 0 {
+ mergeTree := testRepo.makeManyObjectsTree(tb, "merge", i, 2)
+
+ mainTip = testRepo.CommitTree(tb, mergeTree, fmt.Sprintf("merge-%04d", i), mainTip, devTip)
+ if i%110 == 0 {
+ testRepo.TagAnnotated(tb, fmt.Sprintf("merge-v%04d", i), mainTip, fmt.Sprintf("tag-merge-%04d", i))
+ }
+ }
+ }
+
+ if hasMain {
+ testRepo.UpdateRef(tb, "refs/heads/main", mainTip)
+ }
+
+ if hasDev {
+ testRepo.UpdateRef(tb, "refs/heads/dev", devTip)
+ }
+}
+
+// makeManyObjectsTree builds one synthetic tree with fanout blobs.
+func (testRepo *TestRepo) makeManyObjectsTree(tb testing.TB, prefix string, i int, files int) objectid.ObjectID {
+ tb.Helper()
+
+ lines := make([]string, 0, files)
+ for j := range files {
+ body := []byte(fmt.Sprintf("%s-%04d-%02d\n%s\n", prefix, i, j, strings.Repeat("x", 160+(i+j)%96)))
+ blobID := testRepo.HashObject(tb, "blob", body)
+ lines = append(lines, fmt.Sprintf("100644 blob %s\t%s_%04d_%02d.txt\n", blobID.String(), prefix, i, j))
+ }
+
+ return testRepo.Mktree(tb, strings.Join(lines, ""))
+}
diff --git a/internal/testgit/repo_pack_objects_reader.go b/internal/testgit/repo_pack_objects_reader.go
new file mode 100644
index 00000000..dc997514
--- /dev/null
+++ b/internal/testgit/repo_pack_objects_reader.go
@@ -0,0 +1,94 @@
+package testgit
+
+import (
+ "fmt"
+ "io"
+ "os/exec"
+ "strings"
+ "sync"
+ "testing"
+)
+
+// packObjectsReadCloser wraps a pipe reader and process wait fn.
+type packObjectsReadCloser struct {
+ reader io.ReadCloser
+ wait func() error
+ once sync.Once
+}
+
+// Read proxies reads to the wrapped reader.
+func (reader *packObjectsReadCloser) Read(dst []byte) (int, error) {
+ return reader.reader.Read(dst)
+}
+
+// Close closes the stream and waits for the underlying process.
+func (reader *packObjectsReadCloser) Close() error {
+ var out error
+
+ reader.once.Do(func() {
+ errClose := reader.reader.Close()
+ errWait := reader.wait()
+
+ if errClose != nil {
+ out = errClose
+
+ return
+ }
+
+ out = errWait
+ })
+
+ return out
+}
+
+// PackObjectsReader streams `git pack-objects --stdout --revs` output.
+func (testRepo *TestRepo) PackObjectsReader(tb testing.TB, revs []string, thin bool) io.ReadCloser {
+ tb.Helper()
+
+ args := []string{"pack-objects", "--stdout", "--revs"}
+ if thin {
+ args = append(args, "--thin")
+ }
+
+ //nolint:noctx
+ cmd := exec.Command("git", args...) //#nosec G204
+ cmd.Dir = testRepo.dir
+ cmd.Env = testRepo.env
+ cmd.Stdin = strings.NewReader(strings.Join(revs, "\n") + "\n")
+
+ pr, pw := io.Pipe()
+ cmd.Stdout = pw
+ stderr := &strings.Builder{}
+ cmd.Stderr = stderr
+
+ waitDone := make(chan error, 1)
+
+ go func() {
+ err := cmd.Start()
+ if err != nil {
+ _ = pw.CloseWithError(fmt.Errorf("git %v start failed: %w", args, err))
+
+ waitDone <- nil
+
+ return
+ }
+
+ err = cmd.Wait()
+ if err != nil {
+ _ = pw.CloseWithError(fmt.Errorf("git %v failed: %w\n%s", args, err, stderr.String()))
+ } else {
+ _ = pw.Close()
+ }
+
+ waitDone <- nil
+ }()
+
+ return &packObjectsReadCloser{
+ reader: pr,
+ wait: func() error {
+ <-waitDone
+
+ return nil
+ },
+ }
+}