2019-07-02 02:15:14 +00:00
|
|
|
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}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Encode writes an index into the commit-graph file
|
|
|
|
func (e *Encoder) Encode(idx Index) error {
|
|
|
|
// Get all the hashes in the input index
|
|
|
|
hashes := idx.Hashes()
|
|
|
|
|
|
|
|
// Sort the inout and prepare helper structures we'll need for encoding
|
|
|
|
hashToIndex, fanout, extraEdgesCount := e.prepare(idx, hashes)
|
|
|
|
|
|
|
|
chunkSignatures := [][]byte{oidFanoutSignature, oidLookupSignature, commitDataSignature}
|
|
|
|
chunkSizes := []uint64{4 * 256, uint64(len(hashes)) * 20, uint64(len(hashes)) * 36}
|
|
|
|
if extraEdgesCount > 0 {
|
|
|
|
chunkSignatures = append(chunkSignatures, extraEdgeListSignature)
|
|
|
|
chunkSizes = append(chunkSizes, uint64(extraEdgesCount)*4)
|
|
|
|
}
|
|
|
|
|
2019-07-31 16:45:42 +00:00
|
|
|
if err := e.encodeFileHeader(len(chunkSignatures)); err != nil {
|
2019-07-02 02:15:14 +00:00
|
|
|
return err
|
|
|
|
}
|
2019-07-31 16:45:42 +00:00
|
|
|
if err := e.encodeChunkHeaders(chunkSignatures, chunkSizes); err != nil {
|
2019-07-02 02:15:14 +00:00
|
|
|
return err
|
|
|
|
}
|
2019-07-31 16:45:42 +00:00
|
|
|
if err := e.encodeFanout(fanout); err != nil {
|
2019-07-02 02:15:14 +00:00
|
|
|
return err
|
|
|
|
}
|
2019-07-31 16:45:42 +00:00
|
|
|
if err := e.encodeOidLookup(hashes); err != nil {
|
2019-07-02 02:15:14 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
if extraEdges, err := e.encodeCommitData(hashes, hashToIndex, idx); err == nil {
|
|
|
|
if err = e.encodeExtraEdges(extraEdges); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-07-31 16:45:42 +00:00
|
|
|
} else {
|
2019-07-02 02:15:14 +00:00
|
|
|
return err
|
|
|
|
}
|
2019-07-31 16:45:42 +00:00
|
|
|
|
2019-07-02 02:15:14 +00:00
|
|
|
return e.encodeChecksum()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *Encoder) prepare(idx Index, hashes []plumbing.Hash) (hashToIndex map[plumbing.Hash]uint32, fanout []uint32, extraEdgesCount uint32) {
|
|
|
|
// Sort the hashes and build our index
|
|
|
|
plumbing.HashesSort(hashes)
|
|
|
|
hashToIndex = make(map[plumbing.Hash]uint32)
|
|
|
|
fanout = make([]uint32, 256)
|
|
|
|
for i, hash := range hashes {
|
|
|
|
hashToIndex[hash] = uint32(i)
|
|
|
|
fanout[hash[0]]++
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert the fanout to cumulative values
|
|
|
|
for i := 1; i <= 0xff; i++ {
|
|
|
|
fanout[i] += fanout[i-1]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find out if we will need extra edge table
|
|
|
|
for i := 0; i < len(hashes); i++ {
|
|
|
|
v, _ := idx.GetCommitDataByIndex(i)
|
|
|
|
if len(v.ParentHashes) > 2 {
|
|
|
|
extraEdgesCount += uint32(len(v.ParentHashes) - 1)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *Encoder) encodeFileHeader(chunkCount int) (err error) {
|
|
|
|
if _, err = e.Write(commitFileSignature); err == nil {
|
|
|
|
_, err = e.Write([]byte{1, 1, byte(chunkCount), 0})
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *Encoder) encodeChunkHeaders(chunkSignatures [][]byte, chunkSizes []uint64) (err error) {
|
|
|
|
// 8 bytes of file header, 12 bytes for each chunk header and 12 byte for terminator
|
|
|
|
offset := uint64(8 + len(chunkSignatures)*12 + 12)
|
|
|
|
for i, signature := range chunkSignatures {
|
|
|
|
if _, err = e.Write(signature); err == nil {
|
|
|
|
err = binary.WriteUint64(e, offset)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
offset += chunkSizes[i]
|
|
|
|
}
|
|
|
|
if _, err = e.Write(lastSignature); err == nil {
|
|
|
|
err = binary.WriteUint64(e, offset)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *Encoder) encodeFanout(fanout []uint32) (err error) {
|
|
|
|
for i := 0; i <= 0xff; i++ {
|
|
|
|
if err = binary.WriteUint32(e, fanout[i]); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *Encoder) encodeOidLookup(hashes []plumbing.Hash) (err error) {
|
|
|
|
for _, hash := range hashes {
|
|
|
|
if _, err = e.Write(hash[:]); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *Encoder) encodeCommitData(hashes []plumbing.Hash, hashToIndex map[plumbing.Hash]uint32, idx Index) (extraEdges []uint32, err error) {
|
|
|
|
for _, hash := range hashes {
|
|
|
|
origIndex, _ := idx.GetIndexByHash(hash)
|
|
|
|
commitData, _ := idx.GetCommitDataByIndex(origIndex)
|
|
|
|
if _, err = e.Write(commitData.TreeHash[:]); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
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(extraEdges)) | parentOctopusUsed
|
|
|
|
for _, parentHash := range commitData.ParentHashes[1:] {
|
|
|
|
extraEdges = append(extraEdges, hashToIndex[parentHash])
|
|
|
|
}
|
|
|
|
extraEdges[len(extraEdges)-1] |= parentLast
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = binary.WriteUint32(e, parent1); err == nil {
|
|
|
|
err = binary.WriteUint32(e, parent2)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
unixTime := uint64(commitData.When.Unix())
|
|
|
|
unixTime |= uint64(commitData.Generation) << 34
|
|
|
|
if err = binary.WriteUint64(e, unixTime); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *Encoder) encodeExtraEdges(extraEdges []uint32) (err error) {
|
|
|
|
for _, parent := range extraEdges {
|
|
|
|
if err = binary.WriteUint32(e, parent); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *Encoder) encodeChecksum() error {
|
|
|
|
_, err := e.Write(e.hash.Sum(nil)[:20])
|
|
|
|
return err
|
|
|
|
}
|