shithub: furgit

Download patch

ref: d951176970f646b1cc2e586f8b5fda31c93365c5
parent: ffd8a66b3a1dcf16c574af736b3582b0611d288e
author: Runxi Yu <me@runxiyu.org>
date: Sat Feb 21 14:00:01 EST 2026

objectstore/packed: Use constants/functions from format/pack

--- a/format/pack/entry.go
+++ b/format/pack/entry.go
@@ -46,3 +46,67 @@
 	}
 	return header, nil
 }
+
+// Entry is one parsed pack entry prefix, including any delta base reference
+// data that appears before the compressed payload.
+type Entry struct {
+	// Type is the pack entry type.
+	Type objecttype.Type
+	// Size is the declared resulting object size.
+	Size int64
+	// DataOffset is the byte offset from the start of the entry to the zlib
+	// payload bytes.
+	DataOffset int
+	// RefBaseID is the referenced base object ID bytes for ref-delta entries.
+	RefBaseID []byte
+	// OfsBaseDistance is the backward distance for ofs-delta entries.
+	OfsBaseDistance uint64
+}
+
+// ParseEntry parses one full pack entry prefix from data.
+//
+// hashSize must match the hash algorithm size used by the pack/index.
+func ParseEntry(data []byte, hashSize int) (Entry, error) {
+	var zero Entry
+
+	header, err := ParseEntryHeader(data)
+	if err != nil {
+		return zero, err
+	}
+	entry := Entry{
+		Type:       header.Type,
+		Size:       header.Size,
+		DataOffset: header.HeaderSize,
+	}
+
+	switch entry.Type {
+	case objecttype.TypeCommit, objecttype.TypeTree, objecttype.TypeBlob, objecttype.TypeTag:
+		// Base object entries have no extra prefix fields.
+	case objecttype.TypeRefDelta:
+		if hashSize <= 0 {
+			return zero, fmt.Errorf("format/pack: invalid hash size %d", hashSize)
+		}
+		end := entry.DataOffset + hashSize
+		if end > len(data) {
+			return zero, fmt.Errorf("format/pack: truncated ref-delta base id")
+		}
+		entry.RefBaseID = data[entry.DataOffset:end]
+		entry.DataOffset = end
+	case objecttype.TypeOfsDelta:
+		dist, consumed, err := ParseOfsDeltaDistance(data[entry.DataOffset:])
+		if err != nil {
+			return zero, err
+		}
+		entry.OfsBaseDistance = dist
+		entry.DataOffset += consumed
+	case objecttype.TypeInvalid, objecttype.TypeFuture:
+		return zero, fmt.Errorf("format/pack: unsupported object type %d", entry.Type)
+	default:
+		return zero, fmt.Errorf("format/pack: unsupported object type %d", entry.Type)
+	}
+
+	if entry.DataOffset > len(data) {
+		return zero, fmt.Errorf("format/pack: entry data offset out of bounds")
+	}
+	return entry, nil
+}
--- a/objectstore/packed/delta_base.go
+++ b/objectstore/packed/delta_base.go
@@ -3,6 +3,7 @@
 import (
 	"fmt"
 
+	packfmt "codeberg.org/lindenii/furgit/format/pack"
 	"codeberg.org/lindenii/furgit/objecttype"
 )
 
@@ -24,7 +25,7 @@
 	if err != nil {
 		return objecttype.TypeInvalid, nil, err
 	}
-	if !isBaseObjectType(meta.ty) {
+	if !packfmt.IsBaseObjectType(meta.ty) {
 		return objecttype.TypeInvalid, nil, fmt.Errorf("objectstore/packed: delta plan base is not a base object")
 	}
 	base, err := inflateAt(pack, meta.dataOffset, meta.size)
--- a/objectstore/packed/delta_plan.go
+++ b/objectstore/packed/delta_plan.go
@@ -4,6 +4,7 @@
 	"fmt"
 
 	deltaapply "codeberg.org/lindenii/furgit/format/delta/apply"
+	packfmt "codeberg.org/lindenii/furgit/format/pack"
 	"codeberg.org/lindenii/furgit/objecttype"
 )
 
@@ -46,7 +47,7 @@
 			return deltaPlan{}, err
 		}
 		if plan.declaredSize < 0 {
-			if isBaseObjectType(meta.ty) {
+			if packfmt.IsBaseObjectType(meta.ty) {
 				plan.declaredSize = meta.size
 			} else {
 				declaredSize, err := deltaDeclaredSizeAt(pack, meta.dataOffset)
@@ -57,7 +58,7 @@
 			}
 		}
 
-		if isBaseObjectType(meta.ty) {
+		if packfmt.IsBaseObjectType(meta.ty) {
 			plan.baseLoc = current
 			plan.baseType = meta.ty
 			return plan, nil
--- a/objectstore/packed/entry_parse.go
+++ b/objectstore/packed/entry_parse.go
@@ -3,6 +3,7 @@
 import (
 	"fmt"
 
+	packfmt "codeberg.org/lindenii/furgit/format/pack"
 	"codeberg.org/lindenii/furgit/internal/intconv"
 	"codeberg.org/lindenii/furgit/objectid"
 	"codeberg.org/lindenii/furgit/objecttype"
@@ -33,93 +34,28 @@
 	if err != nil {
 		return zero, fmt.Errorf("objectstore/packed: pack %q offset conversion: %w", pack.name, err)
 	}
-	first := pack.data[pos]
-	pos++
+	entry, err := packfmt.ParseEntry(pack.data[pos:], algo.Size())
+	if err != nil {
+		return zero, fmt.Errorf("objectstore/packed: pack %q: %w", pack.name, err)
+	}
 
 	meta := entryMeta{
-		ty:   objecttype.Type((first >> 4) & 0x07),
-		size: int64(first & 0x0f),
+		ty:         entry.Type,
+		size:       entry.Size,
+		dataOffset: pos + entry.DataOffset,
 	}
-
-	shift := uint(4)
-	b := first
-	for b&0x80 != 0 {
-		if pos >= len(pack.data) {
-			return zero, fmt.Errorf("objectstore/packed: pack %q truncated entry header", pack.name)
-		}
-		b = pack.data[pos]
-		pos++
-		meta.size |= int64(b&0x7f) << shift
-		shift += 7
-	}
-	if meta.size < 0 {
-		return zero, fmt.Errorf("objectstore/packed: pack %q entry has negative size", pack.name)
-	}
-
 	switch meta.ty {
-	case objecttype.TypeCommit, objecttype.TypeTree, objecttype.TypeBlob, objecttype.TypeTag:
-		// Base object entries have no extra header fields.
 	case objecttype.TypeRefDelta:
-		hashSize := algo.Size()
-		if pos+hashSize > len(pack.data) {
-			return zero, fmt.Errorf("objectstore/packed: pack %q truncated ref-delta base id", pack.name)
-		}
-		baseID, err := objectid.FromBytes(algo, pack.data[pos:pos+hashSize])
+		baseID, err := objectid.FromBytes(algo, entry.RefBaseID)
 		if err != nil {
-			return zero, err
+			return zero, fmt.Errorf("objectstore/packed: pack %q invalid ref-delta base id: %w", pack.name, err)
 		}
 		meta.baseRefID = baseID
-		pos += hashSize
 	case objecttype.TypeOfsDelta:
-		dist, consumed, err := parseOfsDeltaDistance(pack.data[pos:])
-		if err != nil {
-			return zero, err
-		}
-		pos += consumed
-		if offset <= dist {
+		if offset <= entry.OfsBaseDistance {
 			return zero, fmt.Errorf("objectstore/packed: pack %q has invalid ofs-delta base", pack.name)
 		}
-		meta.baseOfs = offset - dist
-	case objecttype.TypeInvalid, objecttype.TypeFuture:
-		return zero, fmt.Errorf("objectstore/packed: pack %q has unsupported object type %d", pack.name, meta.ty)
-	default:
-		return zero, fmt.Errorf("objectstore/packed: pack %q has unsupported object type %d", pack.name, meta.ty)
+		meta.baseOfs = offset - entry.OfsBaseDistance
 	}
-
-	meta.dataOffset = pos
-	if meta.dataOffset > len(pack.data) {
-		return zero, fmt.Errorf("objectstore/packed: pack %q entry data offset out of bounds", pack.name)
-	}
 	return meta, nil
-}
-
-// parseOfsDeltaDistance parses one ofs-delta backward distance.
-func parseOfsDeltaDistance(buf []byte) (uint64, int, error) {
-	if len(buf) == 0 {
-		return 0, 0, fmt.Errorf("objectstore/packed: malformed ofs-delta distance")
-	}
-	b := buf[0]
-	dist := uint64(b & 0x7f)
-	consumed := 1
-	for b&0x80 != 0 {
-		if consumed >= len(buf) {
-			return 0, 0, fmt.Errorf("objectstore/packed: malformed ofs-delta distance")
-		}
-		b = buf[consumed]
-		consumed++
-		dist = ((dist + 1) << 7) + uint64(b&0x7f)
-	}
-	return dist, consumed, nil
-}
-
-// isBaseObjectType reports whether ty is one of the four canonical object types.
-func isBaseObjectType(ty objecttype.Type) bool {
-	switch ty {
-	case objecttype.TypeCommit, objecttype.TypeTree, objecttype.TypeBlob, objecttype.TypeTag:
-		return true
-	case objecttype.TypeInvalid, objecttype.TypeFuture, objecttype.TypeOfsDelta, objecttype.TypeRefDelta:
-		return false
-	default:
-		return false
-	}
 }
--- a/objectstore/packed/pack.go
+++ b/objectstore/packed/pack.go
@@ -6,11 +6,10 @@
 	"os"
 	"syscall"
 
+	packfmt "codeberg.org/lindenii/furgit/format/pack"
 	"codeberg.org/lindenii/furgit/internal/intconv"
 )
 
-const packSignature = 0x5041434b
-
 // packFile stores one mapped and validated .pack file.
 type packFile struct {
 	// name is the .pack basename.
@@ -37,12 +36,12 @@
 	if err != nil {
 		return nil, err
 	}
-	if binary.BigEndian.Uint32(data[:4]) != packSignature {
+	if binary.BigEndian.Uint32(data[:4]) != packfmt.Signature {
 		_ = syscall.Munmap(data)
 		return nil, fmt.Errorf("objectstore/packed: pack %q invalid signature", name)
 	}
 	version := binary.BigEndian.Uint32(data[4:8])
-	if version != 2 && version != 3 {
+	if !packfmt.VersionSupported(version) {
 		_ = syscall.Munmap(data)
 		return nil, fmt.Errorf("objectstore/packed: pack %q unsupported version %d", name, version)
 	}
--- a/objectstore/packed/read_reader.go
+++ b/objectstore/packed/read_reader.go
@@ -5,6 +5,7 @@
 	"fmt"
 	"io"
 
+	packfmt "codeberg.org/lindenii/furgit/format/pack"
 	"codeberg.org/lindenii/furgit/internal/iolimit"
 	"codeberg.org/lindenii/furgit/objectheader"
 	"codeberg.org/lindenii/furgit/objectid"
@@ -40,7 +41,7 @@
 	if err != nil {
 		return objecttype.TypeInvalid, 0, nil, err
 	}
-	if isBaseObjectType(meta.ty) {
+	if packfmt.IsBaseObjectType(meta.ty) {
 		zr, err := zlibReaderAt(pack, meta.dataOffset)
 		if err != nil {
 			return objecttype.TypeInvalid, 0, nil, err
@@ -71,7 +72,7 @@
 	if err != nil {
 		return nil, err
 	}
-	if isBaseObjectType(meta.ty) {
+	if packfmt.IsBaseObjectType(meta.ty) {
 		header, ok := objectheader.Encode(meta.ty, meta.size)
 		if !ok {
 			return nil, fmt.Errorf("objectstore/packed: failed to encode object header for type %d", meta.ty)
--