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.

file.go 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. package commitgraph
  2. import (
  3. "bytes"
  4. encbin "encoding/binary"
  5. "errors"
  6. "io"
  7. "time"
  8. "github.com/go-git/go-git/v5/plumbing"
  9. "github.com/go-git/go-git/v5/utils/binary"
  10. )
  11. var (
  12. // ErrUnsupportedVersion is returned by OpenFileIndex when the commit graph
  13. // file version is not supported.
  14. ErrUnsupportedVersion = errors.New("Unsupported version")
  15. // ErrUnsupportedHash is returned by OpenFileIndex when the commit graph
  16. // hash function is not supported. Currently only SHA-1 is defined and
  17. // supported
  18. ErrUnsupportedHash = errors.New("Unsupported hash algorithm")
  19. // ErrMalformedCommitGraphFile is returned by OpenFileIndex when the commit
  20. // graph file is corrupted.
  21. ErrMalformedCommitGraphFile = errors.New("Malformed commit graph file")
  22. commitFileSignature = []byte{'C', 'G', 'P', 'H'}
  23. oidFanoutSignature = []byte{'O', 'I', 'D', 'F'}
  24. oidLookupSignature = []byte{'O', 'I', 'D', 'L'}
  25. commitDataSignature = []byte{'C', 'D', 'A', 'T'}
  26. extraEdgeListSignature = []byte{'E', 'D', 'G', 'E'}
  27. lastSignature = []byte{0, 0, 0, 0}
  28. parentNone = uint32(0x70000000)
  29. parentOctopusUsed = uint32(0x80000000)
  30. parentOctopusMask = uint32(0x7fffffff)
  31. parentLast = uint32(0x80000000)
  32. )
  33. type fileIndex struct {
  34. reader io.ReaderAt
  35. fanout [256]int
  36. oidFanoutOffset int64
  37. oidLookupOffset int64
  38. commitDataOffset int64
  39. extraEdgeListOffset int64
  40. }
  41. // OpenFileIndex opens a serialized commit graph file in the format described at
  42. // https://github.com/git/git/blob/master/Documentation/technical/commit-graph-format.txt
  43. func OpenFileIndex(reader io.ReaderAt) (Index, error) {
  44. fi := &fileIndex{reader: reader}
  45. if err := fi.verifyFileHeader(); err != nil {
  46. return nil, err
  47. }
  48. if err := fi.readChunkHeaders(); err != nil {
  49. return nil, err
  50. }
  51. if err := fi.readFanout(); err != nil {
  52. return nil, err
  53. }
  54. return fi, nil
  55. }
  56. func (fi *fileIndex) verifyFileHeader() error {
  57. // Verify file signature
  58. var signature = make([]byte, 4)
  59. if _, err := fi.reader.ReadAt(signature, 0); err != nil {
  60. return err
  61. }
  62. if !bytes.Equal(signature, commitFileSignature) {
  63. return ErrMalformedCommitGraphFile
  64. }
  65. // Read and verify the file header
  66. var header = make([]byte, 4)
  67. if _, err := fi.reader.ReadAt(header, 4); err != nil {
  68. return err
  69. }
  70. if header[0] != 1 {
  71. return ErrUnsupportedVersion
  72. }
  73. if header[1] != 1 {
  74. return ErrUnsupportedHash
  75. }
  76. return nil
  77. }
  78. func (fi *fileIndex) readChunkHeaders() error {
  79. var chunkID = make([]byte, 4)
  80. for i := 0; ; i++ {
  81. chunkHeader := io.NewSectionReader(fi.reader, 8+(int64(i)*12), 12)
  82. if _, err := io.ReadAtLeast(chunkHeader, chunkID, 4); err != nil {
  83. return err
  84. }
  85. chunkOffset, err := binary.ReadUint64(chunkHeader)
  86. if err != nil {
  87. return err
  88. }
  89. if bytes.Equal(chunkID, oidFanoutSignature) {
  90. fi.oidFanoutOffset = int64(chunkOffset)
  91. } else if bytes.Equal(chunkID, oidLookupSignature) {
  92. fi.oidLookupOffset = int64(chunkOffset)
  93. } else if bytes.Equal(chunkID, commitDataSignature) {
  94. fi.commitDataOffset = int64(chunkOffset)
  95. } else if bytes.Equal(chunkID, extraEdgeListSignature) {
  96. fi.extraEdgeListOffset = int64(chunkOffset)
  97. } else if bytes.Equal(chunkID, lastSignature) {
  98. break
  99. }
  100. }
  101. if fi.oidFanoutOffset <= 0 || fi.oidLookupOffset <= 0 || fi.commitDataOffset <= 0 {
  102. return ErrMalformedCommitGraphFile
  103. }
  104. return nil
  105. }
  106. func (fi *fileIndex) readFanout() error {
  107. fanoutReader := io.NewSectionReader(fi.reader, fi.oidFanoutOffset, 256*4)
  108. for i := 0; i < 256; i++ {
  109. fanoutValue, err := binary.ReadUint32(fanoutReader)
  110. if err != nil {
  111. return err
  112. }
  113. if fanoutValue > 0x7fffffff {
  114. return ErrMalformedCommitGraphFile
  115. }
  116. fi.fanout[i] = int(fanoutValue)
  117. }
  118. return nil
  119. }
  120. func (fi *fileIndex) GetIndexByHash(h plumbing.Hash) (int, error) {
  121. var oid plumbing.Hash
  122. // Find the hash in the oid lookup table
  123. var low int
  124. if h[0] == 0 {
  125. low = 0
  126. } else {
  127. low = fi.fanout[h[0]-1]
  128. }
  129. high := fi.fanout[h[0]]
  130. for low < high {
  131. mid := (low + high) >> 1
  132. offset := fi.oidLookupOffset + int64(mid)*20
  133. if _, err := fi.reader.ReadAt(oid[:], offset); err != nil {
  134. return 0, err
  135. }
  136. cmp := bytes.Compare(h[:], oid[:])
  137. if cmp < 0 {
  138. high = mid
  139. } else if cmp == 0 {
  140. return mid, nil
  141. } else {
  142. low = mid + 1
  143. }
  144. }
  145. return 0, plumbing.ErrObjectNotFound
  146. }
  147. func (fi *fileIndex) GetCommitDataByIndex(idx int) (*CommitData, error) {
  148. if idx >= fi.fanout[0xff] {
  149. return nil, plumbing.ErrObjectNotFound
  150. }
  151. offset := fi.commitDataOffset + int64(idx)*36
  152. commitDataReader := io.NewSectionReader(fi.reader, offset, 36)
  153. treeHash, err := binary.ReadHash(commitDataReader)
  154. if err != nil {
  155. return nil, err
  156. }
  157. parent1, err := binary.ReadUint32(commitDataReader)
  158. if err != nil {
  159. return nil, err
  160. }
  161. parent2, err := binary.ReadUint32(commitDataReader)
  162. if err != nil {
  163. return nil, err
  164. }
  165. genAndTime, err := binary.ReadUint64(commitDataReader)
  166. if err != nil {
  167. return nil, err
  168. }
  169. var parentIndexes []int
  170. if parent2&parentOctopusUsed == parentOctopusUsed {
  171. // Octopus merge
  172. parentIndexes = []int{int(parent1 & parentOctopusMask)}
  173. offset := fi.extraEdgeListOffset + 4*int64(parent2&parentOctopusMask)
  174. buf := make([]byte, 4)
  175. for {
  176. _, err := fi.reader.ReadAt(buf, offset)
  177. if err != nil {
  178. return nil, err
  179. }
  180. parent := encbin.BigEndian.Uint32(buf)
  181. offset += 4
  182. parentIndexes = append(parentIndexes, int(parent&parentOctopusMask))
  183. if parent&parentLast == parentLast {
  184. break
  185. }
  186. }
  187. } else if parent2 != parentNone {
  188. parentIndexes = []int{int(parent1 & parentOctopusMask), int(parent2 & parentOctopusMask)}
  189. } else if parent1 != parentNone {
  190. parentIndexes = []int{int(parent1 & parentOctopusMask)}
  191. }
  192. parentHashes, err := fi.getHashesFromIndexes(parentIndexes)
  193. if err != nil {
  194. return nil, err
  195. }
  196. return &CommitData{
  197. TreeHash: treeHash,
  198. ParentIndexes: parentIndexes,
  199. ParentHashes: parentHashes,
  200. Generation: int(genAndTime >> 34),
  201. When: time.Unix(int64(genAndTime&0x3FFFFFFFF), 0),
  202. }, nil
  203. }
  204. func (fi *fileIndex) getHashesFromIndexes(indexes []int) ([]plumbing.Hash, error) {
  205. hashes := make([]plumbing.Hash, len(indexes))
  206. for i, idx := range indexes {
  207. if idx >= fi.fanout[0xff] {
  208. return nil, ErrMalformedCommitGraphFile
  209. }
  210. offset := fi.oidLookupOffset + int64(idx)*20
  211. if _, err := fi.reader.ReadAt(hashes[i][:], offset); err != nil {
  212. return nil, err
  213. }
  214. }
  215. return hashes, nil
  216. }
  217. // Hashes returns all the hashes that are available in the index
  218. func (fi *fileIndex) Hashes() []plumbing.Hash {
  219. hashes := make([]plumbing.Hash, fi.fanout[0xff])
  220. for i := 0; i < fi.fanout[0xff]; i++ {
  221. offset := fi.oidLookupOffset + int64(i)*20
  222. if n, err := fi.reader.ReadAt(hashes[i][:], offset); err != nil || n < 20 {
  223. return nil
  224. }
  225. }
  226. return hashes
  227. }