123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- package commitgraph
-
- import (
- "crypto/sha1"
- "hash"
- "io"
-
- "github.com/go-git/go-git/v5/plumbing"
- "github.com/go-git/go-git/v5/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)
- }
-
- if err := e.encodeFileHeader(len(chunkSignatures)); err != nil {
- return err
- }
- if err := e.encodeChunkHeaders(chunkSignatures, chunkSizes); err != nil {
- return err
- }
- if err := e.encodeFanout(fanout); err != nil {
- return err
- }
- if err := e.encodeOidLookup(hashes); err != nil {
- return err
- }
- if extraEdges, err := e.encodeCommitData(hashes, hashToIndex, idx); err == nil {
- if err = e.encodeExtraEdges(extraEdges); err != nil {
- return err
- }
- } else {
- return err
- }
-
- 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
- }
|