You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

encoder.go 4.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. package commitgraph
  2. import (
  3. "crypto/sha1"
  4. "hash"
  5. "io"
  6. "github.com/go-git/go-git/v5/plumbing"
  7. "github.com/go-git/go-git/v5/utils/binary"
  8. )
  9. // Encoder writes MemoryIndex structs to an output stream.
  10. type Encoder struct {
  11. io.Writer
  12. hash hash.Hash
  13. }
  14. // NewEncoder returns a new stream encoder that writes to w.
  15. func NewEncoder(w io.Writer) *Encoder {
  16. h := sha1.New()
  17. mw := io.MultiWriter(w, h)
  18. return &Encoder{mw, h}
  19. }
  20. // Encode writes an index into the commit-graph file
  21. func (e *Encoder) Encode(idx Index) error {
  22. // Get all the hashes in the input index
  23. hashes := idx.Hashes()
  24. // Sort the inout and prepare helper structures we'll need for encoding
  25. hashToIndex, fanout, extraEdgesCount := e.prepare(idx, hashes)
  26. chunkSignatures := [][]byte{oidFanoutSignature, oidLookupSignature, commitDataSignature}
  27. chunkSizes := []uint64{4 * 256, uint64(len(hashes)) * 20, uint64(len(hashes)) * 36}
  28. if extraEdgesCount > 0 {
  29. chunkSignatures = append(chunkSignatures, extraEdgeListSignature)
  30. chunkSizes = append(chunkSizes, uint64(extraEdgesCount)*4)
  31. }
  32. if err := e.encodeFileHeader(len(chunkSignatures)); err != nil {
  33. return err
  34. }
  35. if err := e.encodeChunkHeaders(chunkSignatures, chunkSizes); err != nil {
  36. return err
  37. }
  38. if err := e.encodeFanout(fanout); err != nil {
  39. return err
  40. }
  41. if err := e.encodeOidLookup(hashes); err != nil {
  42. return err
  43. }
  44. if extraEdges, err := e.encodeCommitData(hashes, hashToIndex, idx); err == nil {
  45. if err = e.encodeExtraEdges(extraEdges); err != nil {
  46. return err
  47. }
  48. } else {
  49. return err
  50. }
  51. return e.encodeChecksum()
  52. }
  53. func (e *Encoder) prepare(idx Index, hashes []plumbing.Hash) (hashToIndex map[plumbing.Hash]uint32, fanout []uint32, extraEdgesCount uint32) {
  54. // Sort the hashes and build our index
  55. plumbing.HashesSort(hashes)
  56. hashToIndex = make(map[plumbing.Hash]uint32)
  57. fanout = make([]uint32, 256)
  58. for i, hash := range hashes {
  59. hashToIndex[hash] = uint32(i)
  60. fanout[hash[0]]++
  61. }
  62. // Convert the fanout to cumulative values
  63. for i := 1; i <= 0xff; i++ {
  64. fanout[i] += fanout[i-1]
  65. }
  66. // Find out if we will need extra edge table
  67. for i := 0; i < len(hashes); i++ {
  68. v, _ := idx.GetCommitDataByIndex(i)
  69. if len(v.ParentHashes) > 2 {
  70. extraEdgesCount += uint32(len(v.ParentHashes) - 1)
  71. break
  72. }
  73. }
  74. return
  75. }
  76. func (e *Encoder) encodeFileHeader(chunkCount int) (err error) {
  77. if _, err = e.Write(commitFileSignature); err == nil {
  78. _, err = e.Write([]byte{1, 1, byte(chunkCount), 0})
  79. }
  80. return
  81. }
  82. func (e *Encoder) encodeChunkHeaders(chunkSignatures [][]byte, chunkSizes []uint64) (err error) {
  83. // 8 bytes of file header, 12 bytes for each chunk header and 12 byte for terminator
  84. offset := uint64(8 + len(chunkSignatures)*12 + 12)
  85. for i, signature := range chunkSignatures {
  86. if _, err = e.Write(signature); err == nil {
  87. err = binary.WriteUint64(e, offset)
  88. }
  89. if err != nil {
  90. return
  91. }
  92. offset += chunkSizes[i]
  93. }
  94. if _, err = e.Write(lastSignature); err == nil {
  95. err = binary.WriteUint64(e, offset)
  96. }
  97. return
  98. }
  99. func (e *Encoder) encodeFanout(fanout []uint32) (err error) {
  100. for i := 0; i <= 0xff; i++ {
  101. if err = binary.WriteUint32(e, fanout[i]); err != nil {
  102. return
  103. }
  104. }
  105. return
  106. }
  107. func (e *Encoder) encodeOidLookup(hashes []plumbing.Hash) (err error) {
  108. for _, hash := range hashes {
  109. if _, err = e.Write(hash[:]); err != nil {
  110. return err
  111. }
  112. }
  113. return
  114. }
  115. func (e *Encoder) encodeCommitData(hashes []plumbing.Hash, hashToIndex map[plumbing.Hash]uint32, idx Index) (extraEdges []uint32, err error) {
  116. for _, hash := range hashes {
  117. origIndex, _ := idx.GetIndexByHash(hash)
  118. commitData, _ := idx.GetCommitDataByIndex(origIndex)
  119. if _, err = e.Write(commitData.TreeHash[:]); err != nil {
  120. return
  121. }
  122. var parent1, parent2 uint32
  123. if len(commitData.ParentHashes) == 0 {
  124. parent1 = parentNone
  125. parent2 = parentNone
  126. } else if len(commitData.ParentHashes) == 1 {
  127. parent1 = hashToIndex[commitData.ParentHashes[0]]
  128. parent2 = parentNone
  129. } else if len(commitData.ParentHashes) == 2 {
  130. parent1 = hashToIndex[commitData.ParentHashes[0]]
  131. parent2 = hashToIndex[commitData.ParentHashes[1]]
  132. } else if len(commitData.ParentHashes) > 2 {
  133. parent1 = hashToIndex[commitData.ParentHashes[0]]
  134. parent2 = uint32(len(extraEdges)) | parentOctopusUsed
  135. for _, parentHash := range commitData.ParentHashes[1:] {
  136. extraEdges = append(extraEdges, hashToIndex[parentHash])
  137. }
  138. extraEdges[len(extraEdges)-1] |= parentLast
  139. }
  140. if err = binary.WriteUint32(e, parent1); err == nil {
  141. err = binary.WriteUint32(e, parent2)
  142. }
  143. if err != nil {
  144. return
  145. }
  146. unixTime := uint64(commitData.When.Unix())
  147. unixTime |= uint64(commitData.Generation) << 34
  148. if err = binary.WriteUint64(e, unixTime); err != nil {
  149. return
  150. }
  151. }
  152. return
  153. }
  154. func (e *Encoder) encodeExtraEdges(extraEdges []uint32) (err error) {
  155. for _, parent := range extraEdges {
  156. if err = binary.WriteUint32(e, parent); err != nil {
  157. return
  158. }
  159. }
  160. return
  161. }
  162. func (e *Encoder) encodeChecksum() error {
  163. _, err := e.Write(e.hash.Sum(nil)[:20])
  164. return err
  165. }