github.com/driusan/dgit@v0.0.0-20221118233547-f39f0c15edbb/git/packobjects.go (about)

     1  package git
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     6  	"log"
     7  	//"os"
     8  	//"runtime/pprof"
     9  
    10  	"compress/zlib"
    11  	"crypto/sha1"
    12  	"encoding/binary"
    13  	"index/suffixarray"
    14  
    15  	"github.com/driusan/dgit/git/delta"
    16  )
    17  
    18  type PackObjectsOptions struct {
    19  	// The number of entries to use for the sliding window for delta
    20  	// calculations
    21  	Window int
    22  
    23  	// Use offset deltas instead of refdeltas when calculating delta
    24  	DeltaBaseOffset bool
    25  }
    26  
    27  // Used for keeping track of the previous window objects to encode
    28  // their location with DeltaBaseOffset set
    29  type packWindow struct {
    30  	oid      Sha1
    31  	location int
    32  	typ      PackEntryType
    33  	cache    []byte
    34  	index    *suffixarray.Index
    35  }
    36  
    37  // Writes a packfile to w of the objects objects from the client's
    38  // GitDir.
    39  func PackObjects(c *Client, opts PackObjectsOptions, w io.Writer, objects []Sha1) (trailer Sha1, err error) {
    40  	sha := sha1.New()
    41  	w = io.MultiWriter(w, sha)
    42  	n, err := w.Write([]byte{'P', 'A', 'C', 'K'})
    43  	if n != 4 {
    44  		panic("Could not write signature")
    45  	}
    46  	if err != nil {
    47  		return Sha1{}, err
    48  	}
    49  
    50  	// Version
    51  	binary.Write(w, binary.BigEndian, uint32(2))
    52  	// Size
    53  	binary.Write(w, binary.BigEndian, uint32(len(objects)))
    54  	var window []packWindow = make([]packWindow, 0, opts.Window)
    55  
    56  	pos := 12 // PACK + uint32 + uint32
    57  	for i, obj := range objects {
    58  		objcontent, err := c.GetObject(obj)
    59  		if err != nil {
    60  			return Sha1{}, err
    61  		}
    62  		objbytes := objcontent.GetContent()
    63  		best := objbytes
    64  		otyp := obj.PackEntryType(c)
    65  		otypreal := obj.PackEntryType(c)
    66  		written := 0
    67  		var ref *packWindow
    68  
    69  		// We don't bother trying to calculate how close the object
    70  		// is, we just blindly calculate a delta and calculate the
    71  		// size.
    72  		for _, tryobj := range window {
    73  			basebytes := tryobj.cache
    74  			if tryobj.typ != otypreal {
    75  				continue
    76  			}
    77  
    78  			var newdelta bytes.Buffer
    79  			if err := delta.CalculateWithIndex(tryobj.index, &newdelta, basebytes, objbytes, len(best)/2); err == nil {
    80  
    81  				if d := newdelta.Bytes(); len(d) < len(best) {
    82  					best = d
    83  					if opts.DeltaBaseOffset {
    84  						otyp = OBJ_OFS_DELTA
    85  					} else {
    86  						otyp = OBJ_REF_DELTA
    87  					}
    88  					ref = &tryobj
    89  				}
    90  			} else {
    91  				log.Println(err)
    92  			}
    93  		}
    94  
    95  		s := VariableLengthInt(len(best))
    96  
    97  		if n, err := s.WriteVariable(w, otyp); err != nil {
    98  			return Sha1{}, err
    99  		} else {
   100  			written += n
   101  		}
   102  
   103  		if ref != nil {
   104  			if opts.DeltaBaseOffset {
   105  				offset := pos - ref.location
   106  				var buf [128]byte
   107  				n := binary.PutUvarint(buf[:], uint64(offset))
   108  				n, err := w.Write(buf[:n])
   109  				if err != nil {
   110  					return Sha1{}, err
   111  				}
   112  				written += n
   113  
   114  			} else {
   115  				if _, err := w.Write(ref.oid[:]); err != nil {
   116  					return Sha1{}, err
   117  				}
   118  				written += 20
   119  			}
   120  		}
   121  
   122  		// We write into a buffer so that we can calcualte the compressed
   123  		// size easily. the n from io.Writer is the uncompressed size.
   124  		var cbuf bytes.Buffer
   125  		zw := zlib.NewWriter(&cbuf)
   126  		if err != nil {
   127  			panic(err)
   128  		}
   129  		if _, err := zw.Write(best); err != nil {
   130  			return Sha1{}, err
   131  		}
   132  		zw.Close()
   133  
   134  		if _, err := w.Write(cbuf.Bytes()); err != nil {
   135  			return Sha1{}, err
   136  		}
   137  
   138  		written += cbuf.Len()
   139  
   140  		suffix := suffixarray.New(objbytes)
   141  		if opts.Window > 0 {
   142  			if i < opts.Window {
   143  				window = append(window, packWindow{
   144  					oid:      obj,
   145  					location: pos,
   146  					typ:      otypreal,
   147  					cache:    objbytes,
   148  					index:    suffix,
   149  				})
   150  			} else {
   151  				window[i%opts.Window] = packWindow{
   152  					oid:      obj,
   153  					location: pos,
   154  					typ:      otypreal,
   155  					cache:    objbytes,
   156  					index:    suffix,
   157  				}
   158  			}
   159  		}
   160  
   161  		pos += written
   162  	}
   163  	trail := sha.Sum(nil)
   164  	w.Write(trail)
   165  	return Sha1FromSlice(trail)
   166  }