aboutsummaryrefslogtreecommitdiff
path: root/packed_write_pack.go
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-01-29 14:38:18 +0100
committerGravatar Runxi Yu2026-01-29 14:51:10 +0100
commit33de7fd28ce870d0b98016fcb42aa9ae5c0ca78a (patch)
tree06e2ef213cb07396bf59e979096df910ed477ede /packed_write_pack.go
parentpack: Harden pack writing test with 1000 1kb files (diff)
signatureNo signature
packed: More uniform file naming scheme
Diffstat (limited to 'packed_write_pack.go')
-rw-r--r--packed_write_pack.go273
1 files changed, 273 insertions, 0 deletions
diff --git a/packed_write_pack.go b/packed_write_pack.go
new file mode 100644
index 00000000..435c9edb
--- /dev/null
+++ b/packed_write_pack.go
@@ -0,0 +1,273 @@
+package furgit
+
+import (
+ "crypto/sha1"
+ "crypto/sha256"
+ "encoding/binary"
+ "errors"
+ "hash"
+ "io"
+
+ "codeberg.org/lindenii/furgit/internal/zlib"
+)
+
+// TODO
+var errPackDeltaUnimplemented = errors.New("furgit: pack: delta writing not implemented")
+
+// packWriter writes a PACKv2 stream.
+type packWriter struct {
+ w io.Writer
+ h hash.Hash
+ algo hashAlgorithm
+ objCount uint32
+ wroteHeader bool
+ bytesWritten uint64
+}
+
+func newPackWriter(w io.Writer, algo hashAlgorithm, objCount uint32) (*packWriter, error) {
+ if w == nil {
+ return nil, ErrInvalidObject
+ }
+ h, err := newHashWriter(algo)
+ if err != nil {
+ return nil, err
+ }
+ return &packWriter{
+ w: w,
+ h: h,
+ algo: algo,
+ objCount: objCount,
+ }, nil
+}
+
+func newHashWriter(algo hashAlgorithm) (hash.Hash, error) {
+ switch algo {
+ case hashAlgoSHA1:
+ return sha1.New(), nil
+ case hashAlgoSHA256:
+ return sha256.New(), nil
+ default:
+ return nil, ErrInvalidObject
+ }
+}
+
+func (pw *packWriter) writePacked(p []byte) error {
+ if len(p) == 0 {
+ return nil
+ }
+ n, err := pw.w.Write(p)
+ if n > 0 {
+ _, _ = pw.h.Write(p[:n])
+ pw.bytesWritten += uint64(n)
+ }
+ if err != nil {
+ return err
+ }
+ if n != len(p) {
+ return io.ErrShortWrite
+ }
+ return nil
+}
+
+func (pw *packWriter) WriteHeader() error {
+ if pw == nil || pw.wroteHeader {
+ return ErrInvalidObject
+ }
+ var hdr [12]byte
+ binary.BigEndian.PutUint32(hdr[0:4], packMagic)
+ binary.BigEndian.PutUint32(hdr[4:8], packVersion2)
+ binary.BigEndian.PutUint32(hdr[8:12], pw.objCount)
+ if err := pw.writePacked(hdr[:]); err != nil {
+ return err
+ }
+ pw.wroteHeader = true
+ return nil
+}
+
+func (pw *packWriter) WriteObject(ty ObjectType, body []byte) error {
+ if pw == nil || !pw.wroteHeader {
+ return ErrInvalidObject
+ }
+ switch ty {
+ case ObjectTypeCommit, ObjectTypeTree, ObjectTypeBlob, ObjectTypeTag:
+ // remember that go switches don't fallthrough lol
+ default:
+ return ErrInvalidObject
+ }
+ if body == nil {
+ body = []byte{}
+ }
+
+ hdr, err := packHeaderEncode(ty, len(body))
+ if err != nil {
+ return err
+ }
+ if err := pw.writePacked(hdr); err != nil {
+ return err
+ }
+
+ zw := zlib.NewWriter(&packHashWriter{pw: pw})
+ if _, err := zw.Write(body); err != nil {
+ _ = zw.Close()
+ return err
+ }
+ return zw.Close()
+}
+
+func (pw *packWriter) WriteOfsDelta(baseOffset uint64, baseSize, resultSize int, delta []byte) error {
+ _ = baseOffset
+ _ = baseSize
+ _ = resultSize
+ _ = delta
+ return errPackDeltaUnimplemented
+}
+
+func (pw *packWriter) WriteRefDelta(base Hash, baseSize, resultSize int, delta []byte) error {
+ _ = base
+ _ = baseSize
+ _ = resultSize
+ _ = delta
+ return errPackDeltaUnimplemented
+}
+
+func (pw *packWriter) Close() (Hash, error) {
+ if pw == nil || !pw.wroteHeader {
+ return Hash{}, ErrInvalidObject
+ }
+ sum := pw.h.Sum(nil)
+ if _, err := pw.w.Write(sum); err != nil {
+ return Hash{}, err
+ }
+ var out Hash
+ copy(out.data[:], sum)
+ out.algo = pw.algo
+ return out, nil
+}
+
+type packHashWriter struct {
+ pw *packWriter
+}
+
+func (w *packHashWriter) Write(p []byte) (int, error) {
+ if w == nil || w.pw == nil {
+ return 0, ErrInvalidObject
+ }
+ if err := w.pw.writePacked(p); err != nil {
+ return 0, err
+ }
+ return len(p), nil
+}
+
+// packHeaderEncode encodes a pack object header (type + size).
+func packHeaderEncode(ty ObjectType, size int) ([]byte, error) {
+ if size < 0 {
+ return nil, ErrInvalidObject
+ }
+ var out [16]byte
+ pos := 0
+
+ b := byte(size & 0x0f)
+ size >>= 4
+ b |= byte(ty&0x07) << 4
+ if size > 0 {
+ b |= 0x80
+ }
+ out[pos] = b
+ pos++
+
+ for size > 0 {
+ b = byte(size & 0x7f)
+ size >>= 7
+ if size > 0 {
+ b |= 0x80
+ }
+ out[pos] = b
+ pos++
+ }
+
+ return out[:pos], nil
+}
+
+// packVarintEncode encodes a 7-bit varint.
+func packVarintEncode(size int) ([]byte, error) {
+ if size < 0 {
+ return nil, ErrInvalidObject
+ }
+ var out [16]byte
+ pos := 0
+ for {
+ b := byte(size & 0x7f)
+ size >>= 7
+ if size != 0 {
+ b |= 0x80
+ }
+ out[pos] = b
+ pos++
+ if size == 0 {
+ break
+ }
+ }
+ return out[:pos], nil
+}
+
+// packOfsEncode encodes an ofs-delta distance.
+func packOfsEncode(dist uint64) ([]byte, error) {
+ if dist == 0 {
+ return nil, ErrInvalidObject
+ }
+ var out [16]byte
+ pos := 0
+ out[pos] = byte(dist & 0x7f)
+ pos++
+ dist >>= 7
+ for dist != 0 {
+ b := byte((dist - 1) & 0x7f)
+ out[pos] = b | 0x80
+ pos++
+ dist >>= 7
+ }
+ for i, j := 0, pos-1; i < j; i, j = i+1, j-1 {
+ out[i], out[j] = out[j], out[i]
+ }
+ return out[:pos], nil
+}
+
+// packWrite writes a pack stream for the provided object ids.
+func (repo *Repository) packWrite(w io.Writer, objects []Hash, opts packWriteOptions) (Hash, error) {
+ if repo == nil {
+ return Hash{}, ErrInvalidObject
+ }
+ if opts.EnableDeltas || opts.EnableThinPack {
+ return Hash{}, errPackDeltaUnimplemented
+ }
+ if len(objects) > int(^uint32(0)) {
+ return Hash{}, ErrInvalidObject
+ }
+
+ pw, err := newPackWriter(w, repo.hashAlgo, uint32(len(objects)))
+ if err != nil {
+ return Hash{}, err
+ }
+ if err := pw.WriteHeader(); err != nil {
+ return Hash{}, err
+ }
+
+ for _, id := range objects {
+ ty, body, err := repo.ReadObjectTypeRaw(id)
+ if err != nil {
+ return Hash{}, err
+ }
+ if err := pw.WriteObject(ty, body); err != nil {
+ return Hash{}, err
+ }
+ }
+
+ return pw.Close()
+}
+
+type packWriteOptions struct {
+ EnableDeltas bool
+ EnableThinPack bool
+ MinDeltaSavings int
+ MaxDeltaDepth int
+}