package commitgraph
import (
"crypto/sha1"
"hash"
"io"
"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/utils/binary"
)
// Encoder writes MemoryIndex structs to an output stream.
type Encoder struct {
io.Writer
hash hash.Hash
}
// NewEncoder returns a new stream encoder that writes to w.
func NewEncoder(w io.Writer) *Encoder {
h := sha1.New()
mw := io.MultiWriter(w, h)
return &Encoder{mw, h}
}
func (e *Encoder) Encode(idx Index) error {
var err error
// Get all the hashes in the memory index
hashes := idx.Hashes()
// Sort the hashes and build our index
plumbing.HashesSort(hashes)
hashToIndex := make(map[plumbing.Hash]uint32)
hashFirstToCount := make(map[byte]uint32)
for i, hash := range hashes {
hashToIndex[hash] = uint32(i)
hashFirstToCount[hash[0]]++
}
// Find out if we will need large edge table
largeEdgesCount := 0
for i := 0; i < len(hashes); i++ {
v, _ := idx.GetNodeByIndex(i)
if len(v.ParentHashes) > 2 {
largeEdgesCount += len(v.ParentHashes) - 2
break
}
}
chunks := [][]byte{oidFanoutSignature, oidLookupSignature, commitDataSignature}
chunkSizes := []uint64{4 * 256, uint64(len(hashes)) * 20, uint64(len(hashes)) * 36}
if largeEdgesCount > 0 {
chunks = append(chunks, largeEdgeListSignature)
chunkSizes = append(chunkSizes, uint64(largeEdgesCount)*4)
}
// Write header
if _, err = e.Write(commitFileSignature); err == nil {
_, err = e.Write([]byte{1, 1, byte(len(chunks)), 0})
}
if err != nil {
return err
}
// Write chunk headers
offset := uint64(8 + len(chunks)*12 + 12)
for i, signature := range chunks {
if _, err = e.Write(signature); err == nil {
err = binary.WriteUint64(e, offset)
}
if err != nil {
return err
}
offset += chunkSizes[i]
}
if _, err = e.Write([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); err != nil {
return err
}
// Write fanout
var cumulative uint32
for i := 0; i <= 0xff; i++ {
if err = binary.WriteUint32(e, hashFirstToCount[byte(i)]+cumulative); err != nil {
return err
}
cumulative += hashFirstToCount[byte(i)]
}
// Write OID lookup
for _, hash := range hashes {
if _, err = e.Write(hash[:]); err != nil {
return err
}
}
// Write commit data
var largeEdges []uint32
for _, hash := range hashes {
origIndex, _ := idx.GetIndexByHash(hash)
commitData, _ := idx.GetNodeByIndex(origIndex)
if _, err := e.Write(commitData.TreeHash[:]); err != nil {
return err
}
var parent1, parent2 uint32
if len(commitData.ParentHashes) == 0 {
parent1 = parentNone
parent2 = parentNone
} else if len(commitData.ParentHashes) == 1 {
parent1 = hashToIndex[commitData.ParentHashes[0]]
parent2 = parentNone
} else if len(commitData.ParentHashes) == 2 {
parent1 = hashToIndex[commitData.ParentHashes[0]]
parent2 = hashToIndex[commitData.ParentHashes[1]]
} else if len(commitData.ParentHashes) > 2 {
parent1 = hashToIndex[commitData.ParentHashes[0]]
parent2 = uint32(len(largeEdges)) | parentOctopusUsed
for _, parentHash := range commitData.ParentHashes[1:] {
largeEdges = append(largeEdges, hashToIndex[parentHash])
}
largeEdges[len(largeEdges)-1] |= parentLast
}
if err = binary.WriteUint32(e, parent1); err == nil {
err = binary.WriteUint32(e, parent2)
}
if err != nil {
return err
}
unixTime := uint64(commitData.When.Unix())
unixTime |= uint64(commitData.Generation) << 34
if err = binary.WriteUint64(e, unixTime); err != nil {
return err
}
}
// Write large edges if necessary
for _, parent := range largeEdges {
if err = binary.WriteUint32(e, parent); err != nil {
return err
}
}
// Write checksum
if _, err := e.Write(e.hash.Sum(nil)[:20]); err != nil {
return err
}
return nil
}