object.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. package git
  2. import (
  3. "bufio"
  4. "bytes"
  5. "compress/zlib"
  6. "crypto/sha1"
  7. "encoding/hex"
  8. "fmt"
  9. "io"
  10. "io/ioutil"
  11. "strconv"
  12. "strings"
  13. "time"
  14. "github.com/pkg/errors"
  15. )
  16. type Type int
  17. const (
  18. _ Type = iota
  19. BlobT
  20. TreeT
  21. CommitT
  22. )
  23. type Object struct {
  24. Type Type
  25. Size int64
  26. tree []Tree
  27. blob *Blob
  28. commit *Commit
  29. }
  30. func (o Object) Tree() ([]Tree, bool) {
  31. if o.Type != TreeT || o.tree == nil {
  32. return nil, false
  33. }
  34. return o.tree, true
  35. }
  36. func (o Object) Commit() (*Commit, bool) {
  37. if o.Type != CommitT || o.commit == nil {
  38. return nil, false
  39. }
  40. return o.commit, true
  41. }
  42. func (o Object) Blob() ([]byte, bool) {
  43. if o.Type != BlobT || o.blob == nil {
  44. return nil, false
  45. }
  46. return *o.blob, true
  47. }
  48. func newBlob(content []byte) *Blob {
  49. b := Blob(content)
  50. return &b
  51. }
  52. type Hash [sha1.Size]byte
  53. func (h Hash) String() string {
  54. return hex.EncodeToString(h[:])
  55. }
  56. type Blob []byte
  57. type Tree struct {
  58. Mode, Name string
  59. SHA1Sum Hash
  60. }
  61. type Stamp struct {
  62. Name string
  63. Email string
  64. When time.Time
  65. }
  66. func (s Stamp) String() string {
  67. return fmt.Sprintf("%s <%s> %s", s.Name, s.Email, s.When.Format(time.RFC3339))
  68. //return fmt.Sprintf("%q <%q> %d", s.Name, s.Email, s.When.Unix())
  69. }
  70. type Commit struct {
  71. Author, Committer *Stamp
  72. Tree, Parent string
  73. Message string
  74. }
  75. func (o Object) String() string {
  76. switch o.Type {
  77. case BlobT:
  78. if o.blob == nil {
  79. return "broken blob"
  80. }
  81. return fmt.Sprintf("blob<%d> %q", o.Size, string(*o.blob))
  82. case TreeT:
  83. if o.tree == nil {
  84. return "broken tree"
  85. }
  86. s := fmt.Sprintf("tree<%d>\n", o.Size)
  87. for _, t := range o.tree {
  88. s += fmt.Sprintf("%q\t%q\t%s\n", t.Mode, t.Name, t.SHA1Sum)
  89. }
  90. return s
  91. case CommitT:
  92. if o.commit == nil {
  93. return "broken commit"
  94. }
  95. s := fmt.Sprintf("commit<%d>\n", o.Size)
  96. s += fmt.Sprintln("Tree:", o.commit.Tree)
  97. s += fmt.Sprintln("Author:", o.commit.Author)
  98. s += fmt.Sprintln("Committer:", o.commit.Committer)
  99. s += o.commit.Message
  100. return s
  101. default:
  102. return "broken object"
  103. }
  104. }
  105. func DecodeObject(r io.Reader) (*Object, error) {
  106. zr, err := zlib.NewReader(r)
  107. if err != nil {
  108. return nil, errors.Wrapf(err, "zlib newReader failed")
  109. }
  110. br := bufio.NewReader(zr)
  111. header, err := br.ReadBytes(0)
  112. if err != nil {
  113. return nil, errors.Wrapf(err, "error finding header 0byte")
  114. }
  115. o := &Object{}
  116. var hdrLenStr string
  117. switch {
  118. case bytes.HasPrefix(header, []byte("blob ")):
  119. o.Type = BlobT
  120. hdrLenStr = string(header[5 : len(header)-1])
  121. case bytes.HasPrefix(header, []byte("tree ")):
  122. o.Type = TreeT
  123. hdrLenStr = string(header[5 : len(header)-1])
  124. case bytes.HasPrefix(header, []byte("commit ")):
  125. o.Type = CommitT
  126. hdrLenStr = string(header[7 : len(header)-1])
  127. default:
  128. return nil, errors.Errorf("illegal git object:%q", header)
  129. }
  130. hdrLen, err := strconv.ParseInt(hdrLenStr, 10, 64)
  131. if err != nil {
  132. return nil, errors.Wrapf(err, "error parsing header length")
  133. }
  134. o.Size = hdrLen
  135. lr := io.LimitReader(br, hdrLen)
  136. switch o.Type {
  137. case BlobT:
  138. content, err := ioutil.ReadAll(lr)
  139. if err != nil {
  140. return nil, errors.Wrapf(err, "error finding header 0byte")
  141. }
  142. o.blob = newBlob(content)
  143. case TreeT:
  144. o.tree, err = decodeTreeEntries(lr)
  145. if err != nil {
  146. if errors.Cause(err) == io.EOF {
  147. return o, nil
  148. }
  149. return nil, errors.Wrapf(err, "decodecodeTreeEntries failed")
  150. }
  151. case CommitT:
  152. o.commit, err = decodeCommit(lr)
  153. if err != nil {
  154. return nil, errors.Wrapf(err, "decodeCommit() failed")
  155. }
  156. default:
  157. return nil, errors.Errorf("illegal object type:%T %v", o.Type, o.Type)
  158. }
  159. return o, nil
  160. }
  161. func decodeTreeEntries(r io.Reader) ([]Tree, error) {
  162. isEOF := func(err error) bool {
  163. return err == io.EOF
  164. }
  165. var entries []Tree
  166. br := bufio.NewReader(r)
  167. for {
  168. var t Tree
  169. hdr, err := br.ReadSlice(0)
  170. if err != nil {
  171. return entries, errors.Wrapf(err, "error finding modeName 0byte", isEOF)
  172. }
  173. modeName := bytes.Split(hdr[:len(hdr)-1], []byte(" "))
  174. if len(modeName) != 2 {
  175. return entries, errors.Errorf("illegal modeName block: %v", modeName)
  176. }
  177. t.Mode = string(modeName[0])
  178. t.Name = string(modeName[1])
  179. var hash [sha1.Size]byte
  180. n, err := br.Read(hash[:])
  181. if err != nil {
  182. return entries, errors.Wrapf(err, "br.Read() hash failed", isEOF)
  183. }
  184. if n != 20 {
  185. return entries, errors.Errorf("br.Read() fell short: %d", n)
  186. }
  187. t.SHA1Sum = hash
  188. entries = append(entries, t)
  189. }
  190. }
  191. func decodeCommit(r io.Reader) (*Commit, error) {
  192. var (
  193. s = bufio.NewScanner(r)
  194. c Commit
  195. isMsg bool
  196. err error
  197. )
  198. for s.Scan() {
  199. line := s.Text()
  200. switch {
  201. case strings.HasPrefix(line, "tree "):
  202. c.Tree = line[5:]
  203. case strings.HasPrefix(line, "parent "):
  204. c.Parent = line[7:]
  205. case strings.HasPrefix(line, "author "):
  206. c.Author, err = decodeStamp(line[7:])
  207. if err != nil {
  208. return nil, errors.Wrapf(err, "decodeStamp(%q) failed", line[7:])
  209. }
  210. case strings.HasPrefix(line, "committer "):
  211. c.Committer, err = decodeStamp(line[10:])
  212. if err != nil {
  213. return nil, errors.Wrapf(err, "decodeStamp(%q) failed", line[10:])
  214. }
  215. case line == "":
  216. isMsg = true
  217. case isMsg:
  218. c.Message += line
  219. default:
  220. return nil, errors.Errorf("unhandled commit line: %q", line)
  221. }
  222. }
  223. if err := s.Err(); err != nil {
  224. return nil, errors.Wrapf(err, "scanner error")
  225. }
  226. return &c, nil
  227. }
  228. func decodeStamp(s string) (*Stamp, error) {
  229. var stamp Stamp
  230. mailIdxLeft := strings.Index(s, "<")
  231. if mailIdxLeft == -1 {
  232. return nil, errors.New("stamp: no '<' in stamp")
  233. }
  234. mailIdxRight := strings.Index(s, ">")
  235. if mailIdxRight == -1 {
  236. return nil, errors.New("stamp: no '>' in stamp")
  237. }
  238. if mailIdxLeft > mailIdxRight {
  239. return nil, errors.New("stamp: '>' left of '<'")
  240. }
  241. if mailIdxLeft == 0 {
  242. stamp.Name = "empty name"
  243. } else {
  244. stamp.Name = s[:mailIdxLeft-1]
  245. }
  246. stamp.Email = s[mailIdxLeft+1 : mailIdxRight]
  247. if len(s)-6-(mailIdxRight+2) < 0 {
  248. return nil, errors.Errorf("stamp: illegal timestamp: %q", s)
  249. }
  250. epoc, err := strconv.ParseInt(s[mailIdxRight+2:len(s)-6], 10, 64)
  251. if err != nil {
  252. return nil, errors.Wrapf(err, "parseInt() failed")
  253. }
  254. when := time.Unix(epoc, 0)
  255. loc, err := time.Parse("-0700", s[len(s)-5:])
  256. if err != nil {
  257. return nil, errors.Wrapf(err, "timezone decode failed")
  258. }
  259. stamp.When = when.In(loc.Location())
  260. return &stamp, nil
  261. }