aboutsummaryrefslogtreecommitdiff
path: root/object
diff options
context:
space:
mode:
Diffstat (limited to 'object')
-rw-r--r--object/blob/blob.go (renamed from object/blob.go)12
-rw-r--r--object/blob/parse.go6
-rw-r--r--object/blob/parse_test.go (renamed from object/blob_parse_test.go)8
-rw-r--r--object/blob/serialize.go (renamed from object/blob_serialize.go)2
-rw-r--r--object/blob/serialize_test.go (renamed from object/blob_serialize_test.go)8
-rw-r--r--object/blob/test.go10
-rw-r--r--object/blob_parse.go6
-rw-r--r--object/commit/commit.go (renamed from object/commit.go)16
-rw-r--r--object/commit/extraheader.go (renamed from object/extraheader.go)2
-rw-r--r--object/commit/parse.go (renamed from object/commit_parse.go)11
-rw-r--r--object/commit/parse_test.go (renamed from object/commit_parse_test.go)48
-rw-r--r--object/commit/serialize.go (renamed from object/commit_serialize.go)2
-rw-r--r--object/commit/serialize_test.go (renamed from object/commit_serialize_test.go)8
-rw-r--r--object/commit/type.go10
-rw-r--r--object/object.go5
-rw-r--r--object/parse.go12
-rw-r--r--object/resolve/exact_blob.go6
-rw-r--r--object/resolve/exact_commit.go6
-rw-r--r--object/resolve/exact_tag.go6
-rw-r--r--object/resolve/exact_tree.go6
-rw-r--r--object/resolve/path.go20
-rw-r--r--object/resolve/peel_to_blob.go9
-rw-r--r--object/resolve/peel_to_commit.go9
-rw-r--r--object/resolve/peel_to_tag.go4
-rw-r--r--object/resolve/peel_to_tree.go12
-rw-r--r--object/resolve/resolver.go2
-rw-r--r--object/resolve/treefs.go4
-rw-r--r--object/resolve/treefs_entry.go12
-rw-r--r--object/resolve/treefs_info.go16
-rw-r--r--object/resolve/treefs_open.go4
-rw-r--r--object/resolve/treefs_readfile.go4
-rw-r--r--object/resolve/treefs_stat.go2
-rw-r--r--object/resolve/treefs_test.go10
-rw-r--r--object/signature/parse.go (renamed from object/ident.go)49
-rw-r--r--object/signature/serialize.go33
-rw-r--r--object/signature/signature.go10
-rw-r--r--object/signature/when.go10
-rw-r--r--object/stored/stored.go2
-rw-r--r--object/storer/chain/bytes.go2
-rw-r--r--object/storer/chain/chain.go4
-rw-r--r--object/storer/chain/header.go2
-rw-r--r--object/storer/chain/new.go2
-rw-r--r--object/storer/chain/reader.go2
-rw-r--r--object/storer/chain/size.go2
-rw-r--r--object/storer/loose/paths.go2
-rw-r--r--object/storer/loose/read_test.go2
-rw-r--r--object/storer/memory/read_bytes.go2
-rw-r--r--object/storer/memory/read_header.go2
-rw-r--r--object/storer/mix/bytes.go2
-rw-r--r--object/storer/mix/header.go2
-rw-r--r--object/storer/mix/mix.go2
-rw-r--r--object/storer/mix/mru.go2
-rw-r--r--object/storer/mix/new.go2
-rw-r--r--object/storer/mix/reader.go2
-rw-r--r--object/storer/mix/refresh.go2
-rw-r--r--object/storer/mix/size.go2
-rw-r--r--object/storer/packed/read_test.go2
-rw-r--r--object/storer/packed/store.go2
-rw-r--r--object/storer/packed/store_lookup.go2
-rw-r--r--object/tag/parse.go (renamed from object/tag_parse.go)9
-rw-r--r--object/tag/parse_test.go (renamed from object/tag_parse_test.go)24
-rw-r--r--object/tag/serialize.go (renamed from object/tag_serialize.go)2
-rw-r--r--object/tag/serialize_test.go (renamed from object/tag_serialize_test.go)8
-rw-r--r--object/tag/tag.go (renamed from object/tag.go)13
-rw-r--r--object/tag/type.go10
-rw-r--r--object/tree.go163
-rw-r--r--object/tree/entry.go39
-rw-r--r--object/tree/helpers_test.go114
-rw-r--r--object/tree/insert.go23
-rw-r--r--object/tree/lookup.go14
-rw-r--r--object/tree/mode.go12
-rw-r--r--object/tree/name.go51
-rw-r--r--object/tree/parse.go (renamed from object/tree_parse.go)6
-rw-r--r--object/tree/parse_test.go (renamed from object/tree_parse_test.go)26
-rw-r--r--object/tree/remove.go24
-rw-r--r--object/tree/serialize.go (renamed from object/tree_serialize.go)2
-rw-r--r--object/tree/serialize_test.go (renamed from object/tree_serialize_test.go)32
-rw-r--r--object/tree/tree.go7
-rw-r--r--object/tree/type.go10
-rw-r--r--object/tree_helpers_test.go114
80 files changed, 597 insertions, 552 deletions
diff --git a/object/blob.go b/object/blob/blob.go
index 1c827190..977121fb 100644
--- a/object/blob.go
+++ b/object/blob/blob.go
@@ -1,6 +1,5 @@
-package object
-
-import objecttype "codeberg.org/lindenii/furgit/object/type"
+// Package blob provides representations, parsers, and serializers for blob objects.
+package blob
// Blob represents a Git blob object.
//
@@ -10,10 +9,3 @@ import objecttype "codeberg.org/lindenii/furgit/object/type"
type Blob struct {
Data []byte
}
-
-// ObjectType returns TypeBlob.
-func (blob *Blob) ObjectType() objecttype.Type {
- _ = blob
-
- return objecttype.TypeBlob
-}
diff --git a/object/blob/parse.go b/object/blob/parse.go
new file mode 100644
index 00000000..faee9e46
--- /dev/null
+++ b/object/blob/parse.go
@@ -0,0 +1,6 @@
+package blob
+
+// Parse decodes a blob object body.
+func Parse(body []byte) (*Blob, error) {
+ return &Blob{Data: append([]byte(nil), body...)}, nil
+}
diff --git a/object/blob_parse_test.go b/object/blob/parse_test.go
index eb8f2f56..09d5d5d0 100644
--- a/object/blob_parse_test.go
+++ b/object/blob/parse_test.go
@@ -1,11 +1,11 @@
-package object_test
+package blob_test
import (
"bytes"
"testing"
"codeberg.org/lindenii/furgit/internal/testgit"
- "codeberg.org/lindenii/furgit/object"
+ "codeberg.org/lindenii/furgit/object/blob"
objectid "codeberg.org/lindenii/furgit/object/id"
)
@@ -18,12 +18,12 @@ func TestBlobParseFromGit(t *testing.T) {
rawBody := testRepo.CatFile(t, "blob", blobID)
- blob, err := object.ParseBlob(rawBody)
+ parsed, err := blob.Parse(rawBody)
if err != nil {
t.Fatalf("ParseBlob: %v", err)
}
- if !bytes.Equal(blob.Data, body) {
+ if !bytes.Equal(parsed.Data, body) {
t.Fatalf("blob body mismatch")
}
})
diff --git a/object/blob_serialize.go b/object/blob/serialize.go
index 2acc4c11..80cce8dc 100644
--- a/object/blob_serialize.go
+++ b/object/blob/serialize.go
@@ -1,4 +1,4 @@
-package object
+package blob
import (
"errors"
diff --git a/object/blob_serialize_test.go b/object/blob/serialize_test.go
index 704811ca..4292abad 100644
--- a/object/blob_serialize_test.go
+++ b/object/blob/serialize_test.go
@@ -1,10 +1,10 @@
-package object_test
+package blob_test
import (
"testing"
"codeberg.org/lindenii/furgit/internal/testgit"
- "codeberg.org/lindenii/furgit/object"
+ "codeberg.org/lindenii/furgit/object/blob"
objectid "codeberg.org/lindenii/furgit/object/id"
)
@@ -15,9 +15,9 @@ func TestBlobSerialize(t *testing.T) {
body := []byte("hello\nblob\n")
wantID := testRepo.HashObject(t, "blob", body)
- blob := &object.Blob{Data: body}
+ obj := &blob.Blob{Data: body}
- rawObj, err := blob.SerializeWithHeader()
+ rawObj, err := obj.SerializeWithHeader()
if err != nil {
t.Fatalf("SerializeWithHeader: %v", err)
}
diff --git a/object/blob/test.go b/object/blob/test.go
new file mode 100644
index 00000000..9e538219
--- /dev/null
+++ b/object/blob/test.go
@@ -0,0 +1,10 @@
+package blob
+
+import objecttype "codeberg.org/lindenii/furgit/object/type"
+
+// ObjectType returns TypeBlob.
+func (blob *Blob) ObjectType() objecttype.Type {
+ _ = blob
+
+ return objecttype.TypeBlob
+}
diff --git a/object/blob_parse.go b/object/blob_parse.go
deleted file mode 100644
index 61aacfac..00000000
--- a/object/blob_parse.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package object
-
-// ParseBlob decodes a blob object body.
-func ParseBlob(body []byte) (*Blob, error) {
- return &Blob{Data: append([]byte(nil), body...)}, nil
-}
diff --git a/object/commit.go b/object/commit/commit.go
index f7b0d676..e2e087f5 100644
--- a/object/commit.go
+++ b/object/commit/commit.go
@@ -1,24 +1,18 @@
-package object
+// Package commit provides representations, parsers, and serializers for commit objects.
+package commit
import (
objectid "codeberg.org/lindenii/furgit/object/id"
- objecttype "codeberg.org/lindenii/furgit/object/type"
+ objectsignature "codeberg.org/lindenii/furgit/object/signature"
)
// Commit represents a Git commit object.
type Commit struct {
Tree objectid.ObjectID
Parents []objectid.ObjectID
- Author Signature
- Committer Signature
+ Author objectsignature.Signature
+ Committer objectsignature.Signature
Message []byte
ChangeID string
ExtraHeaders []ExtraHeader
}
-
-// ObjectType returns TypeCommit.
-func (commit *Commit) ObjectType() objecttype.Type {
- _ = commit
-
- return objecttype.TypeCommit
-}
diff --git a/object/extraheader.go b/object/commit/extraheader.go
index 4ad1ec09..79d4f9cc 100644
--- a/object/extraheader.go
+++ b/object/commit/extraheader.go
@@ -1,4 +1,4 @@
-package object
+package commit
// ExtraHeader represents an extra header in a Git object.
type ExtraHeader struct {
diff --git a/object/commit_parse.go b/object/commit/parse.go
index 6578d523..9dcc930d 100644
--- a/object/commit_parse.go
+++ b/object/commit/parse.go
@@ -1,4 +1,4 @@
-package object
+package commit
import (
"bytes"
@@ -6,10 +6,11 @@ import (
"fmt"
objectid "codeberg.org/lindenii/furgit/object/id"
+ objectsignature "codeberg.org/lindenii/furgit/object/signature"
)
-// ParseCommit decodes a commit object body.
-func ParseCommit(body []byte, algo objectid.Algorithm) (*Commit, error) {
+// Parse decodes a commit object body.
+func Parse(body []byte, algo objectid.Algorithm) (*Commit, error) {
c := new(Commit)
i := 0
@@ -47,14 +48,14 @@ func ParseCommit(body []byte, algo objectid.Algorithm) (*Commit, error) {
c.Parents = append(c.Parents, id)
case "author":
- idt, err := ParseSignature(value)
+ idt, err := objectsignature.Parse(value)
if err != nil {
return nil, fmt.Errorf("object: commit: author: %w", err)
}
c.Author = *idt
case "committer":
- idt, err := ParseSignature(value)
+ idt, err := objectsignature.Parse(value)
if err != nil {
return nil, fmt.Errorf("object: commit: committer: %w", err)
}
diff --git a/object/commit_parse_test.go b/object/commit/parse_test.go
index fae2b4c1..ad2c7aed 100644
--- a/object/commit_parse_test.go
+++ b/object/commit/parse_test.go
@@ -1,4 +1,4 @@
-package object_test
+package commit_test
import (
"bytes"
@@ -6,7 +6,7 @@ import (
"testing"
"codeberg.org/lindenii/furgit/internal/testgit"
- "codeberg.org/lindenii/furgit/object"
+ "codeberg.org/lindenii/furgit/object/commit"
objectid "codeberg.org/lindenii/furgit/object/id"
)
@@ -18,29 +18,29 @@ func TestCommitParseFromGit(t *testing.T) {
rawBody := testRepo.CatFile(t, "commit", commitID)
- commit, err := object.ParseCommit(rawBody, algo)
+ parsed, err := commit.Parse(rawBody, algo)
if err != nil {
t.Fatalf("ParseCommit: %v", err)
}
- if commit.Tree != treeID {
- t.Fatalf("tree id mismatch: got %s want %s", commit.Tree, treeID)
+ if parsed.Tree != treeID {
+ t.Fatalf("tree id mismatch: got %s want %s", parsed.Tree, treeID)
}
- if len(commit.Parents) != 0 {
- t.Fatalf("parent count = %d, want 0", len(commit.Parents))
+ if len(parsed.Parents) != 0 {
+ t.Fatalf("parent count = %d, want 0", len(parsed.Parents))
}
- if !bytes.Equal(commit.Author.Name, []byte("Test Author")) {
- t.Fatalf("author name = %q, want %q", commit.Author.Name, "Test Author")
+ if !bytes.Equal(parsed.Author.Name, []byte("Test Author")) {
+ t.Fatalf("author name = %q, want %q", parsed.Author.Name, "Test Author")
}
- if !bytes.Equal(commit.Committer.Name, []byte("Test Committer")) {
- t.Fatalf("committer name = %q, want %q", commit.Committer.Name, "Test Committer")
+ if !bytes.Equal(parsed.Committer.Name, []byte("Test Committer")) {
+ t.Fatalf("committer name = %q, want %q", parsed.Committer.Name, "Test Committer")
}
- if !bytes.Contains(commit.Message, []byte("subject")) {
- t.Fatalf("commit message missing subject: %q", commit.Message)
+ if !bytes.Contains(parsed.Message, []byte("subject")) {
+ t.Fatalf("commit message missing subject: %q", parsed.Message)
}
})
}
@@ -63,29 +63,29 @@ func TestCommitParseMultipleParents(t *testing.T) {
mergeID := testRepo.HashObject(t, "commit", []byte(rawCommit))
rawBody := testRepo.CatFile(t, "commit", mergeID)
- commit, err := object.ParseCommit(rawBody, algo)
+ parsed, err := commit.Parse(rawBody, algo)
if err != nil {
t.Fatalf("ParseCommit(merge): %v", err)
}
- if commit.Tree != treeID {
- t.Fatalf("merge tree = %s, want %s", commit.Tree, treeID)
+ if parsed.Tree != treeID {
+ t.Fatalf("merge tree = %s, want %s", parsed.Tree, treeID)
}
- if len(commit.Parents) != 2 {
- t.Fatalf("merge parent count = %d, want 2", len(commit.Parents))
+ if len(parsed.Parents) != 2 {
+ t.Fatalf("merge parent count = %d, want 2", len(parsed.Parents))
}
- if commit.Parents[0] != parent1 {
- t.Fatalf("merge parent[0] = %s, want %s", commit.Parents[0], parent1)
+ if parsed.Parents[0] != parent1 {
+ t.Fatalf("merge parent[0] = %s, want %s", parsed.Parents[0], parent1)
}
- if commit.Parents[1] != parent2 {
- t.Fatalf("merge parent[1] = %s, want %s", commit.Parents[1], parent2)
+ if parsed.Parents[1] != parent2 {
+ t.Fatalf("merge parent[1] = %s, want %s", parsed.Parents[1], parent2)
}
- if !bytes.Equal(commit.Message, []byte("Merge commit\n")) {
- t.Fatalf("merge message = %q, want %q", commit.Message, "Merge commit\n")
+ if !bytes.Equal(parsed.Message, []byte("Merge commit\n")) {
+ t.Fatalf("merge message = %q, want %q", parsed.Message, "Merge commit\n")
}
})
}
diff --git a/object/commit_serialize.go b/object/commit/serialize.go
index ed81b1d6..721cacf6 100644
--- a/object/commit_serialize.go
+++ b/object/commit/serialize.go
@@ -1,4 +1,4 @@
-package object
+package commit
import (
"bytes"
diff --git a/object/commit_serialize_test.go b/object/commit/serialize_test.go
index cff47b40..e58a8078 100644
--- a/object/commit_serialize_test.go
+++ b/object/commit/serialize_test.go
@@ -1,10 +1,10 @@
-package object_test
+package commit_test
import (
"testing"
"codeberg.org/lindenii/furgit/internal/testgit"
- "codeberg.org/lindenii/furgit/object"
+ "codeberg.org/lindenii/furgit/object/commit"
objectid "codeberg.org/lindenii/furgit/object/id"
)
@@ -16,12 +16,12 @@ func TestCommitSerialize(t *testing.T) {
rawBody := testRepo.CatFile(t, "commit", commitID)
- commit, err := object.ParseCommit(rawBody, algo)
+ parsed, err := commit.Parse(rawBody, algo)
if err != nil {
t.Fatalf("ParseCommit: %v", err)
}
- rawObj, err := commit.SerializeWithHeader()
+ rawObj, err := parsed.SerializeWithHeader()
if err != nil {
t.Fatalf("SerializeWithHeader: %v", err)
}
diff --git a/object/commit/type.go b/object/commit/type.go
new file mode 100644
index 00000000..b8aa11e8
--- /dev/null
+++ b/object/commit/type.go
@@ -0,0 +1,10 @@
+package commit
+
+import objecttype "codeberg.org/lindenii/furgit/object/type"
+
+// ObjectType returns TypeCommit.
+func (commit *Commit) ObjectType() objecttype.Type {
+ _ = commit
+
+ return objecttype.TypeCommit
+}
diff --git a/object/object.go b/object/object.go
index 70d418df..f2325211 100644
--- a/object/object.go
+++ b/object/object.go
@@ -1,10 +1,9 @@
-// Package object parses and serializes objects such as blob, tree, commit, and
-// tag.
+// Package object provides shared object interfaces.
package object
import objecttype "codeberg.org/lindenii/furgit/object/type"
-// Object is a Git object that can serialize itself.
+// Object is a Git object.
type Object interface {
ObjectType() objecttype.Type
SerializeWithoutHeader() ([]byte, error)
diff --git a/object/parse.go b/object/parse.go
index cb75cb43..7cc01a7a 100644
--- a/object/parse.go
+++ b/object/parse.go
@@ -3,8 +3,12 @@ package object
import (
"fmt"
+ "codeberg.org/lindenii/furgit/object/blob"
+ "codeberg.org/lindenii/furgit/object/commit"
objectheader "codeberg.org/lindenii/furgit/object/header"
objectid "codeberg.org/lindenii/furgit/object/id"
+ "codeberg.org/lindenii/furgit/object/tag"
+ "codeberg.org/lindenii/furgit/object/tree"
objecttype "codeberg.org/lindenii/furgit/object/type"
)
@@ -31,13 +35,13 @@ func ParseObjectWithHeader(raw []byte, algo objectid.Algorithm) (Object, error)
func ParseObjectWithoutHeader(ty objecttype.Type, body []byte, algo objectid.Algorithm) (Object, error) {
switch ty {
case objecttype.TypeBlob:
- return ParseBlob(body)
+ return blob.Parse(body)
case objecttype.TypeTree:
- return ParseTree(body, algo)
+ return tree.Parse(body, algo)
case objecttype.TypeCommit:
- return ParseCommit(body, algo)
+ return commit.Parse(body, algo)
case objecttype.TypeTag:
- return ParseTag(body, algo)
+ return tag.Parse(body, algo)
case objecttype.TypeInvalid, objecttype.TypeFuture, objecttype.TypeOfsDelta, objecttype.TypeRefDelta:
return nil, fmt.Errorf("object: unsupported object type %d", ty)
default:
diff --git a/object/resolve/exact_blob.go b/object/resolve/exact_blob.go
index 07501513..2cd8b298 100644
--- a/object/resolve/exact_blob.go
+++ b/object/resolve/exact_blob.go
@@ -3,19 +3,19 @@ package resolve
import (
"fmt"
- "codeberg.org/lindenii/furgit/object"
+ "codeberg.org/lindenii/furgit/object/blob"
objectid "codeberg.org/lindenii/furgit/object/id"
"codeberg.org/lindenii/furgit/object/stored"
)
// ExactBlob reads, parses, and wraps the blob at id.
-func (r *Resolver) ExactBlob(id objectid.ObjectID) (*stored.Stored[*object.Blob], error) {
+func (r *Resolver) ExactBlob(id objectid.ObjectID) (*stored.Stored[*blob.Blob], error) {
parsed, err := r.parseObject(id)
if err != nil {
return nil, err
}
- blob, ok := parsed.(*object.Blob)
+ blob, ok := parsed.(*blob.Blob)
if !ok {
return nil, fmt.Errorf("object/resolve: expected blob object %s, got %v", id, parsed.ObjectType())
}
diff --git a/object/resolve/exact_commit.go b/object/resolve/exact_commit.go
index ba76baa2..e6b379aa 100644
--- a/object/resolve/exact_commit.go
+++ b/object/resolve/exact_commit.go
@@ -3,19 +3,19 @@ package resolve
import (
"fmt"
- "codeberg.org/lindenii/furgit/object"
+ "codeberg.org/lindenii/furgit/object/commit"
objectid "codeberg.org/lindenii/furgit/object/id"
"codeberg.org/lindenii/furgit/object/stored"
)
// ExactCommit reads, parses, and wraps the commit at id.
-func (r *Resolver) ExactCommit(id objectid.ObjectID) (*stored.Stored[*object.Commit], error) {
+func (r *Resolver) ExactCommit(id objectid.ObjectID) (*stored.Stored[*commit.Commit], error) {
parsed, err := r.parseObject(id)
if err != nil {
return nil, err
}
- commit, ok := parsed.(*object.Commit)
+ commit, ok := parsed.(*commit.Commit)
if !ok {
return nil, fmt.Errorf("object/resolve: expected commit object %s, got %v", id, parsed.ObjectType())
}
diff --git a/object/resolve/exact_tag.go b/object/resolve/exact_tag.go
index 26bf2b11..8c5d22c9 100644
--- a/object/resolve/exact_tag.go
+++ b/object/resolve/exact_tag.go
@@ -3,19 +3,19 @@ package resolve
import (
"fmt"
- "codeberg.org/lindenii/furgit/object"
objectid "codeberg.org/lindenii/furgit/object/id"
"codeberg.org/lindenii/furgit/object/stored"
+ "codeberg.org/lindenii/furgit/object/tag"
)
// ExactTag reads, parses, and wraps the tag at id.
-func (r *Resolver) ExactTag(id objectid.ObjectID) (*stored.Stored[*object.Tag], error) {
+func (r *Resolver) ExactTag(id objectid.ObjectID) (*stored.Stored[*tag.Tag], error) {
parsed, err := r.parseObject(id)
if err != nil {
return nil, err
}
- tag, ok := parsed.(*object.Tag)
+ tag, ok := parsed.(*tag.Tag)
if !ok {
return nil, fmt.Errorf("object/resolve: expected tag object %s, got %v", id, parsed.ObjectType())
}
diff --git a/object/resolve/exact_tree.go b/object/resolve/exact_tree.go
index aaf40236..de58ddb1 100644
--- a/object/resolve/exact_tree.go
+++ b/object/resolve/exact_tree.go
@@ -3,19 +3,19 @@ package resolve
import (
"fmt"
- "codeberg.org/lindenii/furgit/object"
objectid "codeberg.org/lindenii/furgit/object/id"
"codeberg.org/lindenii/furgit/object/stored"
+ "codeberg.org/lindenii/furgit/object/tree"
)
// ExactTree reads, parses, and wraps the tree at id.
-func (r *Resolver) ExactTree(id objectid.ObjectID) (*stored.Stored[*object.Tree], error) {
+func (r *Resolver) ExactTree(id objectid.ObjectID) (*stored.Stored[*tree.Tree], error) {
parsed, err := r.parseObject(id)
if err != nil {
return nil, err
}
- tree, ok := parsed.(*object.Tree)
+ tree, ok := parsed.(*tree.Tree)
if !ok {
return nil, fmt.Errorf("object/resolve: expected tree object %s, got %v", id, parsed.ObjectType())
}
diff --git a/object/resolve/path.go b/object/resolve/path.go
index 1f865403..d11f3b48 100644
--- a/object/resolve/path.go
+++ b/object/resolve/path.go
@@ -3,8 +3,8 @@ package resolve
import (
"fmt"
- "codeberg.org/lindenii/furgit/object"
objectid "codeberg.org/lindenii/furgit/object/id"
+ "codeberg.org/lindenii/furgit/object/tree"
)
// PathEmptyError indicates that Path received no segments.
@@ -59,24 +59,24 @@ func (err *PathNotTreeError) Error() string {
//
// If your entry names are valid UTF-8 and uses / solely as segment separators,
// it may be convenient to use TreeFS for an io/fs.FS-like interface.
-func (r *Resolver) Path(root objectid.ObjectID, parts [][]byte) (object.TreeEntry, error) {
+func (r *Resolver) Path(root objectid.ObjectID, parts [][]byte) (tree.TreeEntry, error) {
if len(parts) == 0 {
- return object.TreeEntry{}, &PathEmptyError{}
+ return tree.TreeEntry{}, &PathEmptyError{}
}
current, err := r.PeelToTree(root)
if err != nil {
- return object.TreeEntry{}, err
+ return tree.TreeEntry{}, err
}
for i, part := range parts {
if len(part) == 0 {
- return object.TreeEntry{}, &PathSegmentEmptyError{Index: i}
+ return tree.TreeEntry{}, &PathSegmentEmptyError{Index: i}
}
entry := current.Object().Entry(part)
if entry == nil {
- return object.TreeEntry{}, &PathNotFoundError{
+ return tree.TreeEntry{}, &PathNotFoundError{
Index: i,
Name: append([]byte(nil), part...),
}
@@ -86,8 +86,8 @@ func (r *Resolver) Path(root objectid.ObjectID, parts [][]byte) (object.TreeEntr
return *entry, nil
}
- if entry.Mode != object.FileModeDir {
- return object.TreeEntry{}, &PathNotTreeError{
+ if entry.Mode != tree.FileModeDir {
+ return tree.TreeEntry{}, &PathNotTreeError{
Index: i,
Name: append([]byte(nil), part...),
}
@@ -95,9 +95,9 @@ func (r *Resolver) Path(root objectid.ObjectID, parts [][]byte) (object.TreeEntr
current, err = r.ExactTree(entry.ID)
if err != nil {
- return object.TreeEntry{}, err
+ return tree.TreeEntry{}, err
}
}
- return object.TreeEntry{}, &PathNotFoundError{Index: len(parts) - 1}
+ return tree.TreeEntry{}, &PathNotFoundError{Index: len(parts) - 1}
}
diff --git a/object/resolve/peel_to_blob.go b/object/resolve/peel_to_blob.go
index 424e309f..c8aec1ad 100644
--- a/object/resolve/peel_to_blob.go
+++ b/object/resolve/peel_to_blob.go
@@ -3,13 +3,14 @@ package resolve
import (
"fmt"
- "codeberg.org/lindenii/furgit/object"
+ "codeberg.org/lindenii/furgit/object/blob"
objectid "codeberg.org/lindenii/furgit/object/id"
"codeberg.org/lindenii/furgit/object/stored"
+ "codeberg.org/lindenii/furgit/object/tag"
)
// PeelToBlob peels tags until it reaches a blob.
-func (r *Resolver) PeelToBlob(id objectid.ObjectID) (*stored.Stored[*object.Blob], error) {
+func (r *Resolver) PeelToBlob(id objectid.ObjectID) (*stored.Stored[*blob.Blob], error) {
for {
obj, err := r.ExactObject(id)
if err != nil {
@@ -17,9 +18,9 @@ func (r *Resolver) PeelToBlob(id objectid.ObjectID) (*stored.Stored[*object.Blob
}
switch parsed := obj.Object().(type) {
- case *object.Blob:
+ case *blob.Blob:
return stored.New(id, parsed), nil
- case *object.Tag:
+ case *tag.Tag:
id = parsed.Target
default:
return nil, fmt.Errorf("object/resolve: expected blob-ish object %s, got %v", id, parsed.ObjectType())
diff --git a/object/resolve/peel_to_commit.go b/object/resolve/peel_to_commit.go
index 355a3055..0272dd83 100644
--- a/object/resolve/peel_to_commit.go
+++ b/object/resolve/peel_to_commit.go
@@ -3,13 +3,14 @@ package resolve
import (
"fmt"
- "codeberg.org/lindenii/furgit/object"
+ "codeberg.org/lindenii/furgit/object/commit"
objectid "codeberg.org/lindenii/furgit/object/id"
"codeberg.org/lindenii/furgit/object/stored"
+ "codeberg.org/lindenii/furgit/object/tag"
)
// PeelToCommit peels tags until it reaches a commit.
-func (r *Resolver) PeelToCommit(id objectid.ObjectID) (*stored.Stored[*object.Commit], error) {
+func (r *Resolver) PeelToCommit(id objectid.ObjectID) (*stored.Stored[*commit.Commit], error) {
for {
obj, err := r.ExactObject(id)
if err != nil {
@@ -17,9 +18,9 @@ func (r *Resolver) PeelToCommit(id objectid.ObjectID) (*stored.Stored[*object.Co
}
switch parsed := obj.Object().(type) {
- case *object.Commit:
+ case *commit.Commit:
return stored.New(id, parsed), nil
- case *object.Tag:
+ case *tag.Tag:
id = parsed.Target
default:
return nil, fmt.Errorf("object/resolve: expected commit-ish object %s, got %v", id, parsed.ObjectType())
diff --git a/object/resolve/peel_to_tag.go b/object/resolve/peel_to_tag.go
index 6f61d7e1..e131f4c1 100644
--- a/object/resolve/peel_to_tag.go
+++ b/object/resolve/peel_to_tag.go
@@ -1,12 +1,12 @@
package resolve
import (
- "codeberg.org/lindenii/furgit/object"
objectid "codeberg.org/lindenii/furgit/object/id"
"codeberg.org/lindenii/furgit/object/stored"
+ "codeberg.org/lindenii/furgit/object/tag"
)
// PeelToTag returns the tag at id without further peeling.
-func (r *Resolver) PeelToTag(id objectid.ObjectID) (*stored.Stored[*object.Tag], error) {
+func (r *Resolver) PeelToTag(id objectid.ObjectID) (*stored.Stored[*tag.Tag], error) {
return r.ExactTag(id)
}
diff --git a/object/resolve/peel_to_tree.go b/object/resolve/peel_to_tree.go
index 3b12bdd8..2f2da4d7 100644
--- a/object/resolve/peel_to_tree.go
+++ b/object/resolve/peel_to_tree.go
@@ -3,14 +3,16 @@ package resolve
import (
"fmt"
- "codeberg.org/lindenii/furgit/object"
+ "codeberg.org/lindenii/furgit/object/commit"
objectid "codeberg.org/lindenii/furgit/object/id"
"codeberg.org/lindenii/furgit/object/stored"
+ "codeberg.org/lindenii/furgit/object/tag"
+ "codeberg.org/lindenii/furgit/object/tree"
)
// PeelToTree peels tags until it reaches a tree or commit. If it reaches a
// commit, it returns the commit's root tree.
-func (r *Resolver) PeelToTree(id objectid.ObjectID) (*stored.Stored[*object.Tree], error) {
+func (r *Resolver) PeelToTree(id objectid.ObjectID) (*stored.Stored[*tree.Tree], error) {
for {
obj, err := r.ExactObject(id)
if err != nil {
@@ -18,11 +20,11 @@ func (r *Resolver) PeelToTree(id objectid.ObjectID) (*stored.Stored[*object.Tree
}
switch parsed := obj.Object().(type) {
- case *object.Tree:
+ case *tree.Tree:
return stored.New(id, parsed), nil
- case *object.Commit:
+ case *commit.Commit:
return r.ExactTree(parsed.Tree)
- case *object.Tag:
+ case *tag.Tag:
id = parsed.Target
default:
return nil, fmt.Errorf("object/resolve: expected tree-ish object %s, got %v", id, parsed.ObjectType())
diff --git a/object/resolve/resolver.go b/object/resolve/resolver.go
index 3e76e96a..f5e4e8c3 100644
--- a/object/resolve/resolver.go
+++ b/object/resolve/resolver.go
@@ -1,6 +1,6 @@
package resolve
-import "codeberg.org/lindenii/furgit/object/storer"
+import objectstorer "codeberg.org/lindenii/furgit/object/storer"
// Resolver resolves parsed and streamed objects from an object store.
//
diff --git a/object/resolve/treefs.go b/object/resolve/treefs.go
index de5d588a..a080d56d 100644
--- a/object/resolve/treefs.go
+++ b/object/resolve/treefs.go
@@ -3,8 +3,8 @@ package resolve
import (
"io/fs"
- "codeberg.org/lindenii/furgit/object"
objectid "codeberg.org/lindenii/furgit/object/id"
+ "codeberg.org/lindenii/furgit/object/tree"
)
// TreeFS exposes one Git tree as an fs.FS.
@@ -18,7 +18,7 @@ import (
type TreeFS struct {
resolver *Resolver
rootTree objectid.ObjectID
- rootEntry *object.TreeEntry
+ rootEntry *tree.TreeEntry
}
var (
diff --git a/object/resolve/treefs_entry.go b/object/resolve/treefs_entry.go
index b37ac0a0..6d23e282 100644
--- a/object/resolve/treefs_entry.go
+++ b/object/resolve/treefs_entry.go
@@ -5,8 +5,8 @@ import (
"fmt"
"io/fs"
- "codeberg.org/lindenii/furgit/object"
objectid "codeberg.org/lindenii/furgit/object/id"
+ "codeberg.org/lindenii/furgit/object/tree"
)
func (treeFS *TreeFS) resolvePath(op treeFSOp, name string) (treeEntryValue, error) {
@@ -17,7 +17,7 @@ func (treeFS *TreeFS) resolvePath(op treeFSOp, name string) (treeEntryValue, err
if name == "." {
return treeEntryValue{
name: ".",
- mode: object.FileModeDir,
+ mode: tree.FileModeDir,
treeID: treeFS.rootTree,
treeEntry: treeFS.rootEntry,
}, nil
@@ -58,14 +58,14 @@ func (treeFS *TreeFS) pathResolveError(op treeFSOp, name string, err error) erro
type treeEntryValue struct {
name string
- mode object.FileMode
+ mode tree.FileMode
objectID objectid.ObjectID
treeID objectid.ObjectID
- treeEntry *object.TreeEntry
+ treeEntry *tree.TreeEntry
}
func (entry treeEntryValue) isDir() bool {
- return entry.mode == object.FileModeDir
+ return entry.mode == tree.FileModeDir
}
func (entry treeEntryValue) blobSize(resolve *Resolver) (int64, error) {
@@ -82,7 +82,7 @@ func (entry treeEntryValue) subtreeID() (objectid.ObjectID, error) {
return entry.treeID, nil
}
- if entry.mode != object.FileModeDir {
+ if entry.mode != tree.FileModeDir {
return objectid.ObjectID{}, fmt.Errorf("object/resolve: path %q is not a tree", entry.name)
}
diff --git a/object/resolve/treefs_info.go b/object/resolve/treefs_info.go
index f554973d..f8eb1e9e 100644
--- a/object/resolve/treefs_info.go
+++ b/object/resolve/treefs_info.go
@@ -4,7 +4,7 @@ import (
"io/fs"
"time"
- "codeberg.org/lindenii/furgit/object"
+ "codeberg.org/lindenii/furgit/object/tree"
)
type treeFSInfo struct {
@@ -31,17 +31,17 @@ func (info *treeFSInfo) Info() (fs.FileInfo, error) {
return info, nil
}
-func treeFSEntryMode(mode object.FileMode) fs.FileMode {
+func treeFSEntryMode(mode tree.FileMode) fs.FileMode {
switch mode {
- case object.FileModeDir:
+ case tree.FileModeDir:
return fs.ModeDir | 0o555
- case object.FileModeRegular:
+ case tree.FileModeRegular:
return 0o444
- case object.FileModeExecutable:
+ case tree.FileModeExecutable:
return 0o555
- case object.FileModeSymlink:
+ case tree.FileModeSymlink:
return fs.ModeSymlink | 0o444
- case object.FileModeGitlink:
+ case tree.FileModeGitlink:
return fs.ModeIrregular
default:
return fs.ModeIrregular
@@ -51,7 +51,7 @@ func treeFSEntryMode(mode object.FileMode) fs.FileMode {
func (treeFS *TreeFS) statEntry(entry treeEntryValue) (*treeFSInfo, error) {
size := int64(0)
- if entry.mode == object.FileModeRegular || entry.mode == object.FileModeExecutable || entry.mode == object.FileModeSymlink {
+ if entry.mode == tree.FileModeRegular || entry.mode == tree.FileModeExecutable || entry.mode == tree.FileModeSymlink {
var err error
size, err = entry.blobSize(treeFS.resolver)
diff --git a/object/resolve/treefs_open.go b/object/resolve/treefs_open.go
index c938505b..8e2b3588 100644
--- a/object/resolve/treefs_open.go
+++ b/object/resolve/treefs_open.go
@@ -5,7 +5,7 @@ import (
"io"
"io/fs"
- "codeberg.org/lindenii/furgit/object"
+ "codeberg.org/lindenii/furgit/object/tree"
)
// Open opens name for reading.
@@ -57,7 +57,7 @@ func (treeFS *TreeFS) Open(name string) (fs.File, error) {
}, nil
}
- if entry.mode == object.FileModeGitlink {
+ if entry.mode == tree.FileModeGitlink {
return nil, treeFSPathError(treeFSOpOpen, name, fmt.Errorf("object/resolve: gitlink entries are not readable as files"))
}
diff --git a/object/resolve/treefs_readfile.go b/object/resolve/treefs_readfile.go
index e2bc1698..e1d514a3 100644
--- a/object/resolve/treefs_readfile.go
+++ b/object/resolve/treefs_readfile.go
@@ -4,7 +4,7 @@ import (
"fmt"
"io"
- "codeberg.org/lindenii/furgit/object"
+ "codeberg.org/lindenii/furgit/object/tree"
)
// ReadFile reads the blob contents at name.
@@ -20,7 +20,7 @@ func (treeFS *TreeFS) ReadFile(name string) ([]byte, error) {
return nil, treeFSPathError(treeFSOpReadFile, name, fmt.Errorf("is a directory"))
}
- if entry.mode == object.FileModeGitlink {
+ if entry.mode == tree.FileModeGitlink {
return nil, treeFSPathError(treeFSOpReadFile, name, fmt.Errorf("object/resolve: gitlink entries are not readable as files"))
}
diff --git a/object/resolve/treefs_stat.go b/object/resolve/treefs_stat.go
index 044ba049..396dfbae 100644
--- a/object/resolve/treefs_stat.go
+++ b/object/resolve/treefs_stat.go
@@ -6,7 +6,7 @@ import "io/fs"
//
// TreeFS metadata reflects Git tree entry mode and blob size where applicable.
// It does not represent filesystem stat metadata: ModTime is zero, ownership is
-// unavailable, and Sys returns the underlying object.TreeEntry when one exists.
+// unavailable, and Sys returns the underlying tree.TreeEntry when one exists.
func (treeFS *TreeFS) Stat(name string) (fs.FileInfo, error) {
entry, err := treeFS.resolvePath(treeFSOpStat, name)
if err != nil {
diff --git a/object/resolve/treefs_test.go b/object/resolve/treefs_test.go
index a22e5019..0c436c0b 100644
--- a/object/resolve/treefs_test.go
+++ b/object/resolve/treefs_test.go
@@ -6,9 +6,9 @@ import (
"testing"
"codeberg.org/lindenii/furgit/internal/testgit"
- "codeberg.org/lindenii/furgit/object"
objectid "codeberg.org/lindenii/furgit/object/id"
"codeberg.org/lindenii/furgit/object/resolve"
+ "codeberg.org/lindenii/furgit/object/tree"
"codeberg.org/lindenii/furgit/repository"
)
@@ -71,13 +71,13 @@ func TestTreeFS(t *testing.T) {
t.Fatalf("Stat(plain.txt): %v", err)
}
- entry, ok := info.Sys().(object.TreeEntry)
+ entry, ok := info.Sys().(tree.TreeEntry)
if !ok {
- t.Fatalf("Stat(plain.txt).Sys() type = %T, want object.TreeEntry", info.Sys())
+ t.Fatalf("Stat(plain.txt).Sys() type = %T, want tree.TreeEntry", info.Sys())
}
- if entry.Mode != object.FileModeRegular {
- t.Fatalf("Stat(plain.txt).Sys().Mode = %o, want %o", entry.Mode, object.FileModeRegular)
+ if entry.Mode != tree.FileModeRegular {
+ t.Fatalf("Stat(plain.txt).Sys().Mode = %o, want %o", entry.Mode, tree.FileModeRegular)
}
subFS, err := treeFS.Sub("dir")
diff --git a/object/ident.go b/object/signature/parse.go
index 049b0c01..a6880eee 100644
--- a/object/ident.go
+++ b/object/signature/parse.go
@@ -1,27 +1,17 @@
-package object
+package signature
import (
"bytes"
"errors"
"fmt"
"strconv"
- "strings"
- "time"
"codeberg.org/lindenii/furgit/internal/intconv"
)
-// Signature represents a Git signature (author/committer/tagger).
-type Signature struct {
- Name []byte
- Email []byte
- WhenUnix int64
- OffsetMinutes int32
-}
-
-// ParseSignature parses a canonical Git signature line:
+// Parse parses a canonical Git signature line:
// "Name <email> 123456789 +0000".
-func ParseSignature(line []byte) (*Signature, error) {
+func Parse(line []byte) (*Signature, error) {
lt := bytes.IndexByte(line, '<')
if lt < 0 {
return nil, errors.New("object: signature: missing opening <")
@@ -105,36 +95,3 @@ func ParseSignature(line []byte) (*Signature, error) {
OffsetMinutes: offset,
}, nil
}
-
-// Serialize renders the signature in canonical Git format.
-func (signature Signature) Serialize() ([]byte, error) {
- var b strings.Builder
- b.Grow(len(signature.Name) + len(signature.Email) + 32)
- b.Write(signature.Name)
- b.WriteString(" <")
- b.Write(signature.Email)
- b.WriteString("> ")
- b.WriteString(strconv.FormatInt(signature.WhenUnix, 10))
- b.WriteByte(' ')
-
- offset := signature.OffsetMinutes
-
- sign := '+'
- if offset < 0 {
- sign = '-'
- offset = -offset
- }
-
- hh := offset / 60
- mm := offset % 60
- fmt.Fprintf(&b, "%c%02d%02d", sign, hh, mm)
-
- return []byte(b.String()), nil
-}
-
-// When returns a time.Time with the signature's timezone offset.
-func (signature Signature) When() time.Time {
- loc := time.FixedZone("git", int(signature.OffsetMinutes)*60)
-
- return time.Unix(signature.WhenUnix, 0).In(loc)
-}
diff --git a/object/signature/serialize.go b/object/signature/serialize.go
new file mode 100644
index 00000000..3f60d20d
--- /dev/null
+++ b/object/signature/serialize.go
@@ -0,0 +1,33 @@
+package signature
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+// Serialize renders the signature in canonical Git format.
+func (signature Signature) Serialize() ([]byte, error) {
+ var b strings.Builder
+ b.Grow(len(signature.Name) + len(signature.Email) + 32)
+ b.Write(signature.Name)
+ b.WriteString(" <")
+ b.Write(signature.Email)
+ b.WriteString("> ")
+ b.WriteString(strconv.FormatInt(signature.WhenUnix, 10))
+ b.WriteByte(' ')
+
+ offset := signature.OffsetMinutes
+
+ sign := '+'
+ if offset < 0 {
+ sign = '-'
+ offset = -offset
+ }
+
+ hh := offset / 60
+ mm := offset % 60
+ fmt.Fprintf(&b, "%c%02d%02d", sign, hh, mm)
+
+ return []byte(b.String()), nil
+}
diff --git a/object/signature/signature.go b/object/signature/signature.go
new file mode 100644
index 00000000..22e516f9
--- /dev/null
+++ b/object/signature/signature.go
@@ -0,0 +1,10 @@
+// Package signature provides routines and representations that implement author/committer/tagger signatures.
+package signature
+
+// Signature represents a Git signature (author/committer/tagger).
+type Signature struct {
+ Name []byte
+ Email []byte
+ WhenUnix int64
+ OffsetMinutes int32
+}
diff --git a/object/signature/when.go b/object/signature/when.go
new file mode 100644
index 00000000..0a252f68
--- /dev/null
+++ b/object/signature/when.go
@@ -0,0 +1,10 @@
+package signature
+
+import "time"
+
+// When returns a time.Time with the signature's timezone offset.
+func (signature Signature) When() time.Time {
+ loc := time.FixedZone("git", int(signature.OffsetMinutes)*60)
+
+ return time.Unix(signature.WhenUnix, 0).In(loc)
+}
diff --git a/object/stored/stored.go b/object/stored/stored.go
index f48aaa77..4429a373 100644
--- a/object/stored/stored.go
+++ b/object/stored/stored.go
@@ -1,7 +1,7 @@
// Package stored wraps parsed objects with their storage object IDs.
//
// Stored values are typically instantiated with pointer object types such as
-// *object.Blob, *object.Tree, *object.Commit, or *object.Tag, because those
+// *blob.Blob, *tree.Tree, *commit.Commit, or *tag.Tag, because those
// pointer types satisfy object.Object.
package stored
diff --git a/object/storer/chain/bytes.go b/object/storer/chain/bytes.go
index c3ec1eb8..d41f3b92 100644
--- a/object/storer/chain/bytes.go
+++ b/object/storer/chain/bytes.go
@@ -5,7 +5,7 @@ import (
"fmt"
objectid "codeberg.org/lindenii/furgit/object/id"
- "codeberg.org/lindenii/furgit/object/storer"
+ objectstorer "codeberg.org/lindenii/furgit/object/storer"
objecttype "codeberg.org/lindenii/furgit/object/type"
)
diff --git a/object/storer/chain/chain.go b/object/storer/chain/chain.go
index 8502b590..44909cff 100644
--- a/object/storer/chain/chain.go
+++ b/object/storer/chain/chain.go
@@ -2,9 +2,7 @@
// backends.
package chain
-import (
- "codeberg.org/lindenii/furgit/object/storer"
-)
+import objectstorer "codeberg.org/lindenii/furgit/object/storer"
// Chain queries multiple object databases in order.
//
diff --git a/object/storer/chain/header.go b/object/storer/chain/header.go
index e7791e9e..4feaf8e4 100644
--- a/object/storer/chain/header.go
+++ b/object/storer/chain/header.go
@@ -5,7 +5,7 @@ import (
"fmt"
objectid "codeberg.org/lindenii/furgit/object/id"
- "codeberg.org/lindenii/furgit/object/storer"
+ objectstorer "codeberg.org/lindenii/furgit/object/storer"
objecttype "codeberg.org/lindenii/furgit/object/type"
)
diff --git a/object/storer/chain/new.go b/object/storer/chain/new.go
index f7a4f141..0368bfb3 100644
--- a/object/storer/chain/new.go
+++ b/object/storer/chain/new.go
@@ -1,6 +1,6 @@
package chain
-import "codeberg.org/lindenii/furgit/object/storer"
+import objectstorer "codeberg.org/lindenii/furgit/object/storer"
// New creates an ordered object database chain.
//
diff --git a/object/storer/chain/reader.go b/object/storer/chain/reader.go
index 3ac8cce7..e3c50013 100644
--- a/object/storer/chain/reader.go
+++ b/object/storer/chain/reader.go
@@ -6,7 +6,7 @@ import (
"io"
objectid "codeberg.org/lindenii/furgit/object/id"
- "codeberg.org/lindenii/furgit/object/storer"
+ objectstorer "codeberg.org/lindenii/furgit/object/storer"
objecttype "codeberg.org/lindenii/furgit/object/type"
)
diff --git a/object/storer/chain/size.go b/object/storer/chain/size.go
index 6ad7d12c..c82b248d 100644
--- a/object/storer/chain/size.go
+++ b/object/storer/chain/size.go
@@ -5,7 +5,7 @@ import (
"fmt"
objectid "codeberg.org/lindenii/furgit/object/id"
- "codeberg.org/lindenii/furgit/object/storer"
+ objectstorer "codeberg.org/lindenii/furgit/object/storer"
)
// ReadSize reads object content length from the first backend that has it.
diff --git a/object/storer/loose/paths.go b/object/storer/loose/paths.go
index 73cb0cf3..58ef6b8e 100644
--- a/object/storer/loose/paths.go
+++ b/object/storer/loose/paths.go
@@ -8,7 +8,7 @@ import (
"path/filepath"
objectid "codeberg.org/lindenii/furgit/object/id"
- "codeberg.org/lindenii/furgit/object/storer"
+ objectstorer "codeberg.org/lindenii/furgit/object/storer"
)
// objectPath returns the loose object path for id relative to the objects root.
diff --git a/object/storer/loose/read_test.go b/object/storer/loose/read_test.go
index ece3c9db..d44ecea8 100644
--- a/object/storer/loose/read_test.go
+++ b/object/storer/loose/read_test.go
@@ -9,7 +9,7 @@ import (
"codeberg.org/lindenii/furgit/internal/testgit"
objectid "codeberg.org/lindenii/furgit/object/id"
- "codeberg.org/lindenii/furgit/object/storer"
+ objectstorer "codeberg.org/lindenii/furgit/object/storer"
"codeberg.org/lindenii/furgit/object/storer/loose"
objecttype "codeberg.org/lindenii/furgit/object/type"
)
diff --git a/object/storer/memory/read_bytes.go b/object/storer/memory/read_bytes.go
index 72eaba11..e8b437ea 100644
--- a/object/storer/memory/read_bytes.go
+++ b/object/storer/memory/read_bytes.go
@@ -3,7 +3,7 @@ package memory
import (
objectheader "codeberg.org/lindenii/furgit/object/header"
objectid "codeberg.org/lindenii/furgit/object/id"
- "codeberg.org/lindenii/furgit/object/storer"
+ objectstorer "codeberg.org/lindenii/furgit/object/storer"
objecttype "codeberg.org/lindenii/furgit/object/type"
)
diff --git a/object/storer/memory/read_header.go b/object/storer/memory/read_header.go
index 56979d3c..73cc4561 100644
--- a/object/storer/memory/read_header.go
+++ b/object/storer/memory/read_header.go
@@ -2,7 +2,7 @@ package memory
import (
objectid "codeberg.org/lindenii/furgit/object/id"
- "codeberg.org/lindenii/furgit/object/storer"
+ objectstorer "codeberg.org/lindenii/furgit/object/storer"
objecttype "codeberg.org/lindenii/furgit/object/type"
)
diff --git a/object/storer/mix/bytes.go b/object/storer/mix/bytes.go
index d2a7dc0e..a281c332 100644
--- a/object/storer/mix/bytes.go
+++ b/object/storer/mix/bytes.go
@@ -5,7 +5,7 @@ import (
"fmt"
objectid "codeberg.org/lindenii/furgit/object/id"
- "codeberg.org/lindenii/furgit/object/storer"
+ objectstorer "codeberg.org/lindenii/furgit/object/storer"
objecttype "codeberg.org/lindenii/furgit/object/type"
)
diff --git a/object/storer/mix/header.go b/object/storer/mix/header.go
index 6a5abf26..8bab48c2 100644
--- a/object/storer/mix/header.go
+++ b/object/storer/mix/header.go
@@ -5,7 +5,7 @@ import (
"fmt"
objectid "codeberg.org/lindenii/furgit/object/id"
- "codeberg.org/lindenii/furgit/object/storer"
+ objectstorer "codeberg.org/lindenii/furgit/object/storer"
objecttype "codeberg.org/lindenii/furgit/object/type"
)
diff --git a/object/storer/mix/mix.go b/object/storer/mix/mix.go
index 9edda31e..6154314f 100644
--- a/object/storer/mix/mix.go
+++ b/object/storer/mix/mix.go
@@ -5,7 +5,7 @@ package mix
import (
"sync"
- "codeberg.org/lindenii/furgit/object/storer"
+ objectstorer "codeberg.org/lindenii/furgit/object/storer"
)
// Mix queries multiple object databases with an MRU backend preference.
diff --git a/object/storer/mix/mru.go b/object/storer/mix/mru.go
index 172a641a..644fe818 100644
--- a/object/storer/mix/mru.go
+++ b/object/storer/mix/mru.go
@@ -1,6 +1,6 @@
package mix
-import "codeberg.org/lindenii/furgit/object/storer"
+import objectstorer "codeberg.org/lindenii/furgit/object/storer"
type backendNode struct {
backend objectstorer.Store
diff --git a/object/storer/mix/new.go b/object/storer/mix/new.go
index f92e2724..c59d3b9f 100644
--- a/object/storer/mix/new.go
+++ b/object/storer/mix/new.go
@@ -1,6 +1,6 @@
package mix
-import "codeberg.org/lindenii/furgit/object/storer"
+import objectstorer "codeberg.org/lindenii/furgit/object/storer"
// New creates a Mix from backends.
//
diff --git a/object/storer/mix/reader.go b/object/storer/mix/reader.go
index 66fce069..7ddbaa4e 100644
--- a/object/storer/mix/reader.go
+++ b/object/storer/mix/reader.go
@@ -6,7 +6,7 @@ import (
"io"
objectid "codeberg.org/lindenii/furgit/object/id"
- "codeberg.org/lindenii/furgit/object/storer"
+ objectstorer "codeberg.org/lindenii/furgit/object/storer"
objecttype "codeberg.org/lindenii/furgit/object/type"
)
diff --git a/object/storer/mix/refresh.go b/object/storer/mix/refresh.go
index 916d9e8f..2e861a61 100644
--- a/object/storer/mix/refresh.go
+++ b/object/storer/mix/refresh.go
@@ -3,7 +3,7 @@ package mix
import (
"errors"
- "codeberg.org/lindenii/furgit/object/storer"
+ objectstorer "codeberg.org/lindenii/furgit/object/storer"
)
// Refresh forwards refresh calls to refresh-capable backends.
diff --git a/object/storer/mix/size.go b/object/storer/mix/size.go
index b761177d..da8e02b7 100644
--- a/object/storer/mix/size.go
+++ b/object/storer/mix/size.go
@@ -5,7 +5,7 @@ import (
"fmt"
objectid "codeberg.org/lindenii/furgit/object/id"
- "codeberg.org/lindenii/furgit/object/storer"
+ objectstorer "codeberg.org/lindenii/furgit/object/storer"
)
// ReadSize reads object content length from one backend that has it.
diff --git a/object/storer/packed/read_test.go b/object/storer/packed/read_test.go
index 4686d192..841019fe 100644
--- a/object/storer/packed/read_test.go
+++ b/object/storer/packed/read_test.go
@@ -11,7 +11,7 @@ import (
"codeberg.org/lindenii/furgit/internal/testgit"
objectid "codeberg.org/lindenii/furgit/object/id"
- "codeberg.org/lindenii/furgit/object/storer"
+ objectstorer "codeberg.org/lindenii/furgit/object/storer"
"codeberg.org/lindenii/furgit/object/storer/packed"
)
diff --git a/object/storer/packed/store.go b/object/storer/packed/store.go
index a95bedd7..99556d32 100644
--- a/object/storer/packed/store.go
+++ b/object/storer/packed/store.go
@@ -7,7 +7,7 @@ import (
"sync/atomic"
objectid "codeberg.org/lindenii/furgit/object/id"
- "codeberg.org/lindenii/furgit/object/storer"
+ objectstorer "codeberg.org/lindenii/furgit/object/storer"
)
// Store reads Git objects from pack/index files under an objects/pack root.
diff --git a/object/storer/packed/store_lookup.go b/object/storer/packed/store_lookup.go
index 3985463b..accb2d25 100644
--- a/object/storer/packed/store_lookup.go
+++ b/object/storer/packed/store_lookup.go
@@ -4,7 +4,7 @@ import (
"errors"
objectid "codeberg.org/lindenii/furgit/object/id"
- "codeberg.org/lindenii/furgit/object/storer"
+ objectstorer "codeberg.org/lindenii/furgit/object/storer"
)
// lookup resolves one object ID to its pack location.
diff --git a/object/tag_parse.go b/object/tag/parse.go
index afc9a2e9..f24d5965 100644
--- a/object/tag_parse.go
+++ b/object/tag/parse.go
@@ -1,4 +1,4 @@
-package object
+package tag
import (
"bytes"
@@ -6,11 +6,12 @@ import (
"fmt"
objectid "codeberg.org/lindenii/furgit/object/id"
+ objectsignature "codeberg.org/lindenii/furgit/object/signature"
objecttype "codeberg.org/lindenii/furgit/object/type"
)
-// ParseTag decodes a tag object body.
-func ParseTag(body []byte, algo objectid.Algorithm) (*Tag, error) {
+// Parse decodes a tag object body.
+func Parse(body []byte, algo objectid.Algorithm) (*Tag, error) {
t := new(Tag)
i := 0
@@ -54,7 +55,7 @@ func ParseTag(body []byte, algo objectid.Algorithm) (*Tag, error) {
case "tag":
t.Name = append([]byte(nil), value...)
case "tagger":
- idt, err := ParseSignature(value)
+ idt, err := objectsignature.Parse(value)
if err != nil {
return nil, fmt.Errorf("object: tag: tagger: %w", err)
}
diff --git a/object/tag_parse_test.go b/object/tag/parse_test.go
index 07998f1c..293350ed 100644
--- a/object/tag_parse_test.go
+++ b/object/tag/parse_test.go
@@ -1,12 +1,12 @@
-package object_test
+package tag_test
import (
"bytes"
"testing"
"codeberg.org/lindenii/furgit/internal/testgit"
- "codeberg.org/lindenii/furgit/object"
objectid "codeberg.org/lindenii/furgit/object/id"
+ "codeberg.org/lindenii/furgit/object/tag"
objecttype "codeberg.org/lindenii/furgit/object/type"
)
@@ -19,29 +19,29 @@ func TestTagParseFromGit(t *testing.T) {
rawBody := testRepo.CatFile(t, "tag", tagID)
- tag, err := object.ParseTag(rawBody, algo)
+ parsed, err := tag.Parse(rawBody, algo)
if err != nil {
t.Fatalf("ParseTag: %v", err)
}
- if tag.Target != commitID {
- t.Fatalf("tag target mismatch: got %s want %s", tag.Target, commitID)
+ if parsed.Target != commitID {
+ t.Fatalf("tag target mismatch: got %s want %s", parsed.Target, commitID)
}
- if tag.TargetType != objecttype.TypeCommit {
- t.Fatalf("tag target type = %v, want %v", tag.TargetType, objecttype.TypeCommit)
+ if parsed.TargetType != objecttype.TypeCommit {
+ t.Fatalf("tag target type = %v, want %v", parsed.TargetType, objecttype.TypeCommit)
}
- if !bytes.Equal(tag.Name, []byte("v1")) {
- t.Fatalf("tag name = %q, want %q", tag.Name, "v1")
+ if !bytes.Equal(parsed.Name, []byte("v1")) {
+ t.Fatalf("tag name = %q, want %q", parsed.Name, "v1")
}
- if tag.Tagger == nil {
+ if parsed.Tagger == nil {
t.Fatalf("expected tagger")
}
- if !bytes.Contains(tag.Message, []byte("tag message")) {
- t.Fatalf("tag message mismatch: %q", tag.Message)
+ if !bytes.Contains(parsed.Message, []byte("tag message")) {
+ t.Fatalf("tag message mismatch: %q", parsed.Message)
}
})
}
diff --git a/object/tag_serialize.go b/object/tag/serialize.go
index c914e8dd..5f712950 100644
--- a/object/tag_serialize.go
+++ b/object/tag/serialize.go
@@ -1,4 +1,4 @@
-package object
+package tag
import (
"bytes"
diff --git a/object/tag_serialize_test.go b/object/tag/serialize_test.go
index de9f813d..a1311c39 100644
--- a/object/tag_serialize_test.go
+++ b/object/tag/serialize_test.go
@@ -1,11 +1,11 @@
-package object_test
+package tag_test
import (
"testing"
"codeberg.org/lindenii/furgit/internal/testgit"
- "codeberg.org/lindenii/furgit/object"
objectid "codeberg.org/lindenii/furgit/object/id"
+ "codeberg.org/lindenii/furgit/object/tag"
)
func TestTagSerialize(t *testing.T) {
@@ -17,12 +17,12 @@ func TestTagSerialize(t *testing.T) {
rawBody := testRepo.CatFile(t, "tag", tagID)
- tag, err := object.ParseTag(rawBody, algo)
+ parsed, err := tag.Parse(rawBody, algo)
if err != nil {
t.Fatalf("ParseTag: %v", err)
}
- rawObj, err := tag.SerializeWithHeader()
+ rawObj, err := parsed.SerializeWithHeader()
if err != nil {
t.Fatalf("SerializeWithHeader: %v", err)
}
diff --git a/object/tag.go b/object/tag/tag.go
index 50c4b273..4301557e 100644
--- a/object/tag.go
+++ b/object/tag/tag.go
@@ -1,7 +1,9 @@
-package object
+// Package tag provides representations, parsers, and serializers for tag objects.
+package tag
import (
objectid "codeberg.org/lindenii/furgit/object/id"
+ objectsignature "codeberg.org/lindenii/furgit/object/signature"
objecttype "codeberg.org/lindenii/furgit/object/type"
)
@@ -10,13 +12,6 @@ type Tag struct {
Target objectid.ObjectID
TargetType objecttype.Type
Name []byte
- Tagger *Signature
+ Tagger *objectsignature.Signature
Message []byte
}
-
-// ObjectType returns TypeTag.
-func (tag *Tag) ObjectType() objecttype.Type {
- _ = tag
-
- return objecttype.TypeTag
-}
diff --git a/object/tag/type.go b/object/tag/type.go
new file mode 100644
index 00000000..215103ab
--- /dev/null
+++ b/object/tag/type.go
@@ -0,0 +1,10 @@
+package tag
+
+import objecttype "codeberg.org/lindenii/furgit/object/type"
+
+// ObjectType returns TypeTag.
+func (tag *Tag) ObjectType() objecttype.Type {
+ _ = tag
+
+ return objecttype.TypeTag
+}
diff --git a/object/tree.go b/object/tree.go
deleted file mode 100644
index 83dcb508..00000000
--- a/object/tree.go
+++ /dev/null
@@ -1,163 +0,0 @@
-package object
-
-import (
- "bytes"
- "fmt"
- "sort"
-
- objectid "codeberg.org/lindenii/furgit/object/id"
- objecttype "codeberg.org/lindenii/furgit/object/type"
-)
-
-// FileMode represents the mode of a file in a Git tree.
-type FileMode uint32
-
-const (
- FileModeDir FileMode = 0o40000
- FileModeRegular FileMode = 0o100644
- FileModeExecutable FileMode = 0o100755
- FileModeSymlink FileMode = 0o120000
- FileModeGitlink FileMode = 0o160000
-)
-
-// TreeEntry represents a single entry in a tree.
-type TreeEntry struct {
- Mode FileMode
- Name []byte
- ID objectid.ObjectID
-}
-
-// Tree represents a Git tree object.
-type Tree struct {
- Entries []TreeEntry
-}
-
-// ObjectType returns TypeTree.
-func (tree *Tree) ObjectType() objecttype.Type {
- _ = tree
-
- return objecttype.TypeTree
-}
-
-// Entry looks up a tree entry by name.
-func (tree *Tree) Entry(name []byte) *TreeEntry {
- if len(tree.Entries) == 0 {
- return nil
- }
-
- if e := tree.entry(name, true); e != nil {
- return e
- }
-
- return tree.entry(name, false)
-}
-
-// InsertEntry inserts a tree entry while preserving Git ordering.
-func (tree *Tree) InsertEntry(newEntry TreeEntry) error {
- if tree.entry(newEntry.Name, true) != nil || tree.entry(newEntry.Name, false) != nil {
- return fmt.Errorf("object: tree: entry %q already exists", newEntry.Name)
- }
-
- newIsTree := newEntry.Mode == FileModeDir
- insertAt := sort.Search(len(tree.Entries), func(i int) bool {
- return TreeEntryNameCompare(tree.Entries[i].Name, tree.Entries[i].Mode, newEntry.Name, newIsTree) >= 0
- })
- tree.Entries = append(tree.Entries, TreeEntry{})
- copy(tree.Entries[insertAt+1:], tree.Entries[insertAt:])
- tree.Entries[insertAt] = newEntry
-
- return nil
-}
-
-// RemoveEntry removes a tree entry by name.
-func (tree *Tree) RemoveEntry(name []byte) error {
- if len(tree.Entries) == 0 {
- return fmt.Errorf("object: tree: entry %q not found", name)
- }
-
- for i := range tree.Entries {
- if bytes.Equal(tree.Entries[i].Name, name) {
- copy(tree.Entries[i:], tree.Entries[i+1:])
- tree.Entries = tree.Entries[:len(tree.Entries)-1]
-
- return nil
- }
- }
-
- return fmt.Errorf("object: tree: entry %q not found", name)
-}
-
-func (tree *Tree) entry(name []byte, searchIsTree bool) *TreeEntry {
- low, high := 0, len(tree.Entries)-1
- for low <= high {
- mid := low + (high-low)/2
- entry := &tree.Entries[mid]
-
- cmp := TreeEntryNameCompare(entry.Name, entry.Mode, name, searchIsTree)
- if cmp == 0 {
- if bytes.Equal(entry.Name, name) {
- return entry
- }
-
- return nil
- }
-
- if cmp < 0 {
- low = mid + 1
- } else {
- high = mid - 1
- }
- }
-
- return nil
-}
-
-// TreeEntryNameCompare compares names using Git tree ordering rules.
-func TreeEntryNameCompare(entryName []byte, entryMode FileMode, searchName []byte, searchIsTree bool) int {
- isEntryTree := entryMode == FileModeDir
-
- entryLen := len(entryName)
- if isEntryTree {
- entryLen++
- }
-
- searchLen := len(searchName)
- if searchIsTree {
- searchLen++
- }
-
- n := min(searchLen, entryLen)
-
- for i := range n {
- var ec, sc byte
- if i < len(entryName) {
- ec = entryName[i]
- } else {
- ec = '/'
- }
-
- if i < len(searchName) {
- sc = searchName[i]
- } else {
- sc = '/'
- }
-
- if ec < sc {
- return -1
- }
-
- if ec > sc {
- return 1
- }
- }
-
- if entryLen < searchLen {
- return -1
- }
-
- if entryLen > searchLen {
- return 1
- }
-
- return 0
-}
diff --git a/object/tree/entry.go b/object/tree/entry.go
new file mode 100644
index 00000000..cddcde73
--- /dev/null
+++ b/object/tree/entry.go
@@ -0,0 +1,39 @@
+package tree
+
+import (
+ "bytes"
+
+ objectid "codeberg.org/lindenii/furgit/object/id"
+)
+
+// TreeEntry represents a single entry in a tree.
+type TreeEntry struct {
+ Mode FileMode
+ Name []byte
+ ID objectid.ObjectID
+}
+
+func (tree *Tree) entry(name []byte, searchIsTree bool) *TreeEntry {
+ low, high := 0, len(tree.Entries)-1
+ for low <= high {
+ mid := low + (high-low)/2
+ entry := &tree.Entries[mid]
+
+ cmp := TreeEntryNameCompare(entry.Name, entry.Mode, name, searchIsTree)
+ if cmp == 0 {
+ if bytes.Equal(entry.Name, name) {
+ return entry
+ }
+
+ return nil
+ }
+
+ if cmp < 0 {
+ low = mid + 1
+ } else {
+ high = mid - 1
+ }
+ }
+
+ return nil
+}
diff --git a/object/tree/helpers_test.go b/object/tree/helpers_test.go
new file mode 100644
index 00000000..3da92ce4
--- /dev/null
+++ b/object/tree/helpers_test.go
@@ -0,0 +1,114 @@
+package tree_test
+
+import (
+ "bytes"
+ "fmt"
+ "strings"
+ "testing"
+
+ "codeberg.org/lindenii/furgit/internal/testgit"
+ "codeberg.org/lindenii/furgit/object/tree"
+)
+
+func buildGitMktreeInput(entries []tree.TreeEntry) string {
+ var b strings.Builder
+ for _, e := range entries {
+ fmt.Fprintf(&b, "%o %s %s\t%s\n", e.Mode, mktreeTypeFromMode(e.Mode), e.ID.String(), e.Name)
+ }
+
+ return b.String()
+}
+
+func mktreeTypeFromMode(mode tree.FileMode) string {
+ switch mode {
+ case tree.FileModeDir:
+ return "tree"
+ case tree.FileModeRegular, tree.FileModeExecutable, tree.FileModeSymlink:
+ return "blob"
+ case tree.FileModeGitlink:
+ return "commit"
+ default:
+ return ""
+ }
+}
+
+func gitLsTreeNames(out []byte) [][]byte {
+ if len(out) == 0 {
+ return nil
+ }
+
+ parts := bytes.Split(out, []byte{0})
+ if len(parts) > 0 && len(parts[len(parts)-1]) == 0 {
+ parts = parts[:len(parts)-1]
+ }
+
+ names := make([][]byte, 0, len(parts))
+ for _, name := range parts {
+ names = append(names, append([]byte(nil), name...))
+ }
+
+ return names
+}
+
+func adversarialRootEntries(t *testing.T, testRepo *testgit.TestRepo) []tree.TreeEntry {
+ t.Helper()
+
+ blobA := testRepo.HashObject(t, "blob", []byte("blob-A\n"))
+ blobB := testRepo.HashObject(t, "blob", []byte("blob-B\n"))
+ blobC := testRepo.HashObject(t, "blob", []byte("blob-C\n"))
+
+ subDirA := testRepo.Mktree(t,
+ fmt.Sprintf("100644 blob %s\tnested-a.txt\n100755 blob %s\trun-a.sh\n", blobA.String(), blobB.String()))
+ subDirB := testRepo.Mktree(t,
+ fmt.Sprintf("100644 blob %s\tnested-b.txt\n100644 blob %s\tz-last\n", blobB.String(), blobC.String()))
+ subDirC := testRepo.Mktree(t,
+ fmt.Sprintf("120000 blob %s\tlink-c\n100644 blob %s\tchild\n", blobC.String(), blobA.String()))
+ subDirD := testRepo.Mktree(t,
+ fmt.Sprintf("100644 blob %s\tleaf\n", blobA.String()))
+
+ return []tree.TreeEntry{
+ {Mode: tree.FileModeRegular, Name: []byte("z"), ID: blobA},
+ {Mode: tree.FileModeRegular, Name: []byte("A"), ID: blobB},
+ {Mode: tree.FileModeRegular, Name: []byte("aa"), ID: blobC},
+ {Mode: tree.FileModeRegular, Name: []byte("a0"), ID: blobA},
+ {Mode: tree.FileModeRegular, Name: []byte("a-"), ID: blobB},
+ {Mode: tree.FileModeRegular, Name: []byte("a."), ID: blobC},
+ {Mode: tree.FileModeRegular, Name: []byte("a_"), ID: blobA},
+ {Mode: tree.FileModeRegular, Name: []byte("a~"), ID: blobB},
+ {Mode: tree.FileModeRegular, Name: []byte("Z"), ID: blobC},
+ {Mode: tree.FileModeRegular, Name: []byte("0"), ID: blobA},
+ {Mode: tree.FileModeRegular, Name: []byte("9"), ID: blobB},
+ {Mode: tree.FileModeRegular, Name: []byte("00"), ID: blobC},
+ {Mode: tree.FileModeRegular, Name: []byte("这是一些非 ASCII 的字符"), ID: blobC},
+ {Mode: tree.FileModeRegular, Name: []byte("𲰼是新进入 Unicode 的字符"), ID: blobC},
+ {Mode: tree.FileModeRegular, Name: []byte("Emoji 👀"), ID: blobC},
+ {Mode: tree.FileModeRegular, Name: []byte("_"), ID: blobA},
+ {Mode: tree.FileModeRegular, Name: []byte("-dash"), ID: blobB},
+ {Mode: tree.FileModeRegular, Name: []byte("dot.file"), ID: blobC},
+ {Mode: tree.FileModeRegular, Name: []byte(".hidden"), ID: blobA},
+ {Mode: tree.FileModeRegular, Name: []byte("CAPS"), ID: blobB},
+ {Mode: tree.FileModeRegular, Name: []byte("caps"), ID: blobC},
+ {Mode: tree.FileModeRegular, Name: []byte("mixCase"), ID: blobA},
+ {Mode: tree.FileModeRegular, Name: []byte("name with space"), ID: blobB},
+ {Mode: tree.FileModeRegular, Name: []byte("name-with-dash"), ID: blobC},
+ {Mode: tree.FileModeRegular, Name: []byte("name.with.dot"), ID: blobA},
+ {Mode: tree.FileModeRegular, Name: []byte("name_with_underscore"), ID: blobB},
+ {Mode: tree.FileModeRegular, Name: []byte("tilde~name"), ID: blobC},
+ {Mode: tree.FileModeRegular, Name: []byte("brace{name}"), ID: blobA},
+ {Mode: tree.FileModeRegular, Name: []byte("plus+name"), ID: blobB},
+ {Mode: tree.FileModeRegular, Name: []byte("equal=name"), ID: blobC},
+ {Mode: tree.FileModeRegular, Name: []byte("at@name"), ID: blobA},
+ {Mode: tree.FileModeRegular, Name: []byte("percent%name"), ID: blobB},
+ {Mode: tree.FileModeRegular, Name: []byte("caret^name"), ID: blobC},
+ {Mode: tree.FileModeRegular, Name: []byte("comma,name"), ID: blobA},
+ {Mode: tree.FileModeRegular, Name: []byte("semi;name"), ID: blobB},
+ {Mode: tree.FileModeRegular, Name: []byte("paren(name)"), ID: blobC},
+ {Mode: tree.FileModeRegular, Name: []byte("bracket[name]"), ID: blobA},
+ {Mode: tree.FileModeExecutable, Name: []byte("exec.sh"), ID: blobB},
+ {Mode: tree.FileModeSymlink, Name: []byte("sym.link"), ID: blobC},
+ {Mode: tree.FileModeDir, Name: []byte("dir"), ID: subDirA},
+ {Mode: tree.FileModeDir, Name: []byte("dir0"), ID: subDirB},
+ {Mode: tree.FileModeDir, Name: []byte("dir.space"), ID: subDirC},
+ {Mode: tree.FileModeDir, Name: []byte("x"), ID: subDirD},
+ }
+}
diff --git a/object/tree/insert.go b/object/tree/insert.go
new file mode 100644
index 00000000..bca4aa49
--- /dev/null
+++ b/object/tree/insert.go
@@ -0,0 +1,23 @@
+package tree
+
+import (
+ "fmt"
+ "sort"
+)
+
+// InsertEntry inserts a tree entry while preserving Git ordering.
+func (tree *Tree) InsertEntry(newEntry TreeEntry) error {
+ if tree.entry(newEntry.Name, true) != nil || tree.entry(newEntry.Name, false) != nil {
+ return fmt.Errorf("object: tree: entry %q already exists", newEntry.Name)
+ }
+
+ newIsTree := newEntry.Mode == FileModeDir
+ insertAt := sort.Search(len(tree.Entries), func(i int) bool {
+ return TreeEntryNameCompare(tree.Entries[i].Name, tree.Entries[i].Mode, newEntry.Name, newIsTree) >= 0
+ })
+ tree.Entries = append(tree.Entries, TreeEntry{})
+ copy(tree.Entries[insertAt+1:], tree.Entries[insertAt:])
+ tree.Entries[insertAt] = newEntry
+
+ return nil
+}
diff --git a/object/tree/lookup.go b/object/tree/lookup.go
new file mode 100644
index 00000000..957b31c4
--- /dev/null
+++ b/object/tree/lookup.go
@@ -0,0 +1,14 @@
+package tree
+
+// Entry looks up a tree entry by name.
+func (tree *Tree) Entry(name []byte) *TreeEntry {
+ if len(tree.Entries) == 0 {
+ return nil
+ }
+
+ if e := tree.entry(name, true); e != nil {
+ return e
+ }
+
+ return tree.entry(name, false)
+}
diff --git a/object/tree/mode.go b/object/tree/mode.go
new file mode 100644
index 00000000..b1cbc6bc
--- /dev/null
+++ b/object/tree/mode.go
@@ -0,0 +1,12 @@
+package tree
+
+// FileMode represents the mode of a file in a Git tree.
+type FileMode uint32
+
+const (
+ FileModeDir FileMode = 0o40000
+ FileModeRegular FileMode = 0o100644
+ FileModeExecutable FileMode = 0o100755
+ FileModeSymlink FileMode = 0o120000
+ FileModeGitlink FileMode = 0o160000
+)
diff --git a/object/tree/name.go b/object/tree/name.go
new file mode 100644
index 00000000..02af3292
--- /dev/null
+++ b/object/tree/name.go
@@ -0,0 +1,51 @@
+package tree
+
+// TreeEntryNameCompare compares names using Git tree ordering rules.
+func TreeEntryNameCompare(entryName []byte, entryMode FileMode, searchName []byte, searchIsTree bool) int {
+ isEntryTree := entryMode == FileModeDir
+
+ entryLen := len(entryName)
+ if isEntryTree {
+ entryLen++
+ }
+
+ searchLen := len(searchName)
+ if searchIsTree {
+ searchLen++
+ }
+
+ n := min(searchLen, entryLen)
+
+ for i := range n {
+ var ec, sc byte
+ if i < len(entryName) {
+ ec = entryName[i]
+ } else {
+ ec = '/'
+ }
+
+ if i < len(searchName) {
+ sc = searchName[i]
+ } else {
+ sc = '/'
+ }
+
+ if ec < sc {
+ return -1
+ }
+
+ if ec > sc {
+ return 1
+ }
+ }
+
+ if entryLen < searchLen {
+ return -1
+ }
+
+ if entryLen > searchLen {
+ return 1
+ }
+
+ return 0
+}
diff --git a/object/tree_parse.go b/object/tree/parse.go
index 944dc538..10bef968 100644
--- a/object/tree_parse.go
+++ b/object/tree/parse.go
@@ -1,4 +1,4 @@
-package object
+package tree
import (
"bytes"
@@ -8,8 +8,8 @@ import (
objectid "codeberg.org/lindenii/furgit/object/id"
)
-// ParseTree decodes a tree object body.
-func ParseTree(body []byte, algo objectid.Algorithm) (*Tree, error) {
+// Parse decodes a tree object body.
+func Parse(body []byte, algo objectid.Algorithm) (*Tree, error) {
var entries []TreeEntry
i := 0
diff --git a/object/tree_parse_test.go b/object/tree/parse_test.go
index 2e78243c..6f00220e 100644
--- a/object/tree_parse_test.go
+++ b/object/tree/parse_test.go
@@ -1,12 +1,12 @@
-package object_test
+package tree_test
import (
"bytes"
"testing"
"codeberg.org/lindenii/furgit/internal/testgit"
- "codeberg.org/lindenii/furgit/object"
objectid "codeberg.org/lindenii/furgit/object/id"
+ "codeberg.org/lindenii/furgit/object/tree"
)
func TestTreeParseFromGit(t *testing.T) {
@@ -15,7 +15,7 @@ func TestTreeParseFromGit(t *testing.T) {
testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
entries := adversarialRootEntries(t, testRepo)
- inserted := &object.Tree{}
+ inserted := &tree.Tree{}
for _, entry := range entries {
err := inserted.InsertEntry(entry)
if err != nil {
@@ -27,17 +27,17 @@ func TestTreeParseFromGit(t *testing.T) {
rawBody := testRepo.CatFile(t, "tree", treeID)
- tree, err := object.ParseTree(rawBody, algo)
+ parsed, err := tree.Parse(rawBody, algo)
if err != nil {
t.Fatalf("ParseTree: %v", err)
}
- if len(tree.Entries) != len(inserted.Entries) {
- t.Fatalf("entry count = %d, want %d", len(tree.Entries), len(inserted.Entries))
+ if len(parsed.Entries) != len(inserted.Entries) {
+ t.Fatalf("entry count = %d, want %d", len(parsed.Entries), len(inserted.Entries))
}
for i := range inserted.Entries {
- got := tree.Entries[i]
+ got := parsed.Entries[i]
want := inserted.Entries[i]
if got.Mode != want.Mode || got.ID != want.ID || !bytes.Equal(got.Name, want.Name) {
@@ -47,18 +47,18 @@ func TestTreeParseFromGit(t *testing.T) {
}
lsNames := gitLsTreeNames(testRepo.RunBytes(t, "ls-tree", "--name-only", "-z", treeID.String()))
- if len(lsNames) != len(tree.Entries) {
- t.Fatalf("ls-tree names = %d, want %d", len(lsNames), len(tree.Entries))
+ if len(lsNames) != len(parsed.Entries) {
+ t.Fatalf("ls-tree names = %d, want %d", len(lsNames), len(parsed.Entries))
}
for i := range lsNames {
- if !bytes.Equal(lsNames[i], tree.Entries[i].Name) {
- t.Fatalf("ordering mismatch at %d: git=%q parsed=%q", i, lsNames[i], tree.Entries[i].Name)
+ if !bytes.Equal(lsNames[i], parsed.Entries[i].Name) {
+ t.Fatalf("ordering mismatch at %d: git=%q parsed=%q", i, lsNames[i], parsed.Entries[i].Name)
}
}
for _, want := range inserted.Entries {
- got := tree.Entry(want.Name)
+ got := parsed.Entry(want.Name)
if got == nil {
t.Fatalf("Entry(%q) returned nil", want.Name)
@@ -69,7 +69,7 @@ func TestTreeParseFromGit(t *testing.T) {
}
}
- if tree.Entry([]byte("does-not-exist")) != nil {
+ if parsed.Entry([]byte("does-not-exist")) != nil {
t.Fatalf("Entry on missing name should be nil")
}
})
diff --git a/object/tree/remove.go b/object/tree/remove.go
new file mode 100644
index 00000000..9eb42028
--- /dev/null
+++ b/object/tree/remove.go
@@ -0,0 +1,24 @@
+package tree
+
+import (
+ "bytes"
+ "fmt"
+)
+
+// RemoveEntry removes a tree entry by name.
+func (tree *Tree) RemoveEntry(name []byte) error {
+ if len(tree.Entries) == 0 {
+ return fmt.Errorf("object: tree: entry %q not found", name)
+ }
+
+ for i := range tree.Entries {
+ if bytes.Equal(tree.Entries[i].Name, name) {
+ copy(tree.Entries[i:], tree.Entries[i+1:])
+ tree.Entries = tree.Entries[:len(tree.Entries)-1]
+
+ return nil
+ }
+ }
+
+ return fmt.Errorf("object: tree: entry %q not found", name)
+}
diff --git a/object/tree_serialize.go b/object/tree/serialize.go
index 849738a9..be31297b 100644
--- a/object/tree_serialize.go
+++ b/object/tree/serialize.go
@@ -1,4 +1,4 @@
-package object
+package tree
import (
"errors"
diff --git a/object/tree_serialize_test.go b/object/tree/serialize_test.go
index 26f8768e..9c9a2f1c 100644
--- a/object/tree_serialize_test.go
+++ b/object/tree/serialize_test.go
@@ -1,11 +1,11 @@
-package object_test
+package tree_test
import (
"testing"
"codeberg.org/lindenii/furgit/internal/testgit"
- "codeberg.org/lindenii/furgit/object"
objectid "codeberg.org/lindenii/furgit/object/id"
+ "codeberg.org/lindenii/furgit/object/tree"
)
func TestTreeSerialize(t *testing.T) {
@@ -13,54 +13,54 @@ func TestTreeSerialize(t *testing.T) {
testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
testRepo := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true})
entries := adversarialRootEntries(t, testRepo)
- tree := &object.Tree{}
+ obj := &tree.Tree{}
for i := len(entries) - 1; i >= 0; i-- {
- err := tree.InsertEntry(entries[i])
+ err := obj.InsertEntry(entries[i])
if err != nil {
t.Fatalf("InsertEntry(%q): %v", entries[i].Name, err)
}
}
- if len(tree.Entries) < 32 {
- t.Fatalf("expected at least 32 entries, got %d", len(tree.Entries))
+ if len(obj.Entries) < 32 {
+ t.Fatalf("expected at least 32 entries, got %d", len(obj.Entries))
}
- dup := tree.Entries[0]
+ dup := obj.Entries[0]
- err := tree.InsertEntry(dup)
+ err := obj.InsertEntry(dup)
if err == nil {
t.Fatalf("duplicate InsertEntry should fail")
}
- removed := tree.Entries[len(tree.Entries)/2]
+ removed := obj.Entries[len(obj.Entries)/2]
- err = tree.RemoveEntry(removed.Name)
+ err = obj.RemoveEntry(removed.Name)
if err != nil {
t.Fatalf("RemoveEntry(%q): %v", removed.Name, err)
}
- if tree.Entry(removed.Name) != nil {
+ if obj.Entry(removed.Name) != nil {
t.Fatalf("Entry(%q) should be nil after remove", removed.Name)
}
- err = tree.RemoveEntry([]byte("no-such-entry"))
+ err = obj.RemoveEntry([]byte("no-such-entry"))
if err == nil {
t.Fatalf("RemoveEntry missing entry should fail")
}
- err = tree.InsertEntry(removed)
+ err = obj.InsertEntry(removed)
if err != nil {
t.Fatalf("re-InsertEntry(%q): %v", removed.Name, err)
}
- if tree.Entry(removed.Name) == nil {
+ if obj.Entry(removed.Name) == nil {
t.Fatalf("Entry(%q) should exist after reinsert", removed.Name)
}
- wantTreeID := testRepo.Mktree(t, buildGitMktreeInput(tree.Entries))
+ wantTreeID := testRepo.Mktree(t, buildGitMktreeInput(obj.Entries))
- rawObj, err := tree.SerializeWithHeader()
+ rawObj, err := obj.SerializeWithHeader()
if err != nil {
t.Fatalf("SerializeWithHeader: %v", err)
}
diff --git a/object/tree/tree.go b/object/tree/tree.go
new file mode 100644
index 00000000..3ea6f1ee
--- /dev/null
+++ b/object/tree/tree.go
@@ -0,0 +1,7 @@
+// Package tree provides representations, parsers, and serializers for tree objects.
+package tree
+
+// Tree represents a Git tree object.
+type Tree struct {
+ Entries []TreeEntry
+}
diff --git a/object/tree/type.go b/object/tree/type.go
new file mode 100644
index 00000000..416544af
--- /dev/null
+++ b/object/tree/type.go
@@ -0,0 +1,10 @@
+package tree
+
+import objecttype "codeberg.org/lindenii/furgit/object/type"
+
+// ObjectType returns TypeTree.
+func (tree *Tree) ObjectType() objecttype.Type {
+ _ = tree
+
+ return objecttype.TypeTree
+}
diff --git a/object/tree_helpers_test.go b/object/tree_helpers_test.go
deleted file mode 100644
index 2577e0e1..00000000
--- a/object/tree_helpers_test.go
+++ /dev/null
@@ -1,114 +0,0 @@
-package object_test
-
-import (
- "bytes"
- "fmt"
- "strings"
- "testing"
-
- "codeberg.org/lindenii/furgit/internal/testgit"
- "codeberg.org/lindenii/furgit/object"
-)
-
-func buildGitMktreeInput(entries []object.TreeEntry) string {
- var b strings.Builder
- for _, e := range entries {
- fmt.Fprintf(&b, "%o %s %s\t%s\n", e.Mode, mktreeTypeFromMode(e.Mode), e.ID.String(), e.Name)
- }
-
- return b.String()
-}
-
-func mktreeTypeFromMode(mode object.FileMode) string {
- switch mode {
- case object.FileModeDir:
- return "tree"
- case object.FileModeRegular, object.FileModeExecutable, object.FileModeSymlink:
- return "blob"
- case object.FileModeGitlink:
- return "commit"
- default:
- return ""
- }
-}
-
-func gitLsTreeNames(out []byte) [][]byte {
- if len(out) == 0 {
- return nil
- }
-
- parts := bytes.Split(out, []byte{0})
- if len(parts) > 0 && len(parts[len(parts)-1]) == 0 {
- parts = parts[:len(parts)-1]
- }
-
- names := make([][]byte, 0, len(parts))
- for _, name := range parts {
- names = append(names, append([]byte(nil), name...))
- }
-
- return names
-}
-
-func adversarialRootEntries(t *testing.T, testRepo *testgit.TestRepo) []object.TreeEntry {
- t.Helper()
-
- blobA := testRepo.HashObject(t, "blob", []byte("blob-A\n"))
- blobB := testRepo.HashObject(t, "blob", []byte("blob-B\n"))
- blobC := testRepo.HashObject(t, "blob", []byte("blob-C\n"))
-
- subDirA := testRepo.Mktree(t,
- fmt.Sprintf("100644 blob %s\tnested-a.txt\n100755 blob %s\trun-a.sh\n", blobA.String(), blobB.String()))
- subDirB := testRepo.Mktree(t,
- fmt.Sprintf("100644 blob %s\tnested-b.txt\n100644 blob %s\tz-last\n", blobB.String(), blobC.String()))
- subDirC := testRepo.Mktree(t,
- fmt.Sprintf("120000 blob %s\tlink-c\n100644 blob %s\tchild\n", blobC.String(), blobA.String()))
- subDirD := testRepo.Mktree(t,
- fmt.Sprintf("100644 blob %s\tleaf\n", blobA.String()))
-
- return []object.TreeEntry{
- {Mode: object.FileModeRegular, Name: []byte("z"), ID: blobA},
- {Mode: object.FileModeRegular, Name: []byte("A"), ID: blobB},
- {Mode: object.FileModeRegular, Name: []byte("aa"), ID: blobC},
- {Mode: object.FileModeRegular, Name: []byte("a0"), ID: blobA},
- {Mode: object.FileModeRegular, Name: []byte("a-"), ID: blobB},
- {Mode: object.FileModeRegular, Name: []byte("a."), ID: blobC},
- {Mode: object.FileModeRegular, Name: []byte("a_"), ID: blobA},
- {Mode: object.FileModeRegular, Name: []byte("a~"), ID: blobB},
- {Mode: object.FileModeRegular, Name: []byte("Z"), ID: blobC},
- {Mode: object.FileModeRegular, Name: []byte("0"), ID: blobA},
- {Mode: object.FileModeRegular, Name: []byte("9"), ID: blobB},
- {Mode: object.FileModeRegular, Name: []byte("00"), ID: blobC},
- {Mode: object.FileModeRegular, Name: []byte("这是一些非 ASCII 的字符"), ID: blobC},
- {Mode: object.FileModeRegular, Name: []byte("𲰼是新进入 Unicode 的字符"), ID: blobC},
- {Mode: object.FileModeRegular, Name: []byte("Emoji 👀"), ID: blobC},
- {Mode: object.FileModeRegular, Name: []byte("_"), ID: blobA},
- {Mode: object.FileModeRegular, Name: []byte("-dash"), ID: blobB},
- {Mode: object.FileModeRegular, Name: []byte("dot.file"), ID: blobC},
- {Mode: object.FileModeRegular, Name: []byte(".hidden"), ID: blobA},
- {Mode: object.FileModeRegular, Name: []byte("CAPS"), ID: blobB},
- {Mode: object.FileModeRegular, Name: []byte("caps"), ID: blobC},
- {Mode: object.FileModeRegular, Name: []byte("mixCase"), ID: blobA},
- {Mode: object.FileModeRegular, Name: []byte("name with space"), ID: blobB},
- {Mode: object.FileModeRegular, Name: []byte("name-with-dash"), ID: blobC},
- {Mode: object.FileModeRegular, Name: []byte("name.with.dot"), ID: blobA},
- {Mode: object.FileModeRegular, Name: []byte("name_with_underscore"), ID: blobB},
- {Mode: object.FileModeRegular, Name: []byte("tilde~name"), ID: blobC},
- {Mode: object.FileModeRegular, Name: []byte("brace{name}"), ID: blobA},
- {Mode: object.FileModeRegular, Name: []byte("plus+name"), ID: blobB},
- {Mode: object.FileModeRegular, Name: []byte("equal=name"), ID: blobC},
- {Mode: object.FileModeRegular, Name: []byte("at@name"), ID: blobA},
- {Mode: object.FileModeRegular, Name: []byte("percent%name"), ID: blobB},
- {Mode: object.FileModeRegular, Name: []byte("caret^name"), ID: blobC},
- {Mode: object.FileModeRegular, Name: []byte("comma,name"), ID: blobA},
- {Mode: object.FileModeRegular, Name: []byte("semi;name"), ID: blobB},
- {Mode: object.FileModeRegular, Name: []byte("paren(name)"), ID: blobC},
- {Mode: object.FileModeRegular, Name: []byte("bracket[name]"), ID: blobA},
- {Mode: object.FileModeExecutable, Name: []byte("exec.sh"), ID: blobB},
- {Mode: object.FileModeSymlink, Name: []byte("sym.link"), ID: blobC},
- {Mode: object.FileModeDir, Name: []byte("dir"), ID: subDirA},
- {Mode: object.FileModeDir, Name: []byte("dir0"), ID: subDirB},
- {Mode: object.FileModeDir, Name: []byte("dir.space"), ID: subDirC},
- {Mode: object.FileModeDir, Name: []byte("x"), ID: subDirD},
- }
-}