diff options
| -rw-r--r-- | internal/testgit/repo_make_many_objects_history.go | 83 | ||||
| -rw-r--r-- | internal/testgit/repo_pack_objects_reader.go | 94 |
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 + }, + } +} |
