aboutsummaryrefslogtreecommitdiffstats
path: root/plumbing/format/packfile/encoder.go
diff options
context:
space:
mode:
Diffstat (limited to 'plumbing/format/packfile/encoder.go')
-rw-r--r--plumbing/format/packfile/encoder.go116
1 files changed, 116 insertions, 0 deletions
diff --git a/plumbing/format/packfile/encoder.go b/plumbing/format/packfile/encoder.go
new file mode 100644
index 0000000..1404dbe
--- /dev/null
+++ b/plumbing/format/packfile/encoder.go
@@ -0,0 +1,116 @@
+package packfile
+
+import (
+ "compress/zlib"
+ "crypto/sha1"
+ "fmt"
+ "io"
+
+ "gopkg.in/src-d/go-git.v4/plumbing"
+ "gopkg.in/src-d/go-git.v4/plumbing/storer"
+ "gopkg.in/src-d/go-git.v4/utils/binary"
+)
+
+// Encoder gets the data from the storage and write it into the writer in PACK
+// format
+type Encoder struct {
+ storage storer.ObjectStorer
+ w io.Writer
+ zw *zlib.Writer
+ hasher plumbing.Hasher
+}
+
+// NewEncoder creates a new packfile encoder using a specific Writer and
+// ObjectStorer
+func NewEncoder(w io.Writer, s storer.ObjectStorer) *Encoder {
+ h := plumbing.Hasher{
+ Hash: sha1.New(),
+ }
+ mw := io.MultiWriter(w, h)
+ zw := zlib.NewWriter(mw)
+ return &Encoder{
+ storage: s,
+ w: mw,
+ zw: zw,
+ hasher: h,
+ }
+}
+
+// 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) {
+ if err := e.head(len(hashes)); err != nil {
+ return plumbing.ZeroHash, err
+ }
+
+ for _, h := range hashes {
+ o, err := e.storage.Object(plumbing.AnyObject, h)
+ if err != nil {
+ return plumbing.ZeroHash, err
+ }
+
+ 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 plumbing.Object) error {
+ t := o.Type()
+ if t == plumbing.OFSDeltaObject || t == plumbing.REFDeltaObject {
+ // TODO implements delta objects
+ return fmt.Errorf("delta object not supported: %v", t)
+ }
+
+ if err := e.entryHead(t, o.Size()); err != nil {
+ return err
+ }
+
+ e.zw.Reset(e.w)
+ or, err := o.Reader()
+ if err != nil {
+ return err
+ }
+ _, err = io.Copy(e.zw, or)
+ if err != nil {
+ return err
+ }
+
+ return e.zw.Close()
+}
+
+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)
+}