aboutsummaryrefslogtreecommitdiff
path: root/commitquery/queries_merge_bases_integration_test.go
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-03-29 14:42:13 +0000
committerGravatar Runxi Yu2026-03-29 14:47:04 +0000
commitdf73a4c6f1b58075316ba7449fbfb127b9fbb79d (patch)
tree62fee259ec037410b06419ee3ac9c2c189c35ab3 /commitquery/queries_merge_bases_integration_test.go
parentinternal/priorityqueue: Update docs (diff)
signatureNo signature
commitquery: Reorganize
Diffstat (limited to 'commitquery/queries_merge_bases_integration_test.go')
-rw-r--r--commitquery/queries_merge_bases_integration_test.go311
1 files changed, 311 insertions, 0 deletions
diff --git a/commitquery/queries_merge_bases_integration_test.go b/commitquery/queries_merge_bases_integration_test.go
new file mode 100644
index 00000000..f19468eb
--- /dev/null
+++ b/commitquery/queries_merge_bases_integration_test.go
@@ -0,0 +1,311 @@
+package commitquery_test
+
+import (
+ "maps"
+ "slices"
+ "strings"
+ "testing"
+
+ "codeberg.org/lindenii/furgit/commitquery"
+ "codeberg.org/lindenii/furgit/internal/testgit"
+ objectid "codeberg.org/lindenii/furgit/object/id"
+)
+
+func TestQueryMatchesGitMergeBaseAll(t *testing.T) {
+ t.Parallel()
+
+ testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
+ testRepo := testgit.NewRepo(t, testgit.RepoOptions{
+ ObjectFormat: algo,
+ Bare: true,
+ RefFormat: "files",
+ })
+
+ _, tree1 := testRepo.MakeSingleFileTree(t, "base.txt", []byte("base\n"))
+ base := testRepo.CommitTree(t, tree1, "base")
+
+ _, tree2 := testRepo.MakeSingleFileTree(t, "left.txt", []byte("left\n"))
+ left := testRepo.CommitTree(t, tree2, "left", base)
+
+ _, tree3 := testRepo.MakeSingleFileTree(t, "right.txt", []byte("right\n"))
+ right := testRepo.CommitTree(t, tree3, "right", base)
+
+ tag := testRepo.TagAnnotated(t, "right-tag", right, "right-tag")
+
+ store := testRepo.OpenObjectStore(t)
+
+ query := commitquery.New(store, nil)
+
+ all, err := query.MergeBases(left, tag)
+ if err != nil {
+ t.Fatalf("query.All(): %v", err)
+ }
+
+ got := oidSetFromSlice(all)
+
+ want := gitMergeBaseAllSet(t, testRepo, left, tag)
+ if !maps.Equal(got, want) {
+ t.Fatalf("Query(left, tag) mismatch:\n got=%v\nwant=%v", sortedOIDStrings(got), sortedOIDStrings(want))
+ }
+ })
+}
+
+func TestQueryCrissCrossMatchesGitMergeBaseAll(t *testing.T) {
+ t.Parallel()
+
+ testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
+ testRepo := testgit.NewRepo(t, testgit.RepoOptions{
+ ObjectFormat: algo,
+ Bare: true,
+ RefFormat: "files",
+ })
+
+ _, tree1 := testRepo.MakeSingleFileTree(t, "root.txt", []byte("root\n"))
+ root := testRepo.CommitTree(t, tree1, "root")
+
+ _, tree2 := testRepo.MakeSingleFileTree(t, "base1.txt", []byte("base1\n"))
+ base1 := testRepo.CommitTree(t, tree2, "base1", root)
+
+ _, tree3 := testRepo.MakeSingleFileTree(t, "base2.txt", []byte("base2\n"))
+ base2 := testRepo.CommitTree(t, tree3, "base2", root)
+
+ _, tree4 := testRepo.MakeSingleFileTree(t, "left.txt", []byte("left\n"))
+ left := testRepo.CommitTree(t, tree4, "left", base1, base2)
+
+ _, tree5 := testRepo.MakeSingleFileTree(t, "right.txt", []byte("right\n"))
+ right := testRepo.CommitTree(t, tree5, "right", base2, base1)
+
+ store := testRepo.OpenObjectStore(t)
+
+ query := commitquery.New(store, nil)
+
+ all, err := query.MergeBases(left, right)
+ if err != nil {
+ t.Fatalf("query.All(): %v", err)
+ }
+
+ got := oidSetFromSlice(all)
+
+ want := gitMergeBaseAllSet(t, testRepo, left, right)
+ if !maps.Equal(got, want) {
+ t.Fatalf("Query(left, right) mismatch:\n got=%v\nwant=%v", sortedOIDStrings(got), sortedOIDStrings(want))
+ }
+
+ first, ok, err := query.MergeBase(left, right)
+ if err != nil {
+ t.Fatalf("Base(left, right): %v", err)
+ }
+
+ if !ok {
+ t.Fatal("Base(left, right) unexpectedly reported no base")
+ }
+
+ if !containsID(want, first) {
+ t.Fatalf("Base(left, right)=%s, want one of %v", first, slices.Collect(maps.Keys(want)))
+ }
+ })
+}
+
+func TestQueryMatchesGitMergeBaseAllWithCommitGraph(t *testing.T) {
+ t.Parallel()
+
+ testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
+ testRepo := testgit.NewRepo(t, testgit.RepoOptions{
+ ObjectFormat: algo,
+ Bare: true,
+ RefFormat: "files",
+ })
+
+ _, tree1 := testRepo.MakeSingleFileTree(t, "root.txt", []byte("root\n"))
+ root := testRepo.CommitTree(t, tree1, "root")
+
+ _, tree2 := testRepo.MakeSingleFileTree(t, "base1.txt", []byte("base1\n"))
+ base1 := testRepo.CommitTree(t, tree2, "base1", root)
+
+ _, tree3 := testRepo.MakeSingleFileTree(t, "base2.txt", []byte("base2\n"))
+ base2 := testRepo.CommitTree(t, tree3, "base2", root)
+
+ _, tree4 := testRepo.MakeSingleFileTree(t, "left.txt", []byte("left\n"))
+ left := testRepo.CommitTree(t, tree4, "left", base1, base2)
+
+ _, tree5 := testRepo.MakeSingleFileTree(t, "right.txt", []byte("right\n"))
+ right := testRepo.CommitTree(t, tree5, "right", base2, base1)
+
+ testRepo.UpdateRef(t, "refs/heads/main", right)
+ testRepo.SymbolicRef(t, "HEAD", "refs/heads/main")
+ testRepo.CommitGraphWrite(t, "--reachable")
+
+ store := testRepo.OpenObjectStore(t)
+ graph := testRepo.OpenCommitGraph(t)
+
+ query := commitquery.New(store, graph)
+
+ all, err := query.MergeBases(left, right)
+ if err != nil {
+ t.Fatalf("query.All(): %v", err)
+ }
+
+ got := oidSetFromSlice(all)
+
+ want := gitMergeBaseAllSet(t, testRepo, left, right)
+ if !maps.Equal(got, want) {
+ t.Fatalf("Query(left, right) with commit-graph mismatch:\n got=%v\nwant=%v", sortedOIDStrings(got), sortedOIDStrings(want))
+ }
+
+ first, ok, err := query.MergeBase(left, right)
+ if err != nil {
+ t.Fatalf("Base(left, right): %v", err)
+ }
+
+ if !ok {
+ t.Fatal("Base(left, right) unexpectedly reported no base")
+ }
+
+ if !containsID(want, first) {
+ t.Fatalf("Base(left, right)=%s, want one of %v", first, slices.Collect(maps.Keys(want)))
+ }
+ })
+}
+
+func TestBaseMatchesGitMergeBaseWithoutAll(t *testing.T) {
+ t.Parallel()
+
+ testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
+ testRepo := testgit.NewRepo(t, testgit.RepoOptions{
+ ObjectFormat: algo,
+ Bare: true,
+ RefFormat: "files",
+ })
+
+ _, tree1 := testRepo.MakeSingleFileTree(t, "root.txt", []byte("root\n"))
+ root := testRepo.CommitTree(t, tree1, "root")
+
+ _, tree2 := testRepo.MakeSingleFileTree(t, "base1.txt", []byte("base1\n"))
+ base1 := testRepo.CommitTreeWithEnv(t, []string{
+ "GIT_AUTHOR_DATE=1234567890 +0000",
+ "GIT_COMMITTER_DATE=1234567890 +0000",
+ }, tree2, "base1", root)
+
+ _, tree3 := testRepo.MakeSingleFileTree(t, "base2.txt", []byte("base2\n"))
+ base2 := testRepo.CommitTreeWithEnv(t, []string{
+ "GIT_AUTHOR_DATE=1234567990 +0000",
+ "GIT_COMMITTER_DATE=1234567990 +0000",
+ }, tree3, "base2", root)
+
+ _, tree4 := testRepo.MakeSingleFileTree(t, "left.txt", []byte("left\n"))
+ left := testRepo.CommitTree(t, tree4, "left", base1, base2)
+
+ _, tree5 := testRepo.MakeSingleFileTree(t, "right.txt", []byte("right\n"))
+ right := testRepo.CommitTree(t, tree5, "right", base2, base1)
+
+ store := testRepo.OpenObjectStore(t)
+
+ query := commitquery.New(store, nil)
+
+ got, ok, err := query.MergeBase(left, right)
+ if err != nil {
+ t.Fatalf("Base(left, right): %v", err)
+ }
+
+ if !ok {
+ t.Fatal("Base(left, right) unexpectedly reported no base")
+ }
+
+ want := gitMergeBaseOne(t, testRepo, left, right)
+ if got != want {
+ t.Fatalf("Base(left, right)=%s, want %s", got, want)
+ }
+
+ testRepo.UpdateRef(t, "refs/heads/main", right)
+ testRepo.SymbolicRef(t, "HEAD", "refs/heads/main")
+ testRepo.CommitGraphWrite(t, "--reachable")
+
+ graph := testRepo.OpenCommitGraph(t)
+
+ got, ok, err = commitquery.New(store, graph).MergeBase(left, right)
+ if err != nil {
+ t.Fatalf("Base(left, right) with commit-graph: %v", err)
+ }
+
+ if !ok {
+ t.Fatal("Base(left, right) with commit-graph unexpectedly reported no base")
+ }
+
+ if got != want {
+ t.Fatalf("Base(left, right) with commit-graph=%s, want %s", got, want)
+ }
+ })
+}
+
+// oidSetFromSlice collects one object ID slice into a set.
+func oidSetFromSlice(ids []objectid.ObjectID) map[objectid.ObjectID]struct{} {
+ out := make(map[objectid.ObjectID]struct{})
+
+ for _, id := range ids {
+ out[id] = struct{}{}
+ }
+
+ return out
+}
+
+// gitMergeBaseAllSet returns Git's merge-base --all output as a set.
+func gitMergeBaseAllSet(
+ t *testing.T,
+ testRepo *testgit.TestRepo,
+ left objectid.ObjectID,
+ right objectid.ObjectID,
+) map[objectid.ObjectID]struct{} {
+ t.Helper()
+
+ out := testRepo.Run(t, "merge-base", "--all", left.String(), right.String())
+ set := make(map[objectid.ObjectID]struct{})
+
+ for line := range strings.SplitSeq(strings.TrimSpace(out), "\n") {
+ line = strings.TrimSpace(line)
+ if line == "" {
+ continue
+ }
+
+ id, err := objectid.ParseHex(testRepo.Algorithm(), line)
+ if err != nil {
+ t.Fatalf("parse merge-base oid %q: %v", line, err)
+ }
+
+ set[id] = struct{}{}
+ }
+
+ return set
+}
+
+// gitMergeBaseOne returns Git's merge-base output without --all.
+func gitMergeBaseOne(
+ t *testing.T,
+ testRepo *testgit.TestRepo,
+ left objectid.ObjectID,
+ right objectid.ObjectID,
+) objectid.ObjectID {
+ t.Helper()
+
+ out := strings.TrimSpace(testRepo.Run(t, "merge-base", left.String(), right.String()))
+ if out == "" {
+ t.Fatal("git merge-base returned no output")
+ }
+
+ id, err := objectid.ParseHex(testRepo.Algorithm(), out)
+ if err != nil {
+ t.Fatalf("parse merge-base oid %q: %v", out, err)
+ }
+
+ return id
+}
+
+func sortedOIDStrings(set map[objectid.ObjectID]struct{}) []string {
+ out := make([]string, 0, len(set))
+ for id := range set {
+ out = append(out, id.String())
+ }
+
+ slices.Sort(out)
+
+ return out
+}