diff options
Diffstat (limited to 'object')
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}, - } -} |
