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

229 lines
5.2KB

  1. package git
  2. import (
  3. "bytes"
  4. "path"
  5. "sort"
  6. "strings"
  7. "golang.org/x/crypto/openpgp"
  8. "gopkg.in/src-d/go-git.v4/plumbing"
  9. "gopkg.in/src-d/go-git.v4/plumbing/filemode"
  10. "gopkg.in/src-d/go-git.v4/plumbing/format/index"
  11. "gopkg.in/src-d/go-git.v4/plumbing/object"
  12. "gopkg.in/src-d/go-git.v4/storage"
  13. "gopkg.in/src-d/go-billy.v4"
  14. )
  15. // Commit stores the current contents of the index in a new commit along with
  16. // a log message from the user describing the changes.
  17. func (w *Worktree) Commit(msg string, opts *CommitOptions) (plumbing.Hash, error) {
  18. if err := opts.Validate(w.r); err != nil {
  19. return plumbing.ZeroHash, err
  20. }
  21. if opts.All {
  22. if err := w.autoAddModifiedAndDeleted(); err != nil {
  23. return plumbing.ZeroHash, err
  24. }
  25. }
  26. idx, err := w.r.Storer.Index()
  27. if err != nil {
  28. return plumbing.ZeroHash, err
  29. }
  30. h := &buildTreeHelper{
  31. fs: w.Filesystem,
  32. s: w.r.Storer,
  33. }
  34. tree, err := h.BuildTree(idx)
  35. if err != nil {
  36. return plumbing.ZeroHash, err
  37. }
  38. commit, err := w.buildCommitObject(msg, opts, tree)
  39. if err != nil {
  40. return plumbing.ZeroHash, err
  41. }
  42. return commit, w.updateHEAD(commit)
  43. }
  44. func (w *Worktree) autoAddModifiedAndDeleted() error {
  45. s, err := w.Status()
  46. if err != nil {
  47. return err
  48. }
  49. for path, fs := range s {
  50. if fs.Worktree != Modified && fs.Worktree != Deleted {
  51. continue
  52. }
  53. if _, err := w.Add(path); err != nil {
  54. return err
  55. }
  56. }
  57. return nil
  58. }
  59. func (w *Worktree) updateHEAD(commit plumbing.Hash) error {
  60. head, err := w.r.Storer.Reference(plumbing.HEAD)
  61. if err != nil {
  62. return err
  63. }
  64. name := plumbing.HEAD
  65. if head.Type() != plumbing.HashReference {
  66. name = head.Target()
  67. }
  68. ref := plumbing.NewHashReference(name, commit)
  69. return w.r.Storer.SetReference(ref)
  70. }
  71. func (w *Worktree) buildCommitObject(msg string, opts *CommitOptions, tree plumbing.Hash) (plumbing.Hash, error) {
  72. commit := &object.Commit{
  73. Author: *opts.Author,
  74. Committer: *opts.Committer,
  75. Message: msg,
  76. TreeHash: tree,
  77. ParentHashes: opts.Parents,
  78. }
  79. if opts.SignKey != nil {
  80. sig, err := w.buildCommitSignature(commit, opts.SignKey)
  81. if err != nil {
  82. return plumbing.ZeroHash, err
  83. }
  84. commit.PGPSignature = sig
  85. }
  86. obj := w.r.Storer.NewEncodedObject()
  87. if err := commit.Encode(obj); err != nil {
  88. return plumbing.ZeroHash, err
  89. }
  90. return w.r.Storer.SetEncodedObject(obj)
  91. }
  92. func (w *Worktree) buildCommitSignature(commit *object.Commit, signKey *openpgp.Entity) (string, error) {
  93. encoded := &plumbing.MemoryObject{}
  94. if err := commit.Encode(encoded); err != nil {
  95. return "", err
  96. }
  97. r, err := encoded.Reader()
  98. if err != nil {
  99. return "", err
  100. }
  101. var b bytes.Buffer
  102. if err := openpgp.ArmoredDetachSign(&b, signKey, r, nil); err != nil {
  103. return "", err
  104. }
  105. return b.String(), nil
  106. }
  107. // buildTreeHelper converts a given index.Index file into multiple git objects
  108. // reading the blobs from the given filesystem and creating the trees from the
  109. // index structure. The created objects are pushed to a given Storer.
  110. type buildTreeHelper struct {
  111. fs billy.Filesystem
  112. s storage.Storer
  113. trees map[string]*object.Tree
  114. entries map[string]*object.TreeEntry
  115. }
  116. // BuildTree builds the tree objects and push its to the storer, the hash
  117. // of the root tree is returned.
  118. func (h *buildTreeHelper) BuildTree(idx *index.Index) (plumbing.Hash, error) {
  119. const rootNode = ""
  120. h.trees = map[string]*object.Tree{rootNode: {}}
  121. h.entries = map[string]*object.TreeEntry{}
  122. for _, e := range idx.Entries {
  123. if err := h.commitIndexEntry(e); err != nil {
  124. return plumbing.ZeroHash, err
  125. }
  126. }
  127. return h.copyTreeToStorageRecursive(rootNode, h.trees[rootNode])
  128. }
  129. func (h *buildTreeHelper) commitIndexEntry(e *index.Entry) error {
  130. parts := strings.Split(e.Name, "/")
  131. var fullpath string
  132. for _, part := range parts {
  133. parent := fullpath
  134. fullpath = path.Join(fullpath, part)
  135. h.doBuildTree(e, parent, fullpath)
  136. }
  137. return nil
  138. }
  139. func (h *buildTreeHelper) doBuildTree(e *index.Entry, parent, fullpath string) {
  140. if _, ok := h.trees[fullpath]; ok {
  141. return
  142. }
  143. if _, ok := h.entries[fullpath]; ok {
  144. return
  145. }
  146. te := object.TreeEntry{Name: path.Base(fullpath)}
  147. if fullpath == e.Name {
  148. te.Mode = e.Mode
  149. te.Hash = e.Hash
  150. } else {
  151. te.Mode = filemode.Dir
  152. h.trees[fullpath] = &object.Tree{}
  153. }
  154. h.trees[parent].Entries = append(h.trees[parent].Entries, te)
  155. }
  156. type sortableEntries []object.TreeEntry
  157. func (sortableEntries) sortName(te object.TreeEntry) string {
  158. if te.Mode == filemode.Dir {
  159. return te.Name + "/"
  160. }
  161. return te.Name
  162. }
  163. func (se sortableEntries) Len() int { return len(se) }
  164. func (se sortableEntries) Less(i int, j int) bool { return se.sortName(se[i]) < se.sortName(se[j]) }
  165. func (se sortableEntries) Swap(i int, j int) { se[i], se[j] = se[j], se[i] }
  166. func (h *buildTreeHelper) copyTreeToStorageRecursive(parent string, t *object.Tree) (plumbing.Hash, error) {
  167. sort.Sort(sortableEntries(t.Entries))
  168. for i, e := range t.Entries {
  169. if e.Mode != filemode.Dir && !e.Hash.IsZero() {
  170. continue
  171. }
  172. path := path.Join(parent, e.Name)
  173. var err error
  174. e.Hash, err = h.copyTreeToStorageRecursive(path, h.trees[path])
  175. if err != nil {
  176. return plumbing.ZeroHash, err
  177. }
  178. t.Entries[i] = e
  179. }
  180. o := h.s.NewEncodedObject()
  181. if err := t.Encode(o); err != nil {
  182. return plumbing.ZeroHash, err
  183. }
  184. return h.s.SetEncodedObject(o)
  185. }
上海开阖软件有限公司 沪ICP备12045867号-1