123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225 |
- package packfile
-
- import (
- "compress/zlib"
- "crypto/sha1"
- "fmt"
- "io"
-
- "github.com/go-git/go-git/v5/plumbing"
- "github.com/go-git/go-git/v5/plumbing/storer"
- "github.com/go-git/go-git/v5/utils/binary"
- "github.com/go-git/go-git/v5/utils/ioutil"
- )
-
- // 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
-
- 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,
- useRefDeltas: useRefDeltas,
- }
- }
-
- // Encode creates a packfile containing all the objects referenced in
- // hashes and writes it to the writer in the Encoder. `packWindow`
- // specifies the size of the sliding window used to compare objects
- // for delta compression; 0 turns off delta compression entirely.
- func (e *Encoder) Encode(
- hashes []plumbing.Hash,
- packWindow uint,
- ) (plumbing.Hash, error) {
- objects, err := e.selector.ObjectsToPack(hashes, packWindow)
- 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) (err error) {
- if o.WantWrite() {
- // A cycle exists in this delta chain. This should only occur if a
- // selected object representation disappeared during writing
- // (for example due to a concurrent repack) and a different base
- // was chosen, forcing a cycle. Select something other than a
- // delta, and write this object.
- e.selector.restoreOriginal(o)
- o.BackToOriginal()
- }
-
- if o.IsWritten() {
- return nil
- }
-
- o.MarkWantWrite()
-
- if err := e.writeBaseIfDelta(o); err != nil {
- return err
- }
-
- // We need to check if we already write that object due a cyclic delta chain
- if o.IsWritten() {
- return nil
- }
-
- o.Offset = e.w.Offset()
-
- if o.IsDelta() {
- if err := e.writeDeltaHeader(o); err != nil {
- return err
- }
- } else {
- if err := e.entryHead(o.Type(), o.Size()); err != nil {
- return err
- }
- }
-
- e.zw.Reset(e.w)
-
- defer ioutil.CheckClose(e.zw, &err)
-
- or, err := o.Object.Reader()
- if err != nil {
- return err
- }
-
- defer ioutil.CheckClose(or, &err)
-
- _, err = io.Copy(e.zw, or)
- if err != nil {
- return err
- }
-
- return nil
- }
-
- func (e *Encoder) writeBaseIfDelta(o *ObjectToPack) error {
- if o.IsDelta() && !o.Base.IsWritten() {
- // We must write base first
- return e.entry(o.Base)
- }
-
- return nil
- }
-
- func (e *Encoder) writeDeltaHeader(o *ObjectToPack) 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.Hash())
- } else {
- return e.writeOfsDeltaHeader(o)
- }
- }
-
- func (e *Encoder) writeRefDeltaHeader(base plumbing.Hash) error {
- return binary.Write(e.w, base)
- }
-
- func (e *Encoder) writeOfsDeltaHeader(o *ObjectToPack) error {
- // for OFS_DELTA, offset of the base is interpreted as negative offset
- // relative to the type-byte of the header of the ofs-delta entry.
- relativeOffset := o.Offset - o.Base.Offset
- if relativeOffset <= 0 {
- return fmt.Errorf("bad offset for OFS_DELTA entry: %d", relativeOffset)
- }
-
- return binary.WriteVariableWidthInt(e.w, relativeOffset)
- }
-
- 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
- }
|