From ab7501be34032fb9e5c48726a68ae90a917af9eb Mon Sep 17 00:00:00 2001 From: Runxi Yu Date: Wed, 4 Mar 2026 08:26:56 +0800 Subject: *: Lint --- object/blob.go | 1 + object/blob_parse_test.go | 2 ++ object/blob_serialize.go | 3 +++ object/blob_serialize_test.go | 2 ++ object/commit.go | 1 + object/commit_parse.go | 11 +++++++++++ object/commit_parse_test.go | 11 +++++++++++ object/commit_serialize.go | 10 ++++++++++ object/commit_serialize_test.go | 2 ++ object/ident.go | 18 ++++++++++++++++++ object/tag.go | 1 + object/tag_parse.go | 10 ++++++++++ object/tag_parse_test.go | 6 ++++++ object/tag_serialize.go | 6 ++++++ object/tag_serialize_test.go | 2 ++ object/tree.go | 18 ++++++++++++++++++ object/tree_helpers_test.go | 4 ++++ object/tree_parse.go | 5 +++++ object/tree_parse_test.go | 9 ++++++++- object/tree_serialize.go | 5 +++++ object/tree_serialize_test.go | 23 ++++++++++++++++++----- 21 files changed, 144 insertions(+), 6 deletions(-) (limited to 'object') 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) -- cgit v1.3.1-10-gc9f91