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/packed{,/internal/ingest}: Move from format/packfile/ingest

Runxi Yu a4eeb727 11560391

+219 -256
-196
format/packfile/ingest/api.go
··· 1 - package ingest 2 - 3 - import ( 4 - "bufio" 5 - "bytes" 6 - "errors" 7 - "io" 8 - "os" 9 - 10 - "codeberg.org/lindenii/furgit/common/iowrap" 11 - objectid "codeberg.org/lindenii/furgit/object/id" 12 - objectstore "codeberg.org/lindenii/furgit/object/store" 13 - ) 14 - 15 - // Options controls one pack ingest operation. 16 - type Options struct { 17 - // FixThin appends missing local bases for thin packs. 18 - FixThin bool 19 - // WriteRev writes a .rev alongside the .pack and .idx. 20 - WriteRev bool 21 - // Base supplies existing objects for thin-pack fixup. 22 - Base objectstore.Reader 23 - // Progress receives human-readable progress messages. 24 - // 25 - // When nil, no progress output is emitted. 26 - Progress iowrap.WriteFlusher 27 - // RequireTrailingEOF requires the source to hit EOF after the pack trailer. 28 - // 29 - // This is suitable for exact pack-file readers, but should be disabled for 30 - // full-duplex transport streams like receive-pack where the peer keeps the 31 - // connection open to read the server response. 32 - RequireTrailingEOF bool 33 - } 34 - 35 - // Result describes one successful ingest transaction. 36 - type Result struct { 37 - // PackName is the destination-relative filename of the written .pack. 38 - PackName string 39 - // IdxName is the destination-relative filename of the written .idx. 40 - IdxName string 41 - // RevName is the destination-relative filename of the written .rev. 42 - // 43 - // RevName is empty when writeRev is false. 44 - RevName string 45 - // PackHash is the final pack hash (same hash embedded in .idx/.rev trailers). 46 - PackHash objectid.ObjectID 47 - // ObjectCount is the final object count in the resulting pack. 48 - // 49 - // If thin fixup appends objects, this includes appended base objects. 50 - ObjectCount uint32 51 - // ThinFixed reports whether thin fixup appended local bases. 52 - ThinFixed bool 53 - } 54 - 55 - // HeaderInfo describes the parsed PACK header. 56 - type HeaderInfo struct { 57 - Version uint32 58 - ObjectCount uint32 59 - } 60 - 61 - // DiscardResult describes one successful Discard call. 62 - type DiscardResult struct { 63 - PackHash objectid.ObjectID 64 - ObjectCount uint32 65 - } 66 - 67 - // Pending is one started ingest operation awaiting Continue or Discard. 68 - // 69 - // Exactly one of Continue or Discard may be called. 70 - // 71 - // Labels: MT-Unsafe. 72 - type Pending struct { 73 - reader *bufio.Reader 74 - algo objectid.Algorithm 75 - opts Options 76 - header HeaderInfo 77 - headerRaw [packHeaderSize]byte 78 - 79 - finalized bool 80 - } 81 - 82 - // Ingest reads and validates one PACK header, returning one pending operation. 83 - // 84 - // Labels: Deps-Borrowed, Life-Parent. 85 - func Ingest( 86 - src io.Reader, 87 - algo objectid.Algorithm, 88 - opts Options, 89 - ) (*Pending, error) { 90 - if algo.Size() == 0 { 91 - return nil, objectid.ErrInvalidAlgorithm 92 - } 93 - 94 - reader := bufio.NewReader(src) 95 - 96 - header, headerRaw, err := readAndValidatePackHeader(reader) 97 - if err != nil { 98 - return nil, err 99 - } 100 - 101 - return &Pending{ 102 - reader: reader, 103 - algo: algo, 104 - opts: opts, 105 - header: header, 106 - headerRaw: headerRaw, 107 - }, nil 108 - } 109 - 110 - // Header returns parsed PACK header info. 111 - func (pending *Pending) Header() HeaderInfo { 112 - return pending.header 113 - } 114 - 115 - // Continue ingests the pack stream into destination and writes pack artifacts. 116 - // 117 - // Continue invalidates the receiver. 118 - // 119 - // Artifacts are published under content-addressed final names derived from the 120 - // resulting pack hash. If those final names already exist, Continue treats that 121 - // as success and removes its temporary files. 122 - func (pending *Pending) Continue(destination *os.Root) (Result, error) { 123 - pending.finalized = true 124 - 125 - if pending.header.ObjectCount == 0 { 126 - return Result{}, ErrZeroObjectContinue 127 - } 128 - 129 - state, err := newIngestState( 130 - pending.reader, 131 - destination, 132 - pending.algo, 133 - pending.opts, 134 - pending.header, 135 - pending.headerRaw, 136 - ) 137 - if err != nil { 138 - return Result{}, err 139 - } 140 - 141 - return ingest(state) 142 - } 143 - 144 - // Discard consumes and verifies one zero-object pack stream without writing 145 - // files. 146 - // 147 - // Discard invalidates the receiver. 148 - func (pending *Pending) Discard() (DiscardResult, error) { 149 - pending.finalized = true 150 - 151 - if pending.header.ObjectCount != 0 { 152 - return DiscardResult{}, ErrNonZeroDiscard 153 - } 154 - 155 - hashImpl, err := pending.algo.New() 156 - if err != nil { 157 - return DiscardResult{}, err 158 - } 159 - 160 - _, _ = hashImpl.Write(pending.headerRaw[:]) 161 - 162 - trailer := make([]byte, pending.algo.Size()) 163 - 164 - _, err = io.ReadFull(pending.reader, trailer) 165 - if err != nil { 166 - return DiscardResult{}, &PackTrailerMismatchError{} 167 - } 168 - 169 - computed := hashImpl.Sum(nil) 170 - if !bytes.Equal(computed, trailer) { 171 - return DiscardResult{}, &PackTrailerMismatchError{} 172 - } 173 - 174 - if pending.opts.RequireTrailingEOF { 175 - var probe [1]byte 176 - 177 - n, err := pending.reader.Read(probe[:]) 178 - if n > 0 || err == nil { 179 - return DiscardResult{}, errors.New("packfile/ingest: pack has trailing garbage") 180 - } 181 - 182 - if err != io.EOF { 183 - return DiscardResult{}, err 184 - } 185 - } 186 - 187 - packHash, err := objectid.FromBytes(pending.algo, trailer) 188 - if err != nil { 189 - return DiscardResult{}, err 190 - } 191 - 192 - return DiscardResult{ 193 - PackHash: packHash, 194 - ObjectCount: 0, 195 - }, nil 196 - }
format/packfile/ingest/byteslice_reader.go object/store/packed/internal/ingest/byteslice_reader.go
format/packfile/ingest/cache.go object/store/packed/internal/ingest/cache.go
format/packfile/ingest/counting_writer.go object/store/packed/internal/ingest/counting_writer.go
format/packfile/ingest/crc.go object/store/packed/internal/ingest/crc.go
format/packfile/ingest/delta_header.go object/store/packed/internal/ingest/delta_header.go
format/packfile/ingest/distance.go object/store/packed/internal/ingest/distance.go
-3
format/packfile/ingest/doc.go
··· 1 - // Package ingest implements streaming ingestion of one Git pack stream into a 2 - // destination root, producing .pack/.idx and optionally .rev. 3 - package ingest
format/packfile/ingest/drain.go object/store/packed/internal/ingest/drain.go
format/packfile/ingest/entry.go object/store/packed/internal/ingest/entry.go
format/packfile/ingest/entry_header.go object/store/packed/internal/ingest/entry_header.go
format/packfile/ingest/entry_prefix.go object/store/packed/internal/ingest/entry_prefix.go
-7
format/packfile/ingest/errors.go object/store/packed/internal/ingest/errors.go
··· 66 66 } 67 67 68 68 var errExternalThinBase = errors.New("packfile/ingest: external thin base required") 69 - 70 - var ( 71 - // ErrZeroObjectContinue indicates Continue was called for a zero-object pack. 72 - ErrZeroObjectContinue = errors.New("packfile/ingest: cannot continue zero-object pack") 73 - // ErrNonZeroDiscard indicates Discard was called for a non-zero-object pack. 74 - ErrNonZeroDiscard = errors.New("packfile/ingest: cannot discard non-zero pack") 75 - )
format/packfile/ingest/file_section_writer.go object/store/packed/internal/ingest/file_section_writer.go
format/packfile/ingest/fill.go object/store/packed/internal/ingest/fill.go
format/packfile/ingest/finalize.go object/store/packed/internal/ingest/finalize.go
format/packfile/ingest/flush.go object/store/packed/internal/ingest/flush.go
format/packfile/ingest/hash.go object/store/packed/internal/ingest/hash.go
+12 -7
format/packfile/ingest/header.go object/store/packed/internal/ingest/header.go
··· 10 10 11 11 const packHeaderSize = 12 12 12 13 + type packHeader struct { 14 + Version uint32 15 + ObjectCount uint32 16 + } 17 + 13 18 // readAndValidatePackHeader reads one PACK header from src and validates it. 14 - func readAndValidatePackHeader(src io.Reader) (HeaderInfo, [packHeaderSize]byte, error) { 19 + func readAndValidatePackHeader(src io.Reader) (packHeader, [packHeaderSize]byte, error) { 15 20 var hdr [packHeaderSize]byte 16 21 17 22 _, err := io.ReadFull(src, hdr[:]) 18 23 if err != nil { 19 - return HeaderInfo{}, [packHeaderSize]byte{}, &InvalidPackHeaderError{ 24 + return packHeader{}, [packHeaderSize]byte{}, &InvalidPackHeaderError{ 20 25 Reason: fmt.Sprintf("read header: %v", err), 21 26 } 22 27 } 23 28 24 29 header, err := parseAndValidatePackHeader(hdr) 25 30 if err != nil { 26 - return HeaderInfo{}, [packHeaderSize]byte{}, err 31 + return packHeader{}, [packHeaderSize]byte{}, err 27 32 } 28 33 29 34 return header, hdr, nil 30 35 } 31 36 32 37 // parseAndValidatePackHeader validates one already-read PACK header. 33 - func parseAndValidatePackHeader(hdr [packHeaderSize]byte) (HeaderInfo, error) { 38 + func parseAndValidatePackHeader(hdr [packHeaderSize]byte) (packHeader, error) { 34 39 if binary.BigEndian.Uint32(hdr[:4]) != packfile.Signature { 35 - return HeaderInfo{}, &InvalidPackHeaderError{Reason: "signature mismatch"} 40 + return packHeader{}, &InvalidPackHeaderError{Reason: "signature mismatch"} 36 41 } 37 42 38 43 version := binary.BigEndian.Uint32(hdr[4:8]) 39 44 if !packfile.SupportedVersion(version) { 40 - return HeaderInfo{}, &InvalidPackHeaderError{ 45 + return packHeader{}, &InvalidPackHeaderError{ 41 46 Reason: fmt.Sprintf("unsupported version %d", version), 42 47 } 43 48 } 44 49 45 - return HeaderInfo{ 50 + return packHeader{ 46 51 Version: version, 47 52 ObjectCount: binary.BigEndian.Uint32(hdr[8:12]), 48 53 }, nil
format/packfile/ingest/idx_write.go object/store/packed/internal/ingest/idx_write.go
format/packfile/ingest/ingest.go object/store/packed/internal/ingest/ingest.go
+27 -40
format/packfile/ingest/ingest_test.go object/store/packed/internal/ingest/ingest_test.go
··· 11 11 "strings" 12 12 "testing" 13 13 14 - "codeberg.org/lindenii/furgit/format/packfile/ingest" 15 14 "codeberg.org/lindenii/furgit/internal/testgit" 16 15 objectid "codeberg.org/lindenii/furgit/object/id" 16 + "codeberg.org/lindenii/furgit/object/store/packed/internal/ingest" 17 17 ) 18 18 19 19 type noExtraReadReader struct { ··· 28 28 return r.reader.Read(p) 29 29 } 30 30 31 - func beginAndContinue( 31 + func writePack( 32 32 src io.Reader, 33 33 packRoot *os.Root, 34 34 algo objectid.Algorithm, 35 35 opts ingest.Options, 36 36 ) (ingest.Result, error) { 37 - pending, err := ingest.Ingest(src, algo, opts) 38 - if err != nil { 39 - return ingest.Result{}, err 40 - } 41 - 42 - return pending.Continue(packRoot) 37 + return ingest.WritePack(packRoot, algo, src, opts) 43 38 } 44 39 45 40 // fixturePath returns one fixture file path for the selected algorithm. ··· 189 184 190 185 packRoot := receiver.OpenPackRoot(t) 191 186 192 - result, err := beginAndContinue(bytes.NewReader(packBytes), packRoot, algo, ingest.Options{ 187 + result, err := writePack(bytes.NewReader(packBytes), packRoot, algo, ingest.Options{ 193 188 WriteRev: true, 194 189 RequireTrailingEOF: true, 195 190 }) ··· 237 232 receiver := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true}) 238 233 packRoot := receiver.OpenPackRoot(t) 239 234 240 - _, err := beginAndContinue(bytes.NewReader(thinPack), packRoot, algo, ingest.Options{ 235 + _, err := writePack(bytes.NewReader(thinPack), packRoot, algo, ingest.Options{ 241 236 WriteRev: true, 242 237 RequireTrailingEOF: true, 243 238 }) ··· 273 268 274 269 packRoot := receiver.OpenPackRoot(t) 275 270 276 - _, err := beginAndContinue(bytes.NewReader(basePack), packRoot, algo, ingest.Options{ 271 + _, err := writePack(bytes.NewReader(basePack), packRoot, algo, ingest.Options{ 277 272 RequireTrailingEOF: true, 278 273 }) 279 274 if err != nil { ··· 282 277 283 278 receiverRepo := receiver.OpenRepository(t) 284 279 285 - result, err := beginAndContinue(bytes.NewReader(thinPack), packRoot, algo, ingest.Options{ 280 + result, err := writePack(bytes.NewReader(thinPack), packRoot, algo, ingest.Options{ 286 281 FixThin: true, 287 282 WriteRev: true, 288 283 Base: receiverRepo.Objects(), ··· 317 312 receiver := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true}) 318 313 packRoot := receiver.OpenPackRoot(t) 319 314 320 - _, err := beginAndContinue(bytes.NewReader(packBytes), packRoot, algo, ingest.Options{ 315 + _, err := writePack(bytes.NewReader(packBytes), packRoot, algo, ingest.Options{ 321 316 WriteRev: true, 322 317 RequireTrailingEOF: true, 323 318 }) ··· 360 355 return append(header[:], hashImpl.Sum(nil)...) 361 356 } 362 357 363 - func TestIngestDiscardZeroObjectPack(t *testing.T) { 358 + func TestIngestZeroObjectPackIsDiscardedInternally(t *testing.T) { 364 359 t.Parallel() 365 360 366 361 testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper 367 362 packBytes := zeroObjectPackBytes(t, algo) 363 + receiver := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true}) 364 + packRoot := receiver.OpenPackRoot(t) 368 365 369 - pending, err := ingest.Ingest(bytes.NewReader(packBytes), algo, ingest.Options{ 366 + result, err := writePack(bytes.NewReader(packBytes), packRoot, algo, ingest.Options{ 370 367 RequireTrailingEOF: true, 371 368 }) 372 369 if err != nil { 373 - t.Fatalf("Ingest: %v", err) 370 + t.Fatalf("WritePack: %v", err) 374 371 } 375 372 376 - if pending.Header().ObjectCount != 0 { 377 - t.Fatalf("ObjectCount = %d, want 0", pending.Header().ObjectCount) 373 + if result.ObjectCount != 0 { 374 + t.Fatalf("ObjectCount = %d, want 0", result.ObjectCount) 378 375 } 379 376 380 - discarded, err := pending.Discard() 381 - if err != nil { 382 - t.Fatalf("Discard: %v", err) 377 + if result.PackName != "" { 378 + t.Fatalf("PackName = %q, want empty", result.PackName) 383 379 } 384 380 385 - if discarded.ObjectCount != 0 { 386 - t.Fatalf("Discard.ObjectCount = %d, want 0", discarded.ObjectCount) 381 + if result.IdxName != "" { 382 + t.Fatalf("IdxName = %q, want empty", result.IdxName) 387 383 } 388 - }) 389 - } 390 384 391 - func TestIngestContinueRejectsZeroObjectPack(t *testing.T) { 392 - t.Parallel() 385 + if result.RevName != "" { 386 + t.Fatalf("RevName = %q, want empty", result.RevName) 387 + } 393 388 394 - testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper 395 - packBytes := zeroObjectPackBytes(t, algo) 396 - receiver := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true}) 397 - packRoot := receiver.OpenPackRoot(t) 398 - 399 - pending, err := ingest.Ingest(bytes.NewReader(packBytes), algo, ingest.Options{ 400 - RequireTrailingEOF: true, 401 - }) 389 + entries, err := fs.ReadDir(packRoot.FS(), ".") 402 390 if err != nil { 403 - t.Fatalf("Ingest: %v", err) 391 + t.Fatalf("ReadDir(pack): %v", err) 404 392 } 405 393 406 - _, err = pending.Continue(packRoot) 407 - if !errors.Is(err, ingest.ErrZeroObjectContinue) { 408 - t.Fatalf("Continue error = %v, want ErrZeroObjectContinue", err) 394 + if len(entries) != 0 { 395 + t.Fatalf("unexpected files after zero-object pack: %d", len(entries)) 409 396 } 410 397 }) 411 398 } ··· 420 407 receiver := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: algo, Bare: true}) 421 408 packRoot := receiver.OpenPackRoot(t) 422 409 423 - result, err := beginAndContinue(&noExtraReadReader{reader: bytes.NewReader(packBytes)}, packRoot, algo, ingest.Options{ 410 + result, err := writePack(&noExtraReadReader{reader: bytes.NewReader(packBytes)}, packRoot, algo, ingest.Options{ 424 411 WriteRev: true, 425 412 }) 426 413 if err != nil {
format/packfile/ingest/progress_write.go object/store/packed/internal/ingest/progress_write.go
format/packfile/ingest/record_content.go object/store/packed/internal/ingest/record_content.go
format/packfile/ingest/record_delta.go object/store/packed/internal/ingest/record_delta.go
format/packfile/ingest/record_inflate.go object/store/packed/internal/ingest/record_inflate.go
format/packfile/ingest/record_resolve.go object/store/packed/internal/ingest/record_resolve.go
format/packfile/ingest/records.go object/store/packed/internal/ingest/records.go
format/packfile/ingest/resolve_all.go object/store/packed/internal/ingest/resolve_all.go
format/packfile/ingest/rev_write.go object/store/packed/internal/ingest/rev_write.go
format/packfile/ingest/rewrite_header_trailer.go object/store/packed/internal/ingest/rewrite_header_trailer.go
format/packfile/ingest/scan.go object/store/packed/internal/ingest/scan.go
+1 -1
format/packfile/ingest/state.go object/store/packed/internal/ingest/state.go
··· 49 49 destination *os.Root, 50 50 algo objectid.Algorithm, 51 51 opts Options, 52 - header HeaderInfo, 52 + header packHeader, 53 53 headerRaw [packHeaderSize]byte, 54 54 ) (*ingestState, error) { 55 55 if algo.Size() == 0 {
format/packfile/ingest/stream.go object/store/packed/internal/ingest/stream.go
format/packfile/ingest/temp.go object/store/packed/internal/ingest/temp.go
format/packfile/ingest/testdata/fixtures/sha1/METADATA.txt object/store/packed/internal/ingest/testdata/fixtures/sha1/METADATA.txt
format/packfile/ingest/testdata/fixtures/sha1/base.pack object/store/packed/internal/ingest/testdata/fixtures/sha1/base.pack
format/packfile/ingest/testdata/fixtures/sha1/nonthin.pack object/store/packed/internal/ingest/testdata/fixtures/sha1/nonthin.pack
format/packfile/ingest/testdata/fixtures/sha1/thin.pack object/store/packed/internal/ingest/testdata/fixtures/sha1/thin.pack
format/packfile/ingest/testdata/fixtures/sha256/METADATA.txt object/store/packed/internal/ingest/testdata/fixtures/sha256/METADATA.txt
format/packfile/ingest/testdata/fixtures/sha256/base.pack object/store/packed/internal/ingest/testdata/fixtures/sha256/base.pack
format/packfile/ingest/testdata/fixtures/sha256/nonthin.pack object/store/packed/internal/ingest/testdata/fixtures/sha256/nonthin.pack
format/packfile/ingest/testdata/fixtures/sha256/thin.pack object/store/packed/internal/ingest/testdata/fixtures/sha256/thin.pack
format/packfile/ingest/thin_append.go object/store/packed/internal/ingest/thin_append.go
format/packfile/ingest/thin_fix.go object/store/packed/internal/ingest/thin_fix.go
format/packfile/ingest/thin_unresolved.go object/store/packed/internal/ingest/thin_unresolved.go
format/packfile/ingest/trailer.go object/store/packed/internal/ingest/trailer.go
format/packfile/ingest/use.go object/store/packed/internal/ingest/use.go
+2 -2
object/store/packed/doc.go
··· 1 - // Package packed provides Git object reading from pack/index files under an 2 - // objects/pack directory. 1 + // Package packed provides Git object reading from, and pack writing to, 2 + // an objects/pack directory. 3 3 package packed
+3
object/store/packed/internal/ingest/doc.go
··· 1 + // Package ingest implements streaming ingestion of one Git pack stream into a 2 + // packed destination root, producing .pack/.idx and optionally .rev. 3 + package ingest
+26
object/store/packed/internal/ingest/options.go
··· 1 + package ingest 2 + 3 + import ( 4 + "codeberg.org/lindenii/furgit/common/iowrap" 5 + objectstore "codeberg.org/lindenii/furgit/object/store" 6 + ) 7 + 8 + // Options controls one pack ingest operation. 9 + type Options struct { 10 + // FixThin appends missing local bases for thin packs. 11 + FixThin bool 12 + // WriteRev writes a .rev alongside the .pack and .idx. 13 + WriteRev bool 14 + // Base supplies existing objects for thin-pack fixup. 15 + Base objectstore.Reader 16 + // Progress receives human-readable progress messages. 17 + // 18 + // When nil, no progress output is emitted. 19 + Progress iowrap.WriteFlusher 20 + // RequireTrailingEOF requires the source to hit EOF after the pack trailer. 21 + // 22 + // This is suitable for exact pack-file readers, but should be disabled for 23 + // full-duplex transport streams like receive-pack where the peer keeps the 24 + // connection open to read the server response. 25 + RequireTrailingEOF bool 26 + }
+23
object/store/packed/internal/ingest/result.go
··· 1 + package ingest 2 + 3 + import objectid "codeberg.org/lindenii/furgit/object/id" 4 + 5 + // Result describes one successful ingest transaction. 6 + type Result struct { 7 + // PackName is the destination-relative filename of the written .pack. 8 + PackName string 9 + // IdxName is the destination-relative filename of the written .idx. 10 + IdxName string 11 + // RevName is the destination-relative filename of the written .rev. 12 + // 13 + // RevName is empty when writeRev is false. 14 + RevName string 15 + // PackHash is the final pack hash (same hash embedded in .idx/.rev trailers). 16 + PackHash objectid.ObjectID 17 + // ObjectCount is the final object count in the resulting pack. 18 + // 19 + // If thin fixup appends objects, this includes appended base objects. 20 + ObjectCount uint32 21 + // ThinFixed reports whether thin fixup appended local bases. 22 + ThinFixed bool 23 + }
+50
object/store/packed/internal/ingest/write.go
··· 1 + package ingest 2 + 3 + import ( 4 + "bufio" 5 + "io" 6 + "os" 7 + 8 + objectid "codeberg.org/lindenii/furgit/object/id" 9 + ) 10 + 11 + // WritePack ingests one pack stream into destination and writes pack artifacts. 12 + // 13 + // Artifacts are published under content-addressed final names derived from the 14 + // resulting pack hash. If those final names already exist, WritePack treats 15 + // that as success and removes its temporary files. 16 + func WritePack( 17 + destination *os.Root, 18 + algo objectid.Algorithm, 19 + src io.Reader, 20 + opts Options, 21 + ) (Result, error) { 22 + if algo.Size() == 0 { 23 + return Result{}, objectid.ErrInvalidAlgorithm 24 + } 25 + 26 + reader := bufio.NewReader(src) 27 + 28 + header, headerRaw, err := readAndValidatePackHeader(reader) 29 + if err != nil { 30 + return Result{}, err 31 + } 32 + 33 + if header.ObjectCount == 0 { 34 + return discardZeroObjectPack(reader, algo, opts, headerRaw) 35 + } 36 + 37 + state, err := newIngestState( 38 + reader, 39 + destination, 40 + algo, 41 + opts, 42 + header, 43 + headerRaw, 44 + ) 45 + if err != nil { 46 + return Result{}, err 47 + } 48 + 49 + return ingest(state) 50 + }
+58
object/store/packed/internal/ingest/write_empty.go
··· 1 + package ingest 2 + 3 + import ( 4 + "bytes" 5 + "errors" 6 + "io" 7 + 8 + objectid "codeberg.org/lindenii/furgit/object/id" 9 + ) 10 + 11 + func discardZeroObjectPack( 12 + src io.Reader, 13 + algo objectid.Algorithm, 14 + opts Options, 15 + headerRaw [packHeaderSize]byte, 16 + ) (Result, error) { 17 + hashImpl, err := algo.New() 18 + if err != nil { 19 + return Result{}, err 20 + } 21 + 22 + _, _ = hashImpl.Write(headerRaw[:]) 23 + 24 + trailer := make([]byte, algo.Size()) 25 + 26 + _, err = io.ReadFull(src, trailer) 27 + if err != nil { 28 + return Result{}, &PackTrailerMismatchError{} 29 + } 30 + 31 + computed := hashImpl.Sum(nil) 32 + if !bytes.Equal(computed, trailer) { 33 + return Result{}, &PackTrailerMismatchError{} 34 + } 35 + 36 + if opts.RequireTrailingEOF { 37 + var probe [1]byte 38 + 39 + n, err := src.Read(probe[:]) 40 + if n > 0 || err == nil { 41 + return Result{}, errors.New("packfile/ingest: pack has trailing garbage") 42 + } 43 + 44 + if err != io.EOF { 45 + return Result{}, err 46 + } 47 + } 48 + 49 + packHash, err := objectid.FromBytes(algo, trailer) 50 + if err != nil { 51 + return Result{}, err 52 + } 53 + 54 + return Result{ 55 + PackHash: packHash, 56 + ObjectCount: 0, 57 + }, nil 58 + }
+17
object/store/packed/writer.go
··· 1 + package packed 2 + 3 + import ( 4 + "io" 5 + 6 + objectstore "codeberg.org/lindenii/furgit/object/store" 7 + "codeberg.org/lindenii/furgit/object/store/packed/internal/ingest" 8 + ) 9 + 10 + var _ objectstore.PackWriter = (*Store)(nil) 11 + 12 + // WritePack ingests one pack stream into the packed store. 13 + func (store *Store) WritePack(src io.Reader, _ objectstore.PackWriteOptions) error { 14 + _, err := ingest.WritePack(store.root, store.algo, src, ingest.Options{}) 15 + 16 + return err 17 + }