package packfile import ( "compress/zlib" "crypto/sha1" "fmt" "io" "srcd.works/go-git.v4/plumbing" "srcd.works/go-git.v4/plumbing/storer" "srcd.works/go-git.v4/utils/binary" ) // Encoder gets the data from the storage and write it into the writer in PACK // format type Encoder struct { selector *deltaSelector w *offsetWriter zw *zlib.Writer hasher plumbing.Hasher offsets map[plumbing.Hash]int64 useRefDeltas bool } // NewEncoder creates a new packfile encoder using a specific Writer and // EncodedObjectStorer. By default deltas used to generate the packfile will be // OFSDeltaObject. To use Reference deltas, set useRefDeltas to true. func NewEncoder(w io.Writer, s storer.EncodedObjectStorer, useRefDeltas bool) *Encoder { h := plumbing.Hasher{ Hash: sha1.New(), } mw := io.MultiWriter(w, h) ow := newOffsetWriter(mw) zw := zlib.NewWriter(mw) return &Encoder{ selector: newDeltaSelector(s), w: ow, zw: zw, hasher: h, offsets: make(map[plumbing.Hash]int64), useRefDeltas: useRefDeltas, } } // Encode creates a packfile containing all the objects referenced in hashes // and writes it to the writer in the Encoder. func (e *Encoder) Encode(hashes []plumbing.Hash) (plumbing.Hash, error) { objects, err := e.selector.ObjectsToPack(hashes) if err != nil { return plumbing.ZeroHash, err } return e.encode(objects) } func (e *Encoder) encode(objects []*ObjectToPack) (plumbing.Hash, error) { if err := e.head(len(objects)); err != nil { return plumbing.ZeroHash, err } for _, o := range objects { if err := e.entry(o); err != nil { return plumbing.ZeroHash, err } } return e.footer() } func (e *Encoder) head(numEntries int) error { return binary.Write( e.w, signature, int32(VersionSupported), int32(numEntries), ) } func (e *Encoder) entry(o *ObjectToPack) error { offset := e.w.Offset() if o.IsDelta() { if err := e.writeDeltaHeader(o, offset); err != nil { return err } } else { if err := e.entryHead(o.Object.Type(), o.Object.Size()); err != nil { return err } } // Save the position using the original hash, maybe a delta will need it e.offsets[o.Original.Hash()] = offset e.zw.Reset(e.w) or, err := o.Object.Reader() if err != nil { return err } _, err = io.Copy(e.zw, or) if err != nil { return err } return e.zw.Close() } func (e *Encoder) writeDeltaHeader(o *ObjectToPack, offset int64) error { // Write offset deltas by default t := plumbing.OFSDeltaObject if e.useRefDeltas { t = plumbing.REFDeltaObject } if err := e.entryHead(t, o.Object.Size()); err != nil { return err } if e.useRefDeltas { return e.writeRefDeltaHeader(o.Base.Original.Hash()) } else { return e.writeOfsDeltaHeader(offset, o.Base.Original.Hash()) } } func (e *Encoder) writeRefDeltaHeader(base plumbing.Hash) error { return binary.Write(e.w, base) } func (e *Encoder) writeOfsDeltaHeader(deltaOffset int64, base plumbing.Hash) error { // because it is an offset delta, we need the base // object position offset, ok := e.offsets[base] if !ok { return fmt.Errorf("delta base not found. Hash: %v", base) } return binary.WriteVariableWidthInt(e.w, deltaOffset-offset) } func (e *Encoder) entryHead(typeNum plumbing.ObjectType, size int64) error { t := int64(typeNum) header := []byte{} c := (t << firstLengthBits) | (size & maskFirstLength) size >>= firstLengthBits for { if size == 0 { break } header = append(header, byte(c|maskContinue)) c = size & int64(maskLength) size >>= lengthBits } header = append(header, byte(c)) _, err := e.w.Write(header) return err } func (e *Encoder) footer() (plumbing.Hash, error) { h := e.hasher.Sum() return h, binary.Write(e.w, h) } type offsetWriter struct { w io.Writer offset int64 } func newOffsetWriter(w io.Writer) *offsetWriter { return &offsetWriter{w: w} } func (ow *offsetWriter) Write(p []byte) (n int, err error) { n, err = ow.w.Write(p) ow.offset += int64(n) return n, err } func (ow *offsetWriter) Offset() int64 { return ow.offset }