aboutsummaryrefslogtreecommitdiff
path: root/object
diff options
context:
space:
mode:
Diffstat (limited to 'object')
-rw-r--r--object/blob.go1
-rw-r--r--object/blob_parse_test.go2
-rw-r--r--object/blob_serialize.go3
-rw-r--r--object/blob_serialize_test.go2
-rw-r--r--object/commit.go1
-rw-r--r--object/commit_parse.go11
-rw-r--r--object/commit_parse_test.go11
-rw-r--r--object/commit_serialize.go10
-rw-r--r--object/commit_serialize_test.go2
-rw-r--r--object/ident.go18
-rw-r--r--object/tag.go1
-rw-r--r--object/tag_parse.go10
-rw-r--r--object/tag_parse_test.go6
-rw-r--r--object/tag_serialize.go6
-rw-r--r--object/tag_serialize_test.go2
-rw-r--r--object/tree.go18
-rw-r--r--object/tree_helpers_test.go4
-rw-r--r--object/tree_parse.go5
-rw-r--r--object/tree_parse_test.go9
-rw-r--r--object/tree_serialize.go5
-rw-r--r--object/tree_serialize_test.go23
21 files changed, 144 insertions, 6 deletions
diff --git a/object/blob.go b/object/blob.go
index 8f094405..9c507e1f 100644
--- a/object/blob.go
+++ b/object/blob.go
@@ -10,5 +10,6 @@ type Blob struct {
// ObjectType returns TypeBlob.
func (blob *Blob) ObjectType() objecttype.Type {
_ = blob
+
return objecttype.TypeBlob
}
diff --git a/object/blob_parse_test.go b/object/blob_parse_test.go
index 7b242ef7..1cf3990f 100644
--- a/object/blob_parse_test.go
+++ b/object/blob_parse_test.go
@@ -17,10 +17,12 @@ func TestBlobParseFromGit(t *testing.T) {
blobID := testRepo.HashObject(t, "blob", body)
rawBody := testRepo.CatFile(t, "blob", blobID)
+
blob, err := object.ParseBlob(rawBody)
if err != nil {
t.Fatalf("ParseBlob: %v", err)
}
+
if !bytes.Equal(blob.Data, body) {
t.Fatalf("blob body mismatch")
}
diff --git a/object/blob_serialize.go b/object/blob_serialize.go
index 70354ddc..e9c0ac5e 100644
--- a/object/blob_serialize.go
+++ b/object/blob_serialize.go
@@ -18,12 +18,15 @@ func (blob *Blob) SerializeWithHeader() ([]byte, error) {
if err != nil {
return nil, err
}
+
header, ok := objectheader.Encode(objecttype.TypeBlob, int64(len(body)))
if !ok {
return nil, errors.New("object: blob: failed to encode object header")
}
+
raw := make([]byte, len(header)+len(body))
copy(raw, header)
copy(raw[len(header):], body)
+
return raw, nil
}
diff --git a/object/blob_serialize_test.go b/object/blob_serialize_test.go
index c49815da..69dbe849 100644
--- a/object/blob_serialize_test.go
+++ b/object/blob_serialize_test.go
@@ -16,10 +16,12 @@ func TestBlobSerialize(t *testing.T) {
wantID := testRepo.HashObject(t, "blob", body)
blob := &object.Blob{Data: body}
+
rawObj, err := blob.SerializeWithHeader()
if err != nil {
t.Fatalf("SerializeWithHeader: %v", err)
}
+
gotID := algo.Sum(rawObj)
if gotID != wantID {
t.Fatalf("object id mismatch: got %s want %s", gotID, wantID)
diff --git a/object/commit.go b/object/commit.go
index bd48bb44..34e89033 100644
--- a/object/commit.go
+++ b/object/commit.go
@@ -19,5 +19,6 @@ type Commit struct {
// ObjectType returns TypeCommit.
func (commit *Commit) ObjectType() objecttype.Type {
_ = commit
+
return objecttype.TypeCommit
}
diff --git a/object/commit_parse.go b/object/commit_parse.go
index ae1b2559..31e215de 100644
--- a/object/commit_parse.go
+++ b/object/commit_parse.go
@@ -11,14 +11,17 @@ import (
// ParseCommit decodes a commit object body.
func ParseCommit(body []byte, algo objectid.Algorithm) (*Commit, error) {
c := new(Commit)
+
i := 0
for i < len(body) {
rel := bytes.IndexByte(body[i:], '\n')
if rel < 0 {
return nil, errors.New("object: commit: missing newline")
}
+
line := body[i : i+rel]
i += rel + 1
+
if len(line) == 0 {
break
}
@@ -34,24 +37,28 @@ func ParseCommit(body []byte, algo objectid.Algorithm) (*Commit, error) {
if err != nil {
return nil, fmt.Errorf("object: commit: tree: %w", err)
}
+
c.Tree = id
case "parent":
id, err := objectid.ParseHex(algo, string(value))
if err != nil {
return nil, fmt.Errorf("object: commit: parent: %w", err)
}
+
c.Parents = append(c.Parents, id)
case "author":
idt, err := ParseSignature(value)
if err != nil {
return nil, fmt.Errorf("object: commit: author: %w", err)
}
+
c.Author = *idt
case "committer":
idt, err := ParseSignature(value)
if err != nil {
return nil, fmt.Errorf("object: commit: committer: %w", err)
}
+
c.Committer = *idt
case "change-id":
c.ChangeID = string(value)
@@ -61,9 +68,11 @@ func ParseCommit(body []byte, algo objectid.Algorithm) (*Commit, error) {
if nextRel < 0 {
return nil, errors.New("object: commit: unterminated gpgsig")
}
+
if body[i] != ' ' {
break
}
+
i += nextRel + 1
}
default:
@@ -77,6 +86,8 @@ func ParseCommit(body []byte, algo objectid.Algorithm) (*Commit, error) {
if i > len(body) {
return nil, errors.New("object: commit: parser position out of bounds")
}
+
c.Message = append([]byte(nil), body[i:]...)
+
return c, nil
}
diff --git a/object/commit_parse_test.go b/object/commit_parse_test.go
index a29ab1fa..4dc1dea1 100644
--- a/object/commit_parse_test.go
+++ b/object/commit_parse_test.go
@@ -17,22 +17,28 @@ func TestCommitParseFromGit(t *testing.T) {
_, treeID, commitID := testRepo.MakeCommit(t, "subject\n\nbody")
rawBody := testRepo.CatFile(t, "commit", commitID)
+
commit, err := object.ParseCommit(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 len(commit.Parents) != 0 {
t.Fatalf("parent count = %d, want 0", len(commit.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(commit.Committer.Name, []byte("Test Committer")) {
t.Fatalf("committer name = %q, want %q", commit.Committer.Name, "Test Committer")
}
+
if !bytes.Contains(commit.Message, []byte("subject")) {
t.Fatalf("commit message missing subject: %q", commit.Message)
}
@@ -61,18 +67,23 @@ func TestCommitParseMultipleParents(t *testing.T) {
if err != nil {
t.Fatalf("ParseCommit(merge): %v", err)
}
+
if commit.Tree != treeID {
t.Fatalf("merge tree = %s, want %s", commit.Tree, treeID)
}
+
if len(commit.Parents) != 2 {
t.Fatalf("merge parent count = %d, want 2", len(commit.Parents))
}
+
if commit.Parents[0] != parent1 {
t.Fatalf("merge parent[0] = %s, want %s", commit.Parents[0], parent1)
}
+
if commit.Parents[1] != parent2 {
t.Fatalf("merge parent[1] = %s, want %s", commit.Parents[1], parent2)
}
+
if !bytes.Equal(commit.Message, []byte("Merge commit\n")) {
t.Fatalf("merge message = %q, want %q", commit.Message, "Merge commit\n")
}
diff --git a/object/commit_serialize.go b/object/commit_serialize.go
index ec28aded..eef45ef4 100644
--- a/object/commit_serialize.go
+++ b/object/commit_serialize.go
@@ -16,7 +16,9 @@ func (commit *Commit) SerializeWithoutHeader() ([]byte, error) {
if commit.Tree.Size() == 0 {
return nil, errors.New("object: commit: missing tree id")
}
+
fmt.Fprintf(&buf, "tree %s\n", commit.Tree.String())
+
for _, parent := range commit.Parents {
fmt.Fprintf(&buf, "parent %s\n", parent.String())
}
@@ -25,6 +27,7 @@ func (commit *Commit) SerializeWithoutHeader() ([]byte, error) {
if err != nil {
return nil, err
}
+
buf.WriteString("author ")
buf.Write(authorBytes)
buf.WriteByte('\n')
@@ -33,6 +36,7 @@ func (commit *Commit) SerializeWithoutHeader() ([]byte, error) {
if err != nil {
return nil, err
}
+
buf.WriteString("committer ")
buf.Write(committerBytes)
buf.WriteByte('\n')
@@ -42,10 +46,12 @@ func (commit *Commit) SerializeWithoutHeader() ([]byte, error) {
buf.WriteString(commit.ChangeID)
buf.WriteByte('\n')
}
+
for _, h := range commit.ExtraHeaders {
if h.Key == "" {
return nil, errors.New("object: commit: extra header has empty key")
}
+
buf.WriteString(h.Key)
buf.WriteByte(' ')
buf.Write(h.Value)
@@ -54,6 +60,7 @@ func (commit *Commit) SerializeWithoutHeader() ([]byte, error) {
buf.WriteByte('\n')
buf.Write(commit.Message)
+
return buf.Bytes(), nil
}
@@ -63,12 +70,15 @@ func (commit *Commit) SerializeWithHeader() ([]byte, error) {
if err != nil {
return nil, err
}
+
header, ok := objectheader.Encode(objecttype.TypeCommit, int64(len(body)))
if !ok {
return nil, errors.New("object: commit: failed to encode object header")
}
+
raw := make([]byte, len(header)+len(body))
copy(raw, header)
copy(raw[len(header):], body)
+
return raw, nil
}
diff --git a/object/commit_serialize_test.go b/object/commit_serialize_test.go
index 4f9856b0..70b3fc92 100644
--- a/object/commit_serialize_test.go
+++ b/object/commit_serialize_test.go
@@ -15,6 +15,7 @@ func TestCommitSerialize(t *testing.T) {
_, _, commitID := testRepo.MakeCommit(t, "subject\n\nbody")
rawBody := testRepo.CatFile(t, "commit", commitID)
+
commit, err := object.ParseCommit(rawBody, algo)
if err != nil {
t.Fatalf("ParseCommit: %v", err)
@@ -24,6 +25,7 @@ func TestCommitSerialize(t *testing.T) {
if err != nil {
t.Fatalf("SerializeWithHeader: %v", err)
}
+
gotID := algo.Sum(rawObj)
if gotID != commitID {
t.Fatalf("commit id mismatch: got %s want %s", gotID, commitID)
diff --git a/object/ident.go b/object/ident.go
index 1ea55cc2..049b0c01 100644
--- a/object/ident.go
+++ b/object/ident.go
@@ -26,10 +26,12 @@ func ParseSignature(line []byte) (*Signature, error) {
if lt < 0 {
return nil, errors.New("object: signature: missing opening <")
}
+
gtRel := bytes.IndexByte(line[lt+1:], '>')
if gtRel < 0 {
return nil, errors.New("object: signature: missing closing >")
}
+
gt := lt + 1 + gtRel
nameBytes := append([]byte(nil), bytes.TrimRight(line[:lt], " ")...)
@@ -39,11 +41,14 @@ func ParseSignature(line []byte) (*Signature, error) {
if len(rest) == 0 || rest[0] != ' ' {
return nil, errors.New("object: signature: missing timestamp separator")
}
+
rest = rest[1:]
+
before, after, ok := bytes.Cut(rest, []byte{' '})
if !ok {
return nil, errors.New("object: signature: missing timezone separator")
}
+
when, err := strconv.ParseInt(string(before), 10, 64)
if err != nil {
return nil, fmt.Errorf("object: signature: invalid timestamp: %w", err)
@@ -53,7 +58,9 @@ func ParseSignature(line []byte) (*Signature, error) {
if len(tz) < 5 {
return nil, errors.New("object: signature: invalid timezone encoding")
}
+
sign := 1
+
switch tz[0] {
case '-':
sign = -1
@@ -66,24 +73,31 @@ func ParseSignature(line []byte) (*Signature, error) {
if err != nil {
return nil, fmt.Errorf("object: signature: invalid timezone hours: %w", err)
}
+
mm, err := strconv.Atoi(string(tz[3:5]))
if err != nil {
return nil, fmt.Errorf("object: signature: invalid timezone minutes: %w", err)
}
+
if hh < 0 || hh > 23 {
return nil, errors.New("object: signature: invalid timezone hours range")
}
+
if mm < 0 || mm > 59 {
return nil, errors.New("object: signature: invalid timezone minutes range")
}
+
total := int64(hh)*60 + int64(mm)
+
offset, err := intconv.Int64ToInt32(total)
if err != nil {
return nil, errors.New("object: signature: timezone overflow")
}
+
if sign < 0 {
offset = -offset
}
+
return &Signature{
Name: nameBytes,
Email: emailBytes,
@@ -104,19 +118,23 @@ func (signature Signature) Serialize() ([]byte, error) {
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/tag.go b/object/tag.go
index 9a621ec9..0da3e4a8 100644
--- a/object/tag.go
+++ b/object/tag.go
@@ -17,5 +17,6 @@ type Tag struct {
// ObjectType returns TypeTag.
func (tag *Tag) ObjectType() objecttype.Type {
_ = tag
+
return objecttype.TypeTag
}
diff --git a/object/tag_parse.go b/object/tag_parse.go
index ea194085..c2fee81a 100644
--- a/object/tag_parse.go
+++ b/object/tag_parse.go
@@ -13,6 +13,7 @@ import (
func ParseTag(body []byte, algo objectid.Algorithm) (*Tag, error) {
t := new(Tag)
i := 0
+
var haveTarget, haveType bool
for i < len(body) {
@@ -20,8 +21,10 @@ func ParseTag(body []byte, algo objectid.Algorithm) (*Tag, error) {
if rel < 0 {
return nil, errors.New("object: tag: missing newline")
}
+
line := body[i : i+rel]
i += rel + 1
+
if len(line) == 0 {
break
}
@@ -37,6 +40,7 @@ func ParseTag(body []byte, algo objectid.Algorithm) (*Tag, error) {
if err != nil {
return nil, fmt.Errorf("object: tag: object: %w", err)
}
+
t.Target = id
haveTarget = true
case "type":
@@ -44,6 +48,7 @@ func ParseTag(body []byte, algo objectid.Algorithm) (*Tag, error) {
if !ok {
return nil, errors.New("object: tag: unknown target type")
}
+
t.TargetType = ty
haveType = true
case "tag":
@@ -53,6 +58,7 @@ func ParseTag(body []byte, algo objectid.Algorithm) (*Tag, error) {
if err != nil {
return nil, fmt.Errorf("object: tag: tagger: %w", err)
}
+
t.Tagger = idt
case "gpgsig", "gpgsig-sha256":
for i < len(body) {
@@ -60,9 +66,11 @@ func ParseTag(body []byte, algo objectid.Algorithm) (*Tag, error) {
if nextRel < 0 {
return nil, errors.New("object: tag: unterminated gpgsig")
}
+
if body[i] != ' ' {
break
}
+
i += nextRel + 1
}
default:
@@ -73,6 +81,8 @@ func ParseTag(body []byte, algo objectid.Algorithm) (*Tag, error) {
if !haveTarget || !haveType {
return nil, errors.New("object: tag: missing required headers")
}
+
t.Message = append([]byte(nil), body[i:]...)
+
return t, nil
}
diff --git a/object/tag_parse_test.go b/object/tag_parse_test.go
index 7ddb60e9..456d2f63 100644
--- a/object/tag_parse_test.go
+++ b/object/tag_parse_test.go
@@ -18,22 +18,28 @@ func TestTagParseFromGit(t *testing.T) {
tagID := testRepo.TagAnnotated(t, "v1", commitID, "tag message")
rawBody := testRepo.CatFile(t, "tag", tagID)
+
tag, err := object.ParseTag(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 tag.TargetType != objecttype.TypeCommit {
t.Fatalf("tag target type = %v, want %v", tag.TargetType, objecttype.TypeCommit)
}
+
if !bytes.Equal(tag.Name, []byte("v1")) {
t.Fatalf("tag name = %q, want %q", tag.Name, "v1")
}
+
if tag.Tagger == nil {
t.Fatalf("expected tagger")
}
+
if !bytes.Contains(tag.Message, []byte("tag message")) {
t.Fatalf("tag message mismatch: %q", tag.Message)
}
diff --git a/object/tag_serialize.go b/object/tag_serialize.go
index 9ccf0bd0..1e016cdb 100644
--- a/object/tag_serialize.go
+++ b/object/tag_serialize.go
@@ -22,6 +22,7 @@ func (tag *Tag) SerializeWithoutHeader() ([]byte, error) {
if !ok {
return nil, fmt.Errorf("object: tag: invalid target type %d", tag.TargetType)
}
+
buf.WriteString("type ")
buf.WriteString(tyName)
buf.WriteByte('\n')
@@ -35,6 +36,7 @@ func (tag *Tag) SerializeWithoutHeader() ([]byte, error) {
if err != nil {
return nil, err
}
+
buf.WriteString("tagger ")
buf.Write(taggerBytes)
buf.WriteByte('\n')
@@ -42,6 +44,7 @@ func (tag *Tag) SerializeWithoutHeader() ([]byte, error) {
buf.WriteByte('\n')
buf.Write(tag.Message)
+
return buf.Bytes(), nil
}
@@ -51,12 +54,15 @@ func (tag *Tag) SerializeWithHeader() ([]byte, error) {
if err != nil {
return nil, err
}
+
header, ok := objectheader.Encode(objecttype.TypeTag, int64(len(body)))
if !ok {
return nil, errors.New("object: tag: failed to encode object header")
}
+
raw := make([]byte, len(header)+len(body))
copy(raw, header)
copy(raw[len(header):], body)
+
return raw, nil
}
diff --git a/object/tag_serialize_test.go b/object/tag_serialize_test.go
index 1b3ea2f8..e1bdbab2 100644
--- a/object/tag_serialize_test.go
+++ b/object/tag_serialize_test.go
@@ -16,6 +16,7 @@ func TestTagSerialize(t *testing.T) {
tagID := testRepo.TagAnnotated(t, "v1", commitID, "tag message")
rawBody := testRepo.CatFile(t, "tag", tagID)
+
tag, err := object.ParseTag(rawBody, algo)
if err != nil {
t.Fatalf("ParseTag: %v", err)
@@ -25,6 +26,7 @@ func TestTagSerialize(t *testing.T) {
if err != nil {
t.Fatalf("SerializeWithHeader: %v", err)
}
+
gotID := algo.Sum(rawObj)
if gotID != tagID {
t.Fatalf("tag id mismatch: got %s want %s", gotID, tagID)
diff --git a/object/tree.go b/object/tree.go
index 4bb459be..ad4b8f34 100644
--- a/object/tree.go
+++ b/object/tree.go
@@ -35,6 +35,7 @@ type Tree struct {
// ObjectType returns TypeTree.
func (tree *Tree) ObjectType() objecttype.Type {
_ = tree
+
return objecttype.TypeTree
}
@@ -43,9 +44,11 @@ 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)
}
@@ -54,6 +57,7 @@ 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
@@ -61,6 +65,7 @@ func (tree *Tree) InsertEntry(newEntry TreeEntry) error {
tree.Entries = append(tree.Entries, TreeEntry{})
copy(tree.Entries[insertAt+1:], tree.Entries[insertAt:])
tree.Entries[insertAt] = newEntry
+
return nil
}
@@ -69,13 +74,16 @@ 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)
}
@@ -84,19 +92,23 @@ func (tree *Tree) entry(name []byte, searchIsTree bool) *TreeEntry {
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
}
@@ -108,6 +120,7 @@ func TreeEntryNameCompare(entryName []byte, entryMode FileMode, searchName []byt
if isEntryTree {
entryLen++
}
+
searchLen := len(searchName)
if searchIsTree {
searchLen++
@@ -122,14 +135,17 @@ func TreeEntryNameCompare(entryName []byte, entryMode FileMode, searchName []byt
} else {
ec = '/'
}
+
if i < len(searchName) {
sc = searchName[i]
} else {
sc = '/'
}
+
if ec < sc {
return -1
}
+
if ec > sc {
return 1
}
@@ -138,8 +154,10 @@ func TreeEntryNameCompare(entryName []byte, entryMode FileMode, searchName []byt
if entryLen < searchLen {
return -1
}
+
if entryLen > searchLen {
return 1
}
+
return 0
}
diff --git a/object/tree_helpers_test.go b/object/tree_helpers_test.go
index 4727e1c7..2577e0e1 100644
--- a/object/tree_helpers_test.go
+++ b/object/tree_helpers_test.go
@@ -15,6 +15,7 @@ func buildGitMktreeInput(entries []object.TreeEntry) string {
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()
}
@@ -35,14 +36,17 @@ 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
}
diff --git a/object/tree_parse.go b/object/tree_parse.go
index 37a2fa4b..dd4faa8b 100644
--- a/object/tree_parse.go
+++ b/object/tree_parse.go
@@ -11,12 +11,14 @@ import (
// ParseTree decodes a tree object body.
func ParseTree(body []byte, algo objectid.Algorithm) (*Tree, error) {
var entries []TreeEntry
+
i := 0
for i < len(body) {
space := bytes.IndexByte(body[i:], ' ')
if space < 0 {
return nil, fmt.Errorf("object: tree: missing mode terminator")
}
+
modeBytes := body[i : i+space]
i += space + 1
@@ -24,6 +26,7 @@ func ParseTree(body []byte, algo objectid.Algorithm) (*Tree, error) {
if nul < 0 {
return nil, fmt.Errorf("object: tree: missing name terminator")
}
+
nameBytes := body[i : i+nul]
i += nul + 1
@@ -31,10 +34,12 @@ func ParseTree(body []byte, algo objectid.Algorithm) (*Tree, error) {
if idEnd > len(body) {
return nil, fmt.Errorf("object: tree: truncated child object id")
}
+
id, err := objectid.FromBytes(algo, body[i:idEnd])
if err != nil {
return nil, err
}
+
i = idEnd
mode, err := strconv.ParseUint(string(modeBytes), 8, 32)
diff --git a/object/tree_parse_test.go b/object/tree_parse_test.go
index 989d6ff1..d4b7c1e6 100644
--- a/object/tree_parse_test.go
+++ b/object/tree_parse_test.go
@@ -14,9 +14,11 @@ func TestTreeParseFromGit(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)
+
inserted := &object.Tree{}
for _, entry := range entries {
- if err := inserted.InsertEntry(entry); err != nil {
+ err := inserted.InsertEntry(entry)
+ if err != nil {
t.Fatalf("InsertEntry(%q): %v", entry.Name, err)
}
}
@@ -24,16 +26,19 @@ func TestTreeParseFromGit(t *testing.T) {
treeID := testRepo.Mktree(t, buildGitMktreeInput(inserted.Entries))
rawBody := testRepo.CatFile(t, "tree", treeID)
+
tree, err := object.ParseTree(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))
}
for i := range inserted.Entries {
got := tree.Entries[i]
+
want := inserted.Entries[i]
if got.Mode != want.Mode || got.ID != want.ID || !bytes.Equal(got.Name, want.Name) {
t.Fatalf("entry[%d] mismatch: got (%o,%q,%s) want (%o,%q,%s)",
@@ -45,6 +50,7 @@ func TestTreeParseFromGit(t *testing.T) {
if len(lsNames) != len(tree.Entries) {
t.Fatalf("ls-tree names = %d, want %d", len(lsNames), len(tree.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)
@@ -62,6 +68,7 @@ func TestTreeParseFromGit(t *testing.T) {
t.Fatalf("Entry(%q) mismatch", want.Name)
}
}
+
if tree.Entry([]byte("does-not-exist")) != nil {
t.Fatalf("Entry on missing name should be nil")
}
diff --git a/object/tree_serialize.go b/object/tree_serialize.go
index 5c10bef6..42f60f72 100644
--- a/object/tree_serialize.go
+++ b/object/tree_serialize.go
@@ -11,6 +11,7 @@ import (
// SerializeWithoutHeader renders the raw tree body bytes.
func (tree *Tree) SerializeWithoutHeader() ([]byte, error) {
var bodyLen int
+
for _, entry := range tree.Entries {
mode := strconv.FormatUint(uint64(entry.Mode), 8)
bodyLen += len(mode) + 1 + len(entry.Name) + 1 + entry.ID.Size()
@@ -18,6 +19,7 @@ func (tree *Tree) SerializeWithoutHeader() ([]byte, error) {
body := make([]byte, bodyLen)
pos := 0
+
for _, entry := range tree.Entries {
mode := strconv.FormatUint(uint64(entry.Mode), 8)
pos += copy(body[pos:], mode)
@@ -39,12 +41,15 @@ func (tree *Tree) SerializeWithHeader() ([]byte, error) {
if err != nil {
return nil, err
}
+
header, ok := objectheader.Encode(objecttype.TypeTree, int64(len(body)))
if !ok {
return nil, errors.New("object: tree: failed to encode object header")
}
+
raw := make([]byte, len(header)+len(body))
copy(raw, header)
copy(raw[len(header):], body)
+
return raw, nil
}
diff --git a/object/tree_serialize_test.go b/object/tree_serialize_test.go
index e8ebb140..c038ad58 100644
--- a/object/tree_serialize_test.go
+++ b/object/tree_serialize_test.go
@@ -16,32 +16,44 @@ func TestTreeSerialize(t *testing.T) {
tree := &object.Tree{}
for i := len(entries) - 1; i >= 0; i-- {
- if err := tree.InsertEntry(entries[i]); err != nil {
+ err := tree.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))
}
dup := tree.Entries[0]
- if err := tree.InsertEntry(dup); err == nil {
+
+ err := tree.InsertEntry(dup)
+ if err == nil {
t.Fatalf("duplicate InsertEntry should fail")
}
removed := tree.Entries[len(tree.Entries)/2]
- if err := tree.RemoveEntry(removed.Name); err != nil {
+
+ err = tree.RemoveEntry(removed.Name)
+ if err != nil {
t.Fatalf("RemoveEntry(%q): %v", removed.Name, err)
}
+
if tree.Entry(removed.Name) != nil {
t.Fatalf("Entry(%q) should be nil after remove", removed.Name)
}
- if err := tree.RemoveEntry([]byte("no-such-entry")); err == nil {
+
+ err = tree.RemoveEntry([]byte("no-such-entry"))
+ if err == nil {
t.Fatalf("RemoveEntry missing entry should fail")
}
- if err := tree.InsertEntry(removed); err != nil {
+
+ err = tree.InsertEntry(removed)
+ if err != nil {
t.Fatalf("re-InsertEntry(%q): %v", removed.Name, err)
}
+
if tree.Entry(removed.Name) == nil {
t.Fatalf("Entry(%q) should exist after reinsert", removed.Name)
}
@@ -52,6 +64,7 @@ func TestTreeSerialize(t *testing.T) {
if err != nil {
t.Fatalf("SerializeWithHeader: %v", err)
}
+
gotTreeID := algo.Sum(rawObj)
if gotTreeID != wantTreeID {
t.Fatalf("tree id mismatch: got %s want %s", gotTreeID, wantTreeID)