aboutsummaryrefslogtreecommitdiff
path: root/repository
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-03-04 08:26:56 +0800
committerGravatar Runxi Yu2026-03-04 08:59:53 +0800
commitab7501be34032fb9e5c48726a68ae90a917af9eb (patch)
tree20d005647569befea8133e953c3270e8fd2a2a5b /repository
parent*: gofumpt (diff)
signatureNo signature
*: Lint
Diffstat (limited to 'repository')
-rw-r--r--repository/read_stored.go12
-rw-r--r--repository/read_stored_passthrough_test.go17
-rw-r--r--repository/refs_test.go19
-rw-r--r--repository/repository.go43
-rw-r--r--repository/repository_test.go12
-rw-r--r--repository/stored_test.go23
-rw-r--r--repository/traversal_bench_test.go5
-rw-r--r--repository/traversal_helpers_test.go15
-rw-r--r--repository/traversal_test.go15
-rw-r--r--repository/tree_resolve.go5
-rw-r--r--repository/write_loose.go4
-rw-r--r--repository/write_loose_test.go12
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)
}