ref: 9730560a82426408243cb15349f6955a4ba34f60
dir: /packed_write_pack.go/
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 := algo.New()
if err != nil {
return nil, err
}
return &packWriter{
w: w,
h: h,
algo: algo,
objCount: objCount,
}, nil
}
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
}