diff options
| author | 2026-03-04 08:26:56 +0800 | |
|---|---|---|
| committer | 2026-03-04 08:59:53 +0800 | |
| commit | ab7501be34032fb9e5c48726a68ae90a917af9eb (patch) | |
| tree | 20d005647569befea8133e953c3270e8fd2a2a5b /repository | |
| parent | *: gofumpt (diff) | |
| signature | No signature | |
*: Lint
Diffstat (limited to 'repository')
| -rw-r--r-- | repository/read_stored.go | 12 | ||||
| -rw-r--r-- | repository/read_stored_passthrough_test.go | 17 | ||||
| -rw-r--r-- | repository/refs_test.go | 19 | ||||
| -rw-r--r-- | repository/repository.go | 43 | ||||
| -rw-r--r-- | repository/repository_test.go | 12 | ||||
| -rw-r--r-- | repository/stored_test.go | 23 | ||||
| -rw-r--r-- | repository/traversal_bench_test.go | 5 | ||||
| -rw-r--r-- | repository/traversal_helpers_test.go | 15 | ||||
| -rw-r--r-- | repository/traversal_test.go | 15 | ||||
| -rw-r--r-- | repository/tree_resolve.go | 5 | ||||
| -rw-r--r-- | repository/write_loose.go | 4 | ||||
| -rw-r--r-- | repository/write_loose_test.go | 12 |
12 files changed, 176 insertions, 6 deletions
diff --git a/repository/read_stored.go b/repository/read_stored.go index b26421e8..1e92dc40 100644 --- a/repository/read_stored.go +++ b/repository/read_stored.go @@ -15,6 +15,7 @@ func (repo *Repository) ReadStored(id objectid.ObjectID) (objectstored.StoredObj if err != nil { return nil, err } + switch parsed := parsed.(type) { case *object.Blob: return objectstored.NewStoredBlob(id, parsed), nil @@ -35,10 +36,12 @@ func (repo *Repository) ReadStoredBlob(id objectid.ObjectID) (*objectstored.Stor if err != nil { return nil, err } + blob, ok := stored.(*objectstored.StoredBlob) if !ok { return nil, fmt.Errorf("repository: expected blob object %s, got %v", id, stored.Object().ObjectType()) } + return blob, nil } @@ -48,10 +51,12 @@ func (repo *Repository) ReadStoredTree(id objectid.ObjectID) (*objectstored.Stor if err != nil { return nil, err } + tree, ok := stored.(*objectstored.StoredTree) if !ok { return nil, fmt.Errorf("repository: expected tree object %s, got %v", id, stored.Object().ObjectType()) } + return tree, nil } @@ -61,10 +66,12 @@ func (repo *Repository) ReadStoredCommit(id objectid.ObjectID) (*objectstored.St if err != nil { return nil, err } + commit, ok := stored.(*objectstored.StoredCommit) if !ok { return nil, fmt.Errorf("repository: expected commit object %s, got %v", id, stored.Object().ObjectType()) } + return commit, nil } @@ -74,10 +81,12 @@ func (repo *Repository) ReadStoredTag(id objectid.ObjectID) (*objectstored.Store if err != nil { return nil, err } + tag, ok := stored.(*objectstored.StoredTag) if !ok { return nil, fmt.Errorf("repository: expected tag object %s, got %v", id, stored.Object().ObjectType()) } + return tag, nil } @@ -87,13 +96,16 @@ func (repo *Repository) readParsedObject(id objectid.ObjectID) (object.Object, e if err != nil { return nil, err } + parsed, err := object.ParseObjectWithoutHeader(ty, content, repo.algo) if err != nil { tyName, ok := objecttype.Name(ty) if !ok { tyName = fmt.Sprintf("type %d", ty) } + return nil, fmt.Errorf("repository: parse object %s (%s): %w", id, tyName, err) } + return parsed, nil } diff --git a/repository/read_stored_passthrough_test.go b/repository/read_stored_passthrough_test.go index 3adcc103..676dd428 100644 --- a/repository/read_stored_passthrough_test.go +++ b/repository/read_stored_passthrough_test.go @@ -28,21 +28,25 @@ func TestReadStoredPassThroughs(t *testing.T) { if err != nil { t.Fatalf("os.OpenRoot: %v", err) } + defer func() { _ = root.Close() }() repo, err := repository.Open(root) if err != nil { t.Fatalf("repository.Open: %v", err) } + defer func() { _ = repo.Close() }() headerTy, headerSize, err := repo.ReadStoredHeader(commitID) if err != nil { t.Fatalf("ReadStoredHeader: %v", err) } + if headerTy != objecttype.TypeCommit { t.Fatalf("ReadStoredHeader type = %v, want %v", headerTy, objecttype.TypeCommit) } + if headerSize <= 0 { t.Fatalf("ReadStoredHeader size = %d, want > 0", headerSize) } @@ -51,6 +55,7 @@ func TestReadStoredPassThroughs(t *testing.T) { if err != nil { t.Fatalf("ReadStoredBytesFull: %v", err) } + if len(full) == 0 { t.Fatalf("ReadStoredBytesFull returned empty payload") } @@ -59,9 +64,11 @@ func TestReadStoredPassThroughs(t *testing.T) { if err != nil { t.Fatalf("ReadStoredBytesContent: %v", err) } + if contentTy != objecttype.TypeCommit { t.Fatalf("ReadStoredBytesContent type = %v, want %v", contentTy, objecttype.TypeCommit) } + if len(content) == 0 { t.Fatalf("ReadStoredBytesContent returned empty content") } @@ -70,14 +77,18 @@ func TestReadStoredPassThroughs(t *testing.T) { if err != nil { t.Fatalf("ReadStoredReaderFull: %v", err) } + fullReaderBytes, readErr := io.ReadAll(fullReader) closeErr := fullReader.Close() + if readErr != nil { t.Fatalf("ReadStoredReaderFull read: %v", readErr) } + if closeErr != nil { t.Fatalf("ReadStoredReaderFull close: %v", closeErr) } + if !bytes.Equal(fullReaderBytes, full) { t.Fatalf("ReadStoredReaderFull bytes mismatch against ReadStoredBytesFull") } @@ -86,20 +97,26 @@ func TestReadStoredPassThroughs(t *testing.T) { if err != nil { t.Fatalf("ReadStoredReaderContent: %v", err) } + if readerTy != objecttype.TypeCommit { t.Fatalf("ReadStoredReaderContent type = %v, want %v", readerTy, objecttype.TypeCommit) } + if readerSize != int64(len(content)) { t.Fatalf("ReadStoredReaderContent size = %d, want %d", readerSize, len(content)) } + readerContentBytes, readErr := io.ReadAll(contentReader) closeErr = contentReader.Close() + if readErr != nil { t.Fatalf("ReadStoredReaderContent read: %v", readErr) } + if closeErr != nil { t.Fatalf("ReadStoredReaderContent close: %v", closeErr) } + if !bytes.Equal(readerContentBytes, content) { t.Fatalf("ReadStoredReaderContent bytes mismatch against ReadStoredBytesContent") } diff --git a/repository/refs_test.go b/repository/refs_test.go index d0cb216b..68f01898 100644 --- a/repository/refs_test.go +++ b/repository/refs_test.go @@ -30,22 +30,26 @@ func TestRefConvenienceMethods(t *testing.T) { if err != nil { t.Fatalf("os.OpenRoot: %v", err) } + defer func() { _ = root.Close() }() repo, err := repository.Open(root) if err != nil { t.Fatalf("repository.Open: %v", err) } + defer func() { _ = repo.Close() }() resolved, err := repo.ResolveRef("HEAD") if err != nil { t.Fatalf("ResolveRef(HEAD): %v", err) } + sym, ok := resolved.(ref.Symbolic) if !ok { t.Fatalf("ResolveRef(HEAD) type = %T, want ref.Symbolic", resolved) } + if sym.Target != "refs/heads/main" { t.Fatalf("ResolveRef(HEAD) target = %q, want %q", sym.Target, "refs/heads/main") } @@ -54,6 +58,7 @@ func TestRefConvenienceMethods(t *testing.T) { if err != nil { t.Fatalf("ResolveRefFully(HEAD): %v", err) } + if fully.ID != commitID { t.Fatalf("ResolveRefFully(HEAD) id = %s, want %s", fully.ID, commitID) } @@ -62,6 +67,7 @@ func TestRefConvenienceMethods(t *testing.T) { if err != nil { t.Fatalf("ListRefs: %v", err) } + if len(refs) < 2 { t.Fatalf("ListRefs returned %d refs, want >= 2", len(refs)) } @@ -70,6 +76,7 @@ func TestRefConvenienceMethods(t *testing.T) { if err != nil { t.Fatalf("ShortenRef: %v", err) } + if short != "heads/main" && short != "main" { t.Fatalf("ShortenRef = %q, want %q or %q", short, "heads/main", "main") } @@ -90,18 +97,21 @@ func TestResolveRefErrorSurface(t *testing.T) { if err != nil { t.Fatalf("os.OpenRoot: %v", err) } + defer func() { _ = root.Close() }() repo, err := repository.Open(root) if err != nil { t.Fatalf("repository.Open: %v", err) } + defer func() { _ = repo.Close() }() _, err = repo.ResolveRef("refs/heads/does-not-exist") if err == nil { t.Fatalf("ResolveRef missing: expected error") } + if !strings.Contains(err.Error(), "not found") { t.Fatalf("ResolveRef missing error = %v, want not found detail", err) } @@ -131,18 +141,21 @@ func TestListRefsLooseOverridesPacked(t *testing.T) { if err != nil { t.Fatalf("os.OpenRoot: %v", err) } + defer func() { _ = root.Close() }() repo, err := repository.Open(root) 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) } @@ -151,12 +164,14 @@ func TestListRefsLooseOverridesPacked(t *testing.T) { 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 } @@ -164,10 +179,12 @@ func TestListRefsLooseOverridesPacked(t *testing.T) { 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) } @@ -176,10 +193,12 @@ func TestListRefsLooseOverridesPacked(t *testing.T) { 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/repository.go b/repository/repository.go index 31034d2a..9927264a 100644 --- a/repository/repository.go +++ b/repository/repository.go @@ -37,6 +37,7 @@ type Repository struct { // Open borrows root during construction and does not close it. func Open(root *os.Root) (repo *Repository, err error) { repo = &Repository{} + defer func() { if err != nil { _ = repo.Close() @@ -47,18 +48,21 @@ func Open(root *os.Root) (repo *Repository, err error) { if err != nil { return nil, err } + repo.config = cfg algo, err := detectObjectAlgorithm(cfg) if err != nil { return nil, err } + repo.algo = algo objects, objectsLooseForWritingOnly, err := openObjectStore(root, algo) if err != nil { return nil, err } + repo.objects = objects repo.objectsLooseForWritingOnly = objectsLooseForWritingOnly @@ -66,6 +70,7 @@ func Open(root *os.Root) (repo *Repository, err error) { if err != nil { return nil, err } + repo.refs = refs return repo, nil @@ -100,17 +105,22 @@ func (repo *Repository) Close() error { var errs []error if repo.refs != nil { - if err := repo.refs.Close(); err != nil { + err := repo.refs.Close() + if err != nil { errs = append(errs, err) } } + if repo.objects != nil { - if err := repo.objects.Close(); err != nil { + err := repo.objects.Close() + if err != nil { errs = append(errs, err) } } + if repo.objectsLooseForWritingOnly != nil { - if err := repo.objectsLooseForWritingOnly.Close(); err != nil { + err := repo.objectsLooseForWritingOnly.Close() + if err != nil { errs = append(errs, err) } } @@ -123,12 +133,14 @@ func parseRepositoryConfig(root *os.Root) (*config.Config, error) { if err != nil { return nil, fmt.Errorf("repository: open config: %w", err) } + defer func() { _ = configFile.Close() }() cfg, err := config.ParseConfig(configFile) if err != nil { return nil, fmt.Errorf("repository: parse config: %w", err) } + return cfg, nil } @@ -137,10 +149,12 @@ func detectObjectAlgorithm(cfg *config.Config) (objectid.Algorithm, error) { if algoName == "" { algoName = objectid.AlgorithmSHA1.String() } + algo, ok := objectid.ParseAlgorithm(algoName) if !ok { return objectid.AlgorithmUnknown, fmt.Errorf("repository: unsupported object format %q", algoName) } + return algo, nil } @@ -154,19 +168,24 @@ func openObjectStore(root *os.Root, algo objectid.Algorithm) (objectstore.Store, if err != nil { return nil, nil, err } + backends := []objectstore.Store{looseStore} packRoot, err := objectsRoot.OpenRoot("pack") if err == nil { var packedStore *objectpacked.Store + packedStore, err = objectpacked.New(packRoot, algo) if err != nil { _ = looseStore.Close() + return nil, nil, err } + backends = append(backends, packedStore) } else if !errors.Is(err, os.ErrNotExist) { _ = looseStore.Close() + return nil, nil, fmt.Errorf("repository: open objects/pack: %w", err) } @@ -175,12 +194,15 @@ func openObjectStore(root *os.Root, algo objectid.Algorithm) (objectstore.Store, objectsRootForWriting, err := root.OpenRoot("objects") if err != nil { _ = objectsChain.Close() + return nil, nil, fmt.Errorf("repository: open objects for loose writing: %w", err) } + objectsLooseForWritingOnly, err := objectloose.New(objectsRootForWriting, algo) if err != nil { _ = objectsRootForWriting.Close() _ = objectsChain.Close() + return nil, nil, err } @@ -192,16 +214,20 @@ func openRefStore(root *os.Root, algo objectid.Algorithm) (out refstore.Store, e if err != nil { return nil, err } + if hasReftable { reftableRoot, err := root.OpenRoot("reftable") if err != nil { return nil, fmt.Errorf("repository: open reftable: %w", err) } + reftableStore, err := reftable.New(reftableRoot, algo) if err != nil { _ = reftableRoot.Close() + return nil, err } + return reftableStore, nil } @@ -209,22 +235,29 @@ func openRefStore(root *os.Root, algo objectid.Algorithm) (out refstore.Store, e if err != nil { return nil, fmt.Errorf("repository: open root for loose refs: %w", err) } + looseStore, err := refloose.New(looseRoot, algo) if err != nil { _ = looseRoot.Close() + return nil, err } + backends := []refstore.Store{looseStore} - if _, err := root.Stat("packed-refs"); err == nil { + _, err = root.Stat("packed-refs") + if err == nil { packedStore, packedErr := refpacked.New(root, algo) if packedErr != nil { _ = looseStore.Close() + return nil, packedErr } + backends = append(backends, packedStore) } else if !errors.Is(err, os.ErrNotExist) { _ = looseStore.Close() + return nil, fmt.Errorf("repository: stat packed-refs: %w", err) } @@ -236,8 +269,10 @@ func hasReftableStack(root *os.Root) (bool, error) { if err == nil { return true, nil } + if errors.Is(err, os.ErrNotExist) { return false, nil } + return false, fmt.Errorf("repository: stat reftable/tables.list: %w", err) } diff --git a/repository/repository_test.go b/repository/repository_test.go index f8b33c8a..22ae5a1a 100644 --- a/repository/repository_test.go +++ b/repository/repository_test.go @@ -29,12 +29,14 @@ func TestOpenFilesRefFormat(t *testing.T) { if err != nil { t.Fatalf("os.OpenRoot: %v", err) } + defer func() { _ = root.Close() }() repo, err := repository.Open(root) if err != nil { t.Fatalf("repository.Open: %v", err) } + defer func() { _ = repo.Close() }() if repo.Algorithm() != algo { @@ -45,9 +47,11 @@ func TestOpenFilesRefFormat(t *testing.T) { if err != nil { t.Fatalf("ReadHeader(commit): %v", err) } + if headerType != objecttype.TypeCommit { t.Fatalf("ReadHeader(commit) type = %v, want %v", headerType, objecttype.TypeCommit) } + if headerSize <= 0 { t.Fatalf("ReadHeader(commit) size = %d, want > 0", headerSize) } @@ -56,10 +60,12 @@ func TestOpenFilesRefFormat(t *testing.T) { if err != nil { t.Fatalf("Resolve(refs/heads/main): %v", err) } + detached, ok := resolved.(ref.Detached) if !ok { t.Fatalf("Resolve(refs/heads/main) type = %T, want ref.Detached", resolved) } + if detached.ID != commitID { t.Fatalf("Resolve(refs/heads/main) id = %s, want %s", detached.ID, commitID) } @@ -68,6 +74,7 @@ func TestOpenFilesRefFormat(t *testing.T) { if err != nil { t.Fatalf("ResolveFully(HEAD): %v", err) } + if head.ID != commitID { t.Fatalf("ResolveFully(HEAD) id = %s, want %s", head.ID, commitID) } @@ -97,6 +104,7 @@ func TestOpenReftableRefFormat(t *testing.T) { func newRepoForRefs(t *testing.T, algo objectid.Algorithm, refFormat string) *testgit.TestRepo { t.Helper() + return testgit.NewRepo(t, testgit.RepoOptions{ ObjectFormat: algo, Bare: true, @@ -109,6 +117,7 @@ func writeMainAndHead(t *testing.T, repoHarness *testgit.TestRepo) objectid.Obje _, _, commitID := repoHarness.MakeCommit(t, "refs") repoHarness.UpdateRef(t, "refs/heads/main", commitID) repoHarness.SymbolicRef(t, "HEAD", "refs/heads/main") + return commitID } @@ -119,18 +128,21 @@ func assertResolveFully(t *testing.T, repoHarness *testgit.TestRepo, name string if err != nil { t.Fatalf("os.OpenRoot: %v", err) } + defer func() { _ = root.Close() }() repo, err := repository.Open(root) if err != nil { t.Fatalf("repository.Open: %v", err) } + defer func() { _ = repo.Close() }() resolved, err := repo.Refs().ResolveFully(name) if err != nil { t.Fatalf("ResolveFully(%s): %v", name, err) } + if resolved.ID != want { t.Fatalf("ResolveFully(%s) id = %s, want %s", name, resolved.ID, want) } diff --git a/repository/stored_test.go b/repository/stored_test.go index b53fcde6..6ebd4259 100644 --- a/repository/stored_test.go +++ b/repository/stored_test.go @@ -28,21 +28,25 @@ func TestReadStoredTyped(t *testing.T) { if err != nil { t.Fatalf("os.OpenRoot: %v", err) } + defer func() { _ = root.Close() }() repo, err := repository.Open(root) if err != nil { t.Fatalf("repository.Open: %v", err) } + defer func() { _ = repo.Close() }() blob, err := repo.ReadStoredBlob(blobID) if err != nil { t.Fatalf("ReadStoredBlob: %v", err) } + if blob.ID() != blobID { t.Fatalf("blob ID = %s, want %s", blob.ID(), blobID) } + if string(blob.Blob().Data) != "commit-body\n" { t.Fatalf("blob body = %q, want %q", blob.Blob().Data, "commit-body\n") } @@ -51,9 +55,11 @@ func TestReadStoredTyped(t *testing.T) { if err != nil { t.Fatalf("ReadStoredTree: %v", err) } + if tree.ID() != treeID { t.Fatalf("tree ID = %s, want %s", tree.ID(), treeID) } + if len(tree.Tree().Entries) != 1 { t.Fatalf("tree entries = %d, want 1", len(tree.Tree().Entries)) } @@ -62,9 +68,11 @@ func TestReadStoredTyped(t *testing.T) { if err != nil { t.Fatalf("ReadStoredCommit: %v", err) } + if commit.ID() != commitID { t.Fatalf("commit ID = %s, want %s", commit.ID(), commitID) } + if commit.Commit().Tree != treeID { t.Fatalf("commit tree = %s, want %s", commit.Commit().Tree, treeID) } @@ -89,12 +97,14 @@ func TestResolveTreeEntry(t *testing.T) { if err != nil { t.Fatalf("os.OpenRoot: %v", err) } + defer func() { _ = root.Close() }() repo, err := repository.Open(root) if err != nil { t.Fatalf("repository.Open: %v", err) } + defer func() { _ = repo.Close() }() rootTree, err := repo.ReadStoredTree(rootTreeID) @@ -106,9 +116,11 @@ func TestResolveTreeEntry(t *testing.T) { if err != nil { t.Fatalf("ResolveTreeEntry: %v", err) } + if entry.Mode != object.FileModeRegular { t.Fatalf("ResolveTreeEntry mode = %o, want %o", entry.Mode, object.FileModeRegular) } + if entry.ID != blobID { t.Fatalf("ResolveTreeEntry id = %s, want %s", entry.ID, blobID) } @@ -133,12 +145,14 @@ func TestResolveTreeEntryErrors(t *testing.T) { if err != nil { t.Fatalf("os.OpenRoot: %v", err) } + defer func() { _ = root.Close() }() repo, err := repository.Open(root) if err != nil { t.Fatalf("repository.Open: %v", err) } + defer func() { _ = repo.Close() }() rootTree, err := repo.ReadStoredTree(rootTreeID) @@ -166,12 +180,14 @@ func TestResolveTreeEntryErrors(t *testing.T) { if err != nil { t.Fatalf("os.OpenRoot: %v", err) } + defer func() { _ = root.Close() }() repo, err := repository.Open(root) if err != nil { t.Fatalf("repository.Open: %v", err) } + defer func() { _ = repo.Close() }() rootTree, err := repo.ReadStoredTree(rootTreeID) @@ -208,18 +224,21 @@ func TestResolveTreeEntryDeepPath(t *testing.T) { 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")) root, err := os.OpenRoot(repoHarness.Dir()) if err != nil { t.Fatalf("os.OpenRoot: %v", err) } + defer func() { _ = root.Close() }() repo, err := repository.Open(root) if err != nil { t.Fatalf("repository.Open: %v", err) } + defer func() { _ = repo.Close() }() rootTree, err := repo.ReadStoredTree(currentTree) @@ -231,9 +250,11 @@ func TestResolveTreeEntryDeepPath(t *testing.T) { 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) } @@ -270,12 +291,14 @@ func TestReadStoredTreeMixedModes(t *testing.T) { if err != nil { t.Fatalf("os.OpenRoot: %v", err) } + defer func() { _ = root.Close() }() repo, err := repository.Open(root) if err != nil { t.Fatalf("repository.Open: %v", err) } + defer func() { _ = repo.Close() }() rootTree, err := repo.ReadStoredTree(rootTreeID) diff --git a/repository/traversal_bench_test.go b/repository/traversal_bench_test.go index 63e131de..3480964e 100644 --- a/repository/traversal_bench_test.go +++ b/repository/traversal_bench_test.go @@ -25,6 +25,7 @@ func BenchmarkTraverseHeadTree(b *testing.B) { if err != nil { b.Fatalf("os.OpenRoot(%q): %v", repoPath, err) } + b.Cleanup(func() { _ = root.Close() }) @@ -33,6 +34,7 @@ func BenchmarkTraverseHeadTree(b *testing.B) { if err != nil { b.Fatalf("repository.Open(root for %q): %v", repoPath, err) } + b.Cleanup(func() { _ = repo.Close() }) @@ -41,10 +43,12 @@ func BenchmarkTraverseHeadTree(b *testing.B) { if err != nil { b.Fatalf("ResolveRefFully(HEAD): %v", err) } + stored, err := repo.ReadStored(head.ID) if err != nil { b.Fatalf("ReadStored(%s): %v", head.ID, err) } + commit, ok := stored.Object().(*object.Commit) if !ok { b.Fatalf("HEAD object type %T, want *object.Commit", stored.Object()) @@ -62,6 +66,7 @@ func BenchmarkTraverseHeadTree(b *testing.B) { } b.StopTimer() + if lastCount <= 0 { b.Fatalf("traverseTreeIter count = %d, want > 0", lastCount) } diff --git a/repository/traversal_helpers_test.go b/repository/traversal_helpers_test.go index b23a81c8..e2d662e6 100644 --- a/repository/traversal_helpers_test.go +++ b/repository/traversal_helpers_test.go @@ -21,10 +21,13 @@ func traverseTreeIter(repo *repository.Repository, root objectid.ObjectID) (int, id := frame.id if !frame.isTree { - if _, err := repo.ReadStoredSize(id); err != nil { + _, err := repo.ReadStoredSize(id) + if err != nil { return 0, err } + total++ + continue } @@ -32,12 +35,15 @@ func traverseTreeIter(repo *repository.Repository, root objectid.ObjectID) (int, if err != nil { return 0, err } + total++ + for i := len(tree.Tree().Entries) - 1; i >= 0; i-- { entry := tree.Tree().Entries[i] if entry.Mode == object.FileModeGitlink { continue } + stack = append(stack, treeWalkFrame{ id: entry.ID, isTree: entry.Mode == object.FileModeDir, @@ -56,15 +62,19 @@ func traverseReachableIter(repo *repository.Repository, root objectid.ObjectID) for len(stack) > 0 { id := stack[len(stack)-1] stack = stack[:len(stack)-1] - if _, ok := visited[id]; ok { + + _, ok := visited[id] + if ok { continue } + visited[id] = struct{}{} stored, err := repo.ReadStored(id) if err != nil { return 0, err } + total++ switch obj := stored.Object().(type) { @@ -77,6 +87,7 @@ func traverseReachableIter(repo *repository.Repository, root objectid.ObjectID) if entry.Mode == object.FileModeGitlink { continue } + stack = append(stack, entry.ID) } case *object.Tag: diff --git a/repository/traversal_test.go b/repository/traversal_test.go index 28c03a2c..34c3a75b 100644 --- a/repository/traversal_test.go +++ b/repository/traversal_test.go @@ -26,6 +26,7 @@ func TestRepositoryDepthFirstEnumerationFromHEAD(t *testing.T) { 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") @@ -46,6 +47,7 @@ func TestRepositoryDepthFirstEnumerationCurrentWorktree(t *testing.T) { if info.IsDir() { walkRepositoryFromHead(t, gitPath) + return } @@ -57,7 +59,9 @@ func TestRepositoryDepthFirstEnumerationCurrentWorktree(t *testing.T) { if err != nil { t.Fatalf("read %q: %v", gitPath, err) } + line := strings.TrimSpace(string(content)) + prefix := "gitdir: " if !strings.HasPrefix(line, prefix) { t.Fatalf("%q file does not begin with %q", gitPath, prefix) @@ -67,23 +71,30 @@ func TestRepositoryDepthFirstEnumerationCurrentWorktree(t *testing.T) { if gitdirRel == "" { t.Fatalf("%q contains empty gitdir path", gitPath) } + gitdirPath := gitdirRel if !filepath.IsAbs(gitdirPath) { gitdirPath = filepath.Join(worktreeRoot, gitdirPath) } + commondirPath := filepath.Join(gitdirPath, "commondir") + commondirContent, err := os.ReadFile(commondirPath) //#nosec G304 if err != nil { t.Fatalf("read %q: %v", commondirPath, err) } + repoPath := strings.TrimSpace(string(commondirContent)) if repoPath == "" { t.Fatalf("%q contains empty repo path", commondirPath) } + if filepath.IsAbs(repoPath) { walkRepositoryFromHead(t, repoPath) + return } + repoPath = filepath.Join(gitdirPath, repoPath) walkRepositoryFromHead(t, repoPath) @@ -96,22 +107,26 @@ func walkRepositoryFromHead(t *testing.T, repoPath string) { if err != nil { t.Fatalf("os.OpenRoot(%q): %v", repoPath, err) } + defer func() { _ = root.Close() }() repo, err := repository.Open(root) if err != nil { t.Fatalf("repository.Open(root for %q): %v", repoPath, err) } + defer func() { _ = repo.Close() }() head, err := repo.ResolveRefFully("HEAD") if err != nil { t.Fatalf("ResolveRefFully(HEAD): %v", err) } + objectsRead, err := traverseReachableIter(repo, head.ID) if err != nil { t.Fatalf("traverseReachableIter(%s): %v", head.ID, err) } + if objectsRead <= 0 { t.Fatalf("no objects were enumerated from HEAD (%s)", fmt.Sprintf("%q", repoPath)) } diff --git a/repository/tree_resolve.go b/repository/tree_resolve.go index 6b7023ba..d4ef529e 100644 --- a/repository/tree_resolve.go +++ b/repository/tree_resolve.go @@ -16,11 +16,13 @@ func (repo *Repository) ResolveTreeEntry(tree *objectstored.StoredTree, parts [] if tree == nil { return object.TreeEntry{}, errors.New("repository: nil root tree") } + if len(parts) == 0 { return object.TreeEntry{}, errors.New("repository: empty tree path") } current := tree + for i, part := range parts { if len(part) == 0 { return object.TreeEntry{}, errors.New("repository: empty tree path segment") @@ -30,9 +32,11 @@ func (repo *Repository) ResolveTreeEntry(tree *objectstored.StoredTree, parts [] if entry == nil { return object.TreeEntry{}, fmt.Errorf("repository: tree entry %q not found", part) } + if i == len(parts)-1 { return *entry, nil } + if entry.Mode != object.FileModeDir { return object.TreeEntry{}, fmt.Errorf("repository: path segment %q is not a tree", part) } @@ -41,6 +45,7 @@ func (repo *Repository) ResolveTreeEntry(tree *objectstored.StoredTree, parts [] if err != nil { return object.TreeEntry{}, err } + current = next } diff --git a/repository/write_loose.go b/repository/write_loose.go index adebb2f6..9784ef25 100644 --- a/repository/write_loose.go +++ b/repository/write_loose.go @@ -15,6 +15,7 @@ func (repo *Repository) WriteLooseBytesFull(raw []byte) (objectid.ObjectID, erro if err != nil { return objectid.ObjectID{}, fmt.Errorf("repository: write loose full bytes: %w", err) } + return id, nil } @@ -24,6 +25,7 @@ func (repo *Repository) WriteLooseBytesContent(ty objecttype.Type, content []byt if err != nil { return objectid.ObjectID{}, fmt.Errorf("repository: write loose content bytes: %w", err) } + return id, nil } @@ -34,6 +36,7 @@ func (repo *Repository) WriteLooseReaderFull(src io.Reader) (objectid.ObjectID, if err != nil { return objectid.ObjectID{}, fmt.Errorf("repository: write loose full reader: %w", err) } + return id, nil } @@ -44,5 +47,6 @@ func (repo *Repository) WriteLooseReaderContent(ty objecttype.Type, size int64, if err != nil { return objectid.ObjectID{}, fmt.Errorf("repository: write loose content reader: %w", err) } + return id, nil } diff --git a/repository/write_loose_test.go b/repository/write_loose_test.go index 603b3a88..ab732df4 100644 --- a/repository/write_loose_test.go +++ b/repository/write_loose_test.go @@ -25,15 +25,18 @@ func TestWriteLooseBytesContent(t *testing.T) { if err != nil { t.Fatalf("os.OpenRoot: %v", err) } + defer func() { _ = root.Close() }() repo, err := repository.Open(root) if err != nil { t.Fatalf("repository.Open: %v", err) } + defer func() { _ = repo.Close() }() content := []byte("write-loose-bytes-content\n") + gotID, err := repo.WriteLooseBytesContent(objecttype.TypeBlob, content) if err != nil { t.Fatalf("WriteLooseBytesContent: %v", err) @@ -48,9 +51,11 @@ func TestWriteLooseBytesContent(t *testing.T) { if err != nil { t.Fatalf("ReadStoredBytesContent: %v", err) } + if ty != objecttype.TypeBlob { t.Fatalf("ReadStoredBytesContent type = %v, want %v", ty, objecttype.TypeBlob) } + if !bytes.Equal(gotContent, content) { t.Fatalf("ReadStoredBytesContent content mismatch") } @@ -71,15 +76,18 @@ func TestWriteLooseReaderContent(t *testing.T) { if err != nil { t.Fatalf("os.OpenRoot: %v", err) } + defer func() { _ = root.Close() }() repo, err := repository.Open(root) if err != nil { t.Fatalf("repository.Open: %v", err) } + defer func() { _ = repo.Close() }() content := []byte("write-loose-reader-content\n") + gotID, err := repo.WriteLooseReaderContent(objecttype.TypeBlob, int64(len(content)), bytes.NewReader(content)) if err != nil { t.Fatalf("WriteLooseReaderContent: %v", err) @@ -107,12 +115,14 @@ func TestWriteLooseFull(t *testing.T) { if err != nil { t.Fatalf("os.OpenRoot: %v", err) } + defer func() { _ = root.Close() }() repo, err := repository.Open(root) if err != nil { t.Fatalf("repository.Open: %v", err) } + defer func() { _ = repo.Close() }() raw, err := repo.ReadStoredBytesFull(commitID) @@ -124,6 +134,7 @@ func TestWriteLooseFull(t *testing.T) { if err != nil { t.Fatalf("WriteLooseBytesFull: %v", err) } + if idFromBytes != commitID { t.Fatalf("WriteLooseBytesFull id = %s, want %s", idFromBytes, commitID) } @@ -132,6 +143,7 @@ func TestWriteLooseFull(t *testing.T) { if err != nil { t.Fatalf("WriteLooseReaderFull: %v", err) } + if idFromReader != commitID { t.Fatalf("WriteLooseReaderFull id = %s, want %s", idFromReader, commitID) } |
