Fast implementation of Git in pure Go codeberg.org/lindenii/furgit
git go
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

object/store/memory: I guess implement the ObjectWriter interface

Runxi Yu 0fe27421 ce22af5e

+265 -8
+5 -8
object/store/memory/add.go
··· 1 1 package memory 2 2 3 3 import ( 4 - objectheader "codeberg.org/lindenii/furgit/object/header" 5 4 objectid "codeberg.org/lindenii/furgit/object/id" 6 5 objecttype "codeberg.org/lindenii/furgit/object/type" 7 6 ) 8 7 9 8 // AddObject stores one object body and returns its object ID. 9 + // 10 + //go:fix inline 10 11 func (store *Store) AddObject(ty objecttype.Type, body []byte) objectid.ObjectID { 11 - header, ok := objectheader.Encode(ty, int64(len(body))) 12 - if !ok { 13 - panic("failed to encode object header") 12 + id, err := store.WriteBytesContent(ty, body) 13 + if err != nil { 14 + panic(err) 14 15 } 15 - 16 - raw := append(append([]byte(nil), header...), body...) 17 - id := store.algo.Sum(raw) 18 - store.objects[id] = storedObject{ty: ty, content: append([]byte(nil), body...)} 19 16 20 17 return id 21 18 }
+35
object/store/memory/write_bytes.go
··· 1 + package memory 2 + 3 + import ( 4 + "bytes" 5 + 6 + objectheader "codeberg.org/lindenii/furgit/object/header" 7 + objectid "codeberg.org/lindenii/furgit/object/id" 8 + objecttype "codeberg.org/lindenii/furgit/object/type" 9 + ) 10 + 11 + // WriteBytesContent writes one typed object content byte slice. 12 + func (store *Store) WriteBytesContent(ty objecttype.Type, content []byte) (objectid.ObjectID, error) { 13 + id := store.algo.Sum(buildRawObject(ty, content)) 14 + store.objects[id] = storedObject{ty: ty, content: append([]byte(nil), content...)} 15 + 16 + return id, nil 17 + } 18 + 19 + // WriteBytesFull writes one full serialized object byte slice as "type size\0content". 20 + func (store *Store) WriteBytesFull(raw []byte) (objectid.ObjectID, error) { 21 + return store.WriteReaderFull(bytes.NewReader(raw)) 22 + } 23 + 24 + func buildRawObject(ty objecttype.Type, body []byte) []byte { 25 + header, ok := objectheader.Encode(ty, int64(len(body))) 26 + if !ok { 27 + panic("failed to encode object header") 28 + } 29 + 30 + raw := make([]byte, len(header)+len(body)) 31 + copy(raw, header) 32 + copy(raw[len(header):], body) 33 + 34 + return raw 35 + }
+55
object/store/memory/write_reader.go
··· 1 + package memory 2 + 3 + import ( 4 + "errors" 5 + "fmt" 6 + "io" 7 + 8 + objectheader "codeberg.org/lindenii/furgit/object/header" 9 + objectid "codeberg.org/lindenii/furgit/object/id" 10 + objecttype "codeberg.org/lindenii/furgit/object/type" 11 + ) 12 + 13 + // WriteReaderContent writes one typed object content stream. 14 + func (store *Store) WriteReaderContent(ty objecttype.Type, size int64, src io.Reader) (objectid.ObjectID, error) { 15 + if size < 0 { 16 + return objectid.ObjectID{}, fmt.Errorf("objectstore/memory: negative content size: %d", size) 17 + } 18 + 19 + content, err := io.ReadAll(io.LimitReader(src, size+1)) 20 + if err != nil { 21 + return objectid.ObjectID{}, err 22 + } 23 + 24 + switch { 25 + case int64(len(content)) > size: 26 + return objectid.ObjectID{}, errors.New("objectstore/memory: object content longer than declared size") 27 + case int64(len(content)) < size: 28 + return objectid.ObjectID{}, errors.New("objectstore/memory: object content shorter than declared size") 29 + } 30 + 31 + return store.WriteBytesContent(ty, content) 32 + } 33 + 34 + // WriteReaderFull writes one full serialized object stream as "type size\0content". 35 + func (store *Store) WriteReaderFull(src io.Reader) (objectid.ObjectID, error) { 36 + raw, err := io.ReadAll(src) 37 + if err != nil { 38 + return objectid.ObjectID{}, err 39 + } 40 + 41 + ty, size, headerLen, ok := objectheader.Parse(raw) 42 + if !ok { 43 + return objectid.ObjectID{}, errors.New("objectstore/memory: malformed object header") 44 + } 45 + 46 + content := raw[headerLen:] 47 + if int64(len(content)) != size { 48 + return objectid.ObjectID{}, errors.New("objectstore/memory: object header size/content mismatch") 49 + } 50 + 51 + id := store.algo.Sum(raw) 52 + store.objects[id] = storedObject{ty: ty, content: append([]byte(nil), content...)} 53 + 54 + return id, nil 55 + }
+170
object/store/memory/write_test.go
··· 1 + package memory 2 + 3 + import ( 4 + "bytes" 5 + "testing" 6 + 7 + "codeberg.org/lindenii/furgit/internal/testgit" 8 + objectheader "codeberg.org/lindenii/furgit/object/header" 9 + objectid "codeberg.org/lindenii/furgit/object/id" 10 + objecttype "codeberg.org/lindenii/furgit/object/type" 11 + ) 12 + 13 + func TestStoreWriteReaderContent(t *testing.T) { 14 + t.Parallel() 15 + testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper 16 + store := New(algo) 17 + content := []byte("memory-content\n") 18 + 19 + gotID, err := store.WriteReaderContent(objecttype.TypeBlob, int64(len(content)), bytes.NewReader(content)) 20 + if err != nil { 21 + t.Fatalf("WriteReaderContent: %v", err) 22 + } 23 + 24 + wantID := algo.Sum(buildRawObject(objecttype.TypeBlob, content)) 25 + if gotID != wantID { 26 + t.Fatalf("WriteReaderContent id = %s, want %s", gotID, wantID) 27 + } 28 + 29 + gotType, gotContent, err := store.ReadBytesContent(gotID) 30 + if err != nil { 31 + t.Fatalf("ReadBytesContent: %v", err) 32 + } 33 + 34 + if gotType != objecttype.TypeBlob { 35 + t.Fatalf("ReadBytesContent type = %v, want %v", gotType, objecttype.TypeBlob) 36 + } 37 + 38 + if !bytes.Equal(gotContent, content) { 39 + t.Fatalf("ReadBytesContent content mismatch") 40 + } 41 + }) 42 + } 43 + 44 + func TestStoreWriteReaderFull(t *testing.T) { 45 + t.Parallel() 46 + testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper 47 + store := New(algo) 48 + content := []byte("memory-full\n") 49 + raw := buildRawObject(objecttype.TypeBlob, content) 50 + 51 + gotID, err := store.WriteReaderFull(bytes.NewReader(raw)) 52 + if err != nil { 53 + t.Fatalf("WriteReaderFull: %v", err) 54 + } 55 + 56 + wantID := algo.Sum(raw) 57 + if gotID != wantID { 58 + t.Fatalf("WriteReaderFull id = %s, want %s", gotID, wantID) 59 + } 60 + 61 + gotRaw, err := store.ReadBytesFull(gotID) 62 + if err != nil { 63 + t.Fatalf("ReadBytesFull: %v", err) 64 + } 65 + 66 + if !bytes.Equal(gotRaw, raw) { 67 + t.Fatalf("ReadBytesFull mismatch") 68 + } 69 + }) 70 + } 71 + 72 + func TestStoreWriteBytes(t *testing.T) { 73 + t.Parallel() 74 + testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper 75 + store := New(algo) 76 + content := []byte("memory-bytes\n") 77 + 78 + gotID, err := store.WriteBytesContent(objecttype.TypeBlob, content) 79 + if err != nil { 80 + t.Fatalf("WriteBytesContent: %v", err) 81 + } 82 + 83 + wantID := algo.Sum(buildRawObject(objecttype.TypeBlob, content)) 84 + if gotID != wantID { 85 + t.Fatalf("WriteBytesContent id = %s, want %s", gotID, wantID) 86 + } 87 + 88 + raw := buildRawObject(objecttype.TypeBlob, content) 89 + 90 + gotID2, err := store.WriteBytesFull(raw) 91 + if err != nil { 92 + t.Fatalf("WriteBytesFull: %v", err) 93 + } 94 + 95 + if gotID2 != wantID { 96 + t.Fatalf("WriteBytesFull id = %s, want %s", gotID2, wantID) 97 + } 98 + }) 99 + } 100 + 101 + func TestStoreWriteReaderValidationErrors(t *testing.T) { 102 + t.Parallel() 103 + testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper 104 + t.Run("content overflow", func(t *testing.T) { 105 + t.Parallel() 106 + store := New(algo) 107 + 108 + _, err := store.WriteReaderContent(objecttype.TypeBlob, 1, bytes.NewReader([]byte("hello"))) 109 + if err == nil { 110 + t.Fatalf("expected error after overflow") 111 + } 112 + }) 113 + 114 + t.Run("content short", func(t *testing.T) { 115 + t.Parallel() 116 + store := New(algo) 117 + 118 + _, err := store.WriteReaderContent(objecttype.TypeBlob, 5, bytes.NewReader([]byte("x"))) 119 + if err == nil { 120 + t.Fatalf("expected error for short content") 121 + } 122 + }) 123 + 124 + t.Run("full malformed header", func(t *testing.T) { 125 + t.Parallel() 126 + store := New(algo) 127 + 128 + _, err := store.WriteReaderFull(bytes.NewReader([]byte("not-a-header"))) 129 + if err == nil { 130 + t.Fatalf("expected error for malformed header") 131 + } 132 + }) 133 + 134 + t.Run("full size mismatch", func(t *testing.T) { 135 + t.Parallel() 136 + store := New(algo) 137 + 138 + _, err := store.WriteReaderFull(bytes.NewReader([]byte("blob 1\x00hello"))) 139 + if err == nil { 140 + t.Fatalf("expected error after mismatch") 141 + } 142 + }) 143 + 144 + t.Run("bytes malformed header", func(t *testing.T) { 145 + t.Parallel() 146 + store := New(algo) 147 + 148 + _, err := store.WriteBytesFull([]byte("not-a-header")) 149 + if err == nil { 150 + t.Fatalf("expected error for malformed byte header") 151 + } 152 + }) 153 + }) 154 + } 155 + 156 + func TestBuildRawObjectMatchesObjectHeaderEncode(t *testing.T) { 157 + t.Parallel() 158 + 159 + content := []byte("body") 160 + raw := buildRawObject(objecttype.TypeBlob, content) 161 + header, ok := objectheader.Encode(objecttype.TypeBlob, int64(len(content))) 162 + if !ok { 163 + t.Fatalf("objectheader.Encode failed") 164 + } 165 + 166 + want := append(append([]byte(nil), header...), content...) 167 + if !bytes.Equal(raw, want) { 168 + t.Fatalf("buildRawObject mismatch") 169 + } 170 + }