diff options
| author | 2026-02-21 15:54:26 +0800 | |
|---|---|---|
| committer | 2026-02-21 15:54:26 +0800 | |
| commit | 6a7fc936c4a969aa05b3941feedafe59f4bd2ffd (patch) | |
| tree | 9518dd365d76f784117a1e5df512fb902e71f7db /repository | |
| parent | repository: Add loose object writing (diff) | |
| signature | No signature | |
*: Add more tests
Diffstat (limited to 'repository')
| -rw-r--r-- | repository/refs_test.go | 72 | ||||
| -rw-r--r-- | repository/stored_test.go | 103 | ||||
| -rw-r--r-- | repository/traversal_test.go | 79 |
3 files changed, 254 insertions, 0 deletions
diff --git a/repository/refs_test.go b/repository/refs_test.go index 4418c707..8ebf93a6 100644 --- a/repository/refs_test.go +++ b/repository/refs_test.go @@ -94,3 +94,75 @@ func TestResolveRefErrorSurface(t *testing.T) { } }) } + +func TestListRefsLooseOverridesPacked(t *testing.T) { + t.Parallel() + + testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper + repoHarness := testgit.NewRepo(t, testgit.RepoOptions{ + ObjectFormat: algo, + Bare: true, + RefFormat: "files", + }) + + repoHarness.SymbolicRef(t, "HEAD", "refs/heads/main") + _, _, commit1 := repoHarness.MakeCommit(t, "commit-one") + repoHarness.UpdateRef(t, "refs/heads/main", commit1) + repoHarness.UpdateRef(t, "refs/heads/feature", commit1) + repoHarness.PackRefs(t, "--all", "--prune") + + _, _, commit2 := repoHarness.MakeCommit(t, "commit-two") + repoHarness.UpdateRef(t, "refs/heads/main", commit2) + + repo, err := repository.Open(repoHarness.Dir()) + if err != nil { + t.Fatalf("repository.Open: %v", err) + } + defer func() { _ = repo.Close() }() + + mainRef, err := repo.ResolveRefFully("refs/heads/main") + if err != nil { + t.Fatalf("ResolveRefFully(main): %v", err) + } + if mainRef.ID != commit2 { + t.Fatalf("ResolveRefFully(main) id = %s, want %s", mainRef.ID, commit2) + } + + refs, err := repo.ListRefs("refs/heads/*") + if err != nil { + t.Fatalf("ListRefs(refs/heads/*): %v", err) + } + byName := make(map[string]ref.Ref, len(refs)) + for _, entry := range refs { + name := entry.Name() + if _, exists := byName[name]; exists { + t.Fatalf("duplicate ref %q in ListRefs output", name) + } + byName[name] = entry + } + + main, ok := byName["refs/heads/main"] + if !ok { + t.Fatalf("missing refs/heads/main in ListRefs output") + } + mainDetached, ok := main.(ref.Detached) + if !ok { + t.Fatalf("refs/heads/main type = %T, want ref.Detached", main) + } + if mainDetached.ID != commit2 { + t.Fatalf("refs/heads/main id = %s, want %s", mainDetached.ID, commit2) + } + + feature, ok := byName["refs/heads/feature"] + if !ok { + t.Fatalf("missing refs/heads/feature in ListRefs output") + } + featureDetached, ok := feature.(ref.Detached) + if !ok { + t.Fatalf("refs/heads/feature type = %T, want ref.Detached", feature) + } + if featureDetached.ID != commit1 { + t.Fatalf("refs/heads/feature id = %s, want %s", featureDetached.ID, commit1) + } + }) +} diff --git a/repository/stored_test.go b/repository/stored_test.go index da1d1392..3768d450 100644 --- a/repository/stored_test.go +++ b/repository/stored_test.go @@ -161,3 +161,106 @@ func TestResolveTreeEntryErrors(t *testing.T) { }) }) } + +func TestResolveTreeEntryDeepPath(t *testing.T) { + t.Parallel() + + testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper + const depth = 50 + + repoHarness := testgit.NewRepo(t, testgit.RepoOptions{ + ObjectFormat: algo, + Bare: true, + RefFormat: "files", + }) + + leafBlobID := repoHarness.HashObject(t, "blob", []byte("deep-content\n")) + currentTree := repoHarness.Mktree(t, fmt.Sprintf("100644 blob %s\tleaf.txt\n", leafBlobID)) + + parts := make([][]byte, 0, depth+1) + for i := depth - 1; i >= 0; i-- { + name := fmt.Sprintf("level%02d", i) + currentTree = repoHarness.Mktree(t, fmt.Sprintf("040000 tree %s\t%s\n", currentTree, name)) + parts = append([][]byte{[]byte(name)}, parts...) + } + parts = append(parts, []byte("leaf.txt")) + + repo, err := repository.Open(repoHarness.Dir()) + if err != nil { + t.Fatalf("repository.Open: %v", err) + } + defer func() { _ = repo.Close() }() + + rootTree, err := repo.ReadStoredTree(currentTree) + if err != nil { + t.Fatalf("ReadStoredTree(root): %v", err) + } + + entry, err := repo.ResolveTreeEntry(rootTree, parts) + if err != nil { + t.Fatalf("ResolveTreeEntry(deep): %v", err) + } + if entry.Mode != object.FileModeRegular { + t.Fatalf("ResolveTreeEntry(deep) mode = %o, want %o", entry.Mode, object.FileModeRegular) + } + if entry.ID != leafBlobID { + t.Fatalf("ResolveTreeEntry(deep) id = %s, want %s", entry.ID, leafBlobID) + } + }) +} + +func TestReadStoredTreeMixedModes(t *testing.T) { + t.Parallel() + + testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper + repoHarness := testgit.NewRepo(t, testgit.RepoOptions{ + ObjectFormat: algo, + Bare: true, + RefFormat: "files", + }) + + normalID := repoHarness.HashObject(t, "blob", []byte("normal-file\n")) + execID := repoHarness.HashObject(t, "blob", []byte("#!/bin/sh\necho hi\n")) + symID := repoHarness.HashObject(t, "blob", []byte("normal.txt")) + nestedBlobID := repoHarness.HashObject(t, "blob", []byte("nested\n")) + nestedTreeID := repoHarness.Mktree(t, fmt.Sprintf("100644 blob %s\tleaf.txt\n", nestedBlobID)) + + rootTreeID := repoHarness.Mktree(t, + fmt.Sprintf( + "100644 blob %s\tnormal.txt\n100755 blob %s\trun.sh\n120000 blob %s\tlink.txt\n040000 tree %s\tdir\n", + normalID, + execID, + symID, + nestedTreeID, + ), + ) + + repo, err := repository.Open(repoHarness.Dir()) + if err != nil { + t.Fatalf("repository.Open: %v", err) + } + defer func() { _ = repo.Close() }() + + rootTree, err := repo.ReadStoredTree(rootTreeID) + if err != nil { + t.Fatalf("ReadStoredTree(root): %v", err) + } + + expect := map[string]object.FileMode{ + "normal.txt": object.FileModeRegular, + "run.sh": object.FileModeExecutable, + "link.txt": object.FileModeSymlink, + "dir": object.FileModeDir, + } + + for name, wantMode := range expect { + entry := rootTree.Tree().Entry([]byte(name)) + if entry == nil { + t.Fatalf("Entry(%q) returned nil", name) + } + if entry.Mode != wantMode { + t.Fatalf("Entry(%q) mode = %o, want %o", name, entry.Mode, wantMode) + } + } + }) +} diff --git a/repository/traversal_test.go b/repository/traversal_test.go new file mode 100644 index 00000000..61560fde --- /dev/null +++ b/repository/traversal_test.go @@ -0,0 +1,79 @@ +package repository_test + +import ( + "testing" + + "codeberg.org/lindenii/furgit/internal/testgit" + "codeberg.org/lindenii/furgit/object" + "codeberg.org/lindenii/furgit/objectid" + "codeberg.org/lindenii/furgit/repository" +) + +func TestRepositoryDepthFirstEnumerationFromHEAD(t *testing.T) { + t.Parallel() + + testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper + repoHarness := testgit.NewRepo(t, testgit.RepoOptions{ + ObjectFormat: algo, + Bare: true, + RefFormat: "files", + }) + + _, _, commit1 := repoHarness.MakeCommit(t, "walk-one") + blob2, tree2 := repoHarness.MakeSingleFileTree(t, "second.txt", []byte("second\n")) + commit2 := repoHarness.CommitTree(t, tree2, "walk-two", commit1) + _ = blob2 + repoHarness.UpdateRef(t, "refs/heads/main", commit2) + repoHarness.SymbolicRef(t, "HEAD", "refs/heads/main") + + repo, err := repository.Open(repoHarness.Dir()) + if err != nil { + t.Fatalf("repository.Open: %v", err) + } + defer func() { _ = repo.Close() }() + + head, err := repo.ResolveRefFully("HEAD") + if err != nil { + t.Fatalf("ResolveRefFully(HEAD): %v", err) + } + + visited := make(map[objectid.ObjectID]bool) + queue := []objectid.ObjectID{head.ID} + objectsRead := 0 + + for len(queue) > 0 { + id := queue[0] + queue = queue[1:] + + if visited[id] { + continue + } + visited[id] = true + + stored, err := repo.ReadStored(id) + if err != nil { + t.Fatalf("ReadStored(%s): %v", id, err) + } + objectsRead++ + + switch obj := stored.Object().(type) { + case *object.Commit: + queue = append(queue, obj.Tree) + queue = append(queue, obj.Parents...) + case *object.Tree: + for _, entry := range obj.Entries { + queue = append(queue, entry.ID) + } + case *object.Tag: + queue = append(queue, obj.Target) + case *object.Blob: + default: + t.Fatalf("unexpected object type: %T", obj) + } + } + + if objectsRead == 0 { + t.Fatalf("no objects were enumerated from HEAD") + } + }) +} |
