本站源代码
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.

431 lines
11KB

  1. package object
  2. import (
  3. "bufio"
  4. "bytes"
  5. "context"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "strings"
  10. "golang.org/x/crypto/openpgp"
  11. "gopkg.in/src-d/go-git.v4/plumbing"
  12. "gopkg.in/src-d/go-git.v4/plumbing/storer"
  13. "gopkg.in/src-d/go-git.v4/utils/ioutil"
  14. )
  15. const (
  16. beginpgp string = "-----BEGIN PGP SIGNATURE-----"
  17. endpgp string = "-----END PGP SIGNATURE-----"
  18. headerpgp string = "gpgsig"
  19. )
  20. // Hash represents the hash of an object
  21. type Hash plumbing.Hash
  22. // Commit points to a single tree, marking it as what the project looked like
  23. // at a certain point in time. It contains meta-information about that point
  24. // in time, such as a timestamp, the author of the changes since the last
  25. // commit, a pointer to the previous commit(s), etc.
  26. // http://shafiulazam.com/gitbook/1_the_git_object_model.html
  27. type Commit struct {
  28. // Hash of the commit object.
  29. Hash plumbing.Hash
  30. // Author is the original author of the commit.
  31. Author Signature
  32. // Committer is the one performing the commit, might be different from
  33. // Author.
  34. Committer Signature
  35. // PGPSignature is the PGP signature of the commit.
  36. PGPSignature string
  37. // Message is the commit message, contains arbitrary text.
  38. Message string
  39. // TreeHash is the hash of the root tree of the commit.
  40. TreeHash plumbing.Hash
  41. // ParentHashes are the hashes of the parent commits of the commit.
  42. ParentHashes []plumbing.Hash
  43. s storer.EncodedObjectStorer
  44. }
  45. // GetCommit gets a commit from an object storer and decodes it.
  46. func GetCommit(s storer.EncodedObjectStorer, h plumbing.Hash) (*Commit, error) {
  47. o, err := s.EncodedObject(plumbing.CommitObject, h)
  48. if err != nil {
  49. return nil, err
  50. }
  51. return DecodeCommit(s, o)
  52. }
  53. // DecodeCommit decodes an encoded object into a *Commit and associates it to
  54. // the given object storer.
  55. func DecodeCommit(s storer.EncodedObjectStorer, o plumbing.EncodedObject) (*Commit, error) {
  56. c := &Commit{s: s}
  57. if err := c.Decode(o); err != nil {
  58. return nil, err
  59. }
  60. return c, nil
  61. }
  62. // Tree returns the Tree from the commit.
  63. func (c *Commit) Tree() (*Tree, error) {
  64. return GetTree(c.s, c.TreeHash)
  65. }
  66. // PatchContext returns the Patch between the actual commit and the provided one.
  67. // Error will be return if context expires. Provided context must be non-nil.
  68. func (c *Commit) PatchContext(ctx context.Context, to *Commit) (*Patch, error) {
  69. fromTree, err := c.Tree()
  70. if err != nil {
  71. return nil, err
  72. }
  73. toTree, err := to.Tree()
  74. if err != nil {
  75. return nil, err
  76. }
  77. return fromTree.PatchContext(ctx, toTree)
  78. }
  79. // Patch returns the Patch between the actual commit and the provided one.
  80. func (c *Commit) Patch(to *Commit) (*Patch, error) {
  81. return c.PatchContext(context.Background(), to)
  82. }
  83. // Parents return a CommitIter to the parent Commits.
  84. func (c *Commit) Parents() CommitIter {
  85. return NewCommitIter(c.s,
  86. storer.NewEncodedObjectLookupIter(c.s, plumbing.CommitObject, c.ParentHashes),
  87. )
  88. }
  89. // NumParents returns the number of parents in a commit.
  90. func (c *Commit) NumParents() int {
  91. return len(c.ParentHashes)
  92. }
  93. var ErrParentNotFound = errors.New("commit parent not found")
  94. // Parent returns the ith parent of a commit.
  95. func (c *Commit) Parent(i int) (*Commit, error) {
  96. if len(c.ParentHashes) == 0 || i > len(c.ParentHashes)-1 {
  97. return nil, ErrParentNotFound
  98. }
  99. return GetCommit(c.s, c.ParentHashes[i])
  100. }
  101. // File returns the file with the specified "path" in the commit and a
  102. // nil error if the file exists. If the file does not exist, it returns
  103. // a nil file and the ErrFileNotFound error.
  104. func (c *Commit) File(path string) (*File, error) {
  105. tree, err := c.Tree()
  106. if err != nil {
  107. return nil, err
  108. }
  109. return tree.File(path)
  110. }
  111. // Files returns a FileIter allowing to iterate over the Tree
  112. func (c *Commit) Files() (*FileIter, error) {
  113. tree, err := c.Tree()
  114. if err != nil {
  115. return nil, err
  116. }
  117. return tree.Files(), nil
  118. }
  119. // ID returns the object ID of the commit. The returned value will always match
  120. // the current value of Commit.Hash.
  121. //
  122. // ID is present to fulfill the Object interface.
  123. func (c *Commit) ID() plumbing.Hash {
  124. return c.Hash
  125. }
  126. // Type returns the type of object. It always returns plumbing.CommitObject.
  127. //
  128. // Type is present to fulfill the Object interface.
  129. func (c *Commit) Type() plumbing.ObjectType {
  130. return plumbing.CommitObject
  131. }
  132. // Decode transforms a plumbing.EncodedObject into a Commit struct.
  133. func (c *Commit) Decode(o plumbing.EncodedObject) (err error) {
  134. if o.Type() != plumbing.CommitObject {
  135. return ErrUnsupportedObject
  136. }
  137. c.Hash = o.Hash()
  138. reader, err := o.Reader()
  139. if err != nil {
  140. return err
  141. }
  142. defer ioutil.CheckClose(reader, &err)
  143. r := bufPool.Get().(*bufio.Reader)
  144. defer bufPool.Put(r)
  145. r.Reset(reader)
  146. var message bool
  147. var pgpsig bool
  148. for {
  149. line, err := r.ReadBytes('\n')
  150. if err != nil && err != io.EOF {
  151. return err
  152. }
  153. if pgpsig {
  154. if len(line) > 0 && line[0] == ' ' {
  155. line = bytes.TrimLeft(line, " ")
  156. c.PGPSignature += string(line)
  157. continue
  158. } else {
  159. pgpsig = false
  160. }
  161. }
  162. if !message {
  163. line = bytes.TrimSpace(line)
  164. if len(line) == 0 {
  165. message = true
  166. continue
  167. }
  168. split := bytes.SplitN(line, []byte{' '}, 2)
  169. var data []byte
  170. if len(split) == 2 {
  171. data = split[1]
  172. }
  173. switch string(split[0]) {
  174. case "tree":
  175. c.TreeHash = plumbing.NewHash(string(data))
  176. case "parent":
  177. c.ParentHashes = append(c.ParentHashes, plumbing.NewHash(string(data)))
  178. case "author":
  179. c.Author.Decode(data)
  180. case "committer":
  181. c.Committer.Decode(data)
  182. case headerpgp:
  183. c.PGPSignature += string(data) + "\n"
  184. pgpsig = true
  185. }
  186. } else {
  187. c.Message += string(line)
  188. }
  189. if err == io.EOF {
  190. return nil
  191. }
  192. }
  193. }
  194. // Encode transforms a Commit into a plumbing.EncodedObject.
  195. func (b *Commit) Encode(o plumbing.EncodedObject) error {
  196. return b.encode(o, true)
  197. }
  198. // EncodeWithoutSignature export a Commit into a plumbing.EncodedObject without the signature (correspond to the payload of the PGP signature).
  199. func (b *Commit) EncodeWithoutSignature(o plumbing.EncodedObject) error {
  200. return b.encode(o, false)
  201. }
  202. func (b *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
  203. o.SetType(plumbing.CommitObject)
  204. w, err := o.Writer()
  205. if err != nil {
  206. return err
  207. }
  208. defer ioutil.CheckClose(w, &err)
  209. if _, err = fmt.Fprintf(w, "tree %s\n", b.TreeHash.String()); err != nil {
  210. return err
  211. }
  212. for _, parent := range b.ParentHashes {
  213. if _, err = fmt.Fprintf(w, "parent %s\n", parent.String()); err != nil {
  214. return err
  215. }
  216. }
  217. if _, err = fmt.Fprint(w, "author "); err != nil {
  218. return err
  219. }
  220. if err = b.Author.Encode(w); err != nil {
  221. return err
  222. }
  223. if _, err = fmt.Fprint(w, "\ncommitter "); err != nil {
  224. return err
  225. }
  226. if err = b.Committer.Encode(w); err != nil {
  227. return err
  228. }
  229. if b.PGPSignature != "" && includeSig {
  230. if _, err = fmt.Fprint(w, "\n"+headerpgp+" "); err != nil {
  231. return err
  232. }
  233. // Split all the signature lines and re-write with a left padding and
  234. // newline. Use join for this so it's clear that a newline should not be
  235. // added after this section, as it will be added when the message is
  236. // printed.
  237. signature := strings.TrimSuffix(b.PGPSignature, "\n")
  238. lines := strings.Split(signature, "\n")
  239. if _, err = fmt.Fprint(w, strings.Join(lines, "\n ")); err != nil {
  240. return err
  241. }
  242. }
  243. if _, err = fmt.Fprintf(w, "\n\n%s", b.Message); err != nil {
  244. return err
  245. }
  246. return err
  247. }
  248. // Stats returns the stats of a commit.
  249. func (c *Commit) Stats() (FileStats, error) {
  250. return c.StatsContext(context.Background())
  251. }
  252. // StatsContext returns the stats of a commit. Error will be return if context
  253. // expires. Provided context must be non-nil.
  254. func (c *Commit) StatsContext(ctx context.Context) (FileStats, error) {
  255. fromTree, err := c.Tree()
  256. if err != nil {
  257. return nil, err
  258. }
  259. toTree := &Tree{}
  260. if c.NumParents() != 0 {
  261. firstParent, err := c.Parents().Next()
  262. if err != nil {
  263. return nil, err
  264. }
  265. toTree, err = firstParent.Tree()
  266. if err != nil {
  267. return nil, err
  268. }
  269. }
  270. patch, err := toTree.PatchContext(ctx, fromTree)
  271. if err != nil {
  272. return nil, err
  273. }
  274. return getFileStatsFromFilePatches(patch.FilePatches()), nil
  275. }
  276. func (c *Commit) String() string {
  277. return fmt.Sprintf(
  278. "%s %s\nAuthor: %s\nDate: %s\n\n%s\n",
  279. plumbing.CommitObject, c.Hash, c.Author.String(),
  280. c.Author.When.Format(DateFormat), indent(c.Message),
  281. )
  282. }
  283. // Verify performs PGP verification of the commit with a provided armored
  284. // keyring and returns openpgp.Entity associated with verifying key on success.
  285. func (c *Commit) Verify(armoredKeyRing string) (*openpgp.Entity, error) {
  286. keyRingReader := strings.NewReader(armoredKeyRing)
  287. keyring, err := openpgp.ReadArmoredKeyRing(keyRingReader)
  288. if err != nil {
  289. return nil, err
  290. }
  291. // Extract signature.
  292. signature := strings.NewReader(c.PGPSignature)
  293. encoded := &plumbing.MemoryObject{}
  294. // Encode commit components, excluding signature and get a reader object.
  295. if err := c.EncodeWithoutSignature(encoded); err != nil {
  296. return nil, err
  297. }
  298. er, err := encoded.Reader()
  299. if err != nil {
  300. return nil, err
  301. }
  302. return openpgp.CheckArmoredDetachedSignature(keyring, er, signature)
  303. }
  304. func indent(t string) string {
  305. var output []string
  306. for _, line := range strings.Split(t, "\n") {
  307. if len(line) != 0 {
  308. line = " " + line
  309. }
  310. output = append(output, line)
  311. }
  312. return strings.Join(output, "\n")
  313. }
  314. // CommitIter is a generic closable interface for iterating over commits.
  315. type CommitIter interface {
  316. Next() (*Commit, error)
  317. ForEach(func(*Commit) error) error
  318. Close()
  319. }
  320. // storerCommitIter provides an iterator from commits in an EncodedObjectStorer.
  321. type storerCommitIter struct {
  322. storer.EncodedObjectIter
  323. s storer.EncodedObjectStorer
  324. }
  325. // NewCommitIter takes a storer.EncodedObjectStorer and a
  326. // storer.EncodedObjectIter and returns a CommitIter that iterates over all
  327. // commits contained in the storer.EncodedObjectIter.
  328. //
  329. // Any non-commit object returned by the storer.EncodedObjectIter is skipped.
  330. func NewCommitIter(s storer.EncodedObjectStorer, iter storer.EncodedObjectIter) CommitIter {
  331. return &storerCommitIter{iter, s}
  332. }
  333. // Next moves the iterator to the next commit and returns a pointer to it. If
  334. // there are no more commits, it returns io.EOF.
  335. func (iter *storerCommitIter) Next() (*Commit, error) {
  336. obj, err := iter.EncodedObjectIter.Next()
  337. if err != nil {
  338. return nil, err
  339. }
  340. return DecodeCommit(iter.s, obj)
  341. }
  342. // ForEach call the cb function for each commit contained on this iter until
  343. // an error appends or the end of the iter is reached. If ErrStop is sent
  344. // the iteration is stopped but no error is returned. The iterator is closed.
  345. func (iter *storerCommitIter) ForEach(cb func(*Commit) error) error {
  346. return iter.EncodedObjectIter.ForEach(func(obj plumbing.EncodedObject) error {
  347. c, err := DecodeCommit(iter.s, obj)
  348. if err != nil {
  349. return err
  350. }
  351. return cb(c)
  352. })
  353. }
  354. func (iter *storerCommitIter) Close() {
  355. iter.EncodedObjectIter.Close()
  356. }
上海开阖软件有限公司 沪ICP备12045867号-1