Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

commit.go 8.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. // Copyright 2015 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package git
  5. import (
  6. "bufio"
  7. "bytes"
  8. "container/list"
  9. "fmt"
  10. "net/http"
  11. "strconv"
  12. "strings"
  13. )
  14. // Commit represents a git commit.
  15. type Commit struct {
  16. Tree
  17. ID SHA1 // The ID of this commit object
  18. Author *Signature
  19. Committer *Signature
  20. CommitMessage string
  21. Signature *CommitGPGSignature
  22. parents []SHA1 // SHA1 strings
  23. submoduleCache *ObjectCache
  24. }
  25. // CommitGPGSignature represents a git commit signature part.
  26. type CommitGPGSignature struct {
  27. Signature string
  28. Payload string //TODO check if can be reconstruct from the rest of commit information to not have duplicate data
  29. }
  30. // similar to https://github.com/git/git/blob/3bc53220cb2dcf709f7a027a3f526befd021d858/commit.c#L1128
  31. func newGPGSignatureFromCommitline(data []byte, signatureStart int, tag bool) (*CommitGPGSignature, error) {
  32. sig := new(CommitGPGSignature)
  33. signatureEnd := bytes.LastIndex(data, []byte("-----END PGP SIGNATURE-----"))
  34. if signatureEnd == -1 {
  35. return nil, fmt.Errorf("end of commit signature not found")
  36. }
  37. sig.Signature = strings.Replace(string(data[signatureStart:signatureEnd+27]), "\n ", "\n", -1)
  38. if tag {
  39. sig.Payload = string(data[:signatureStart-1])
  40. } else {
  41. sig.Payload = string(data[:signatureStart-8]) + string(data[signatureEnd+27:])
  42. }
  43. return sig, nil
  44. }
  45. // Message returns the commit message. Same as retrieving CommitMessage directly.
  46. func (c *Commit) Message() string {
  47. return c.CommitMessage
  48. }
  49. // Summary returns first line of commit message.
  50. func (c *Commit) Summary() string {
  51. return strings.Split(strings.TrimSpace(c.CommitMessage), "\n")[0]
  52. }
  53. // ParentID returns oid of n-th parent (0-based index).
  54. // It returns nil if no such parent exists.
  55. func (c *Commit) ParentID(n int) (SHA1, error) {
  56. if n >= len(c.parents) {
  57. return SHA1{}, ErrNotExist{"", ""}
  58. }
  59. return c.parents[n], nil
  60. }
  61. // Parent returns n-th parent (0-based index) of the commit.
  62. func (c *Commit) Parent(n int) (*Commit, error) {
  63. id, err := c.ParentID(n)
  64. if err != nil {
  65. return nil, err
  66. }
  67. parent, err := c.repo.getCommit(id)
  68. if err != nil {
  69. return nil, err
  70. }
  71. return parent, nil
  72. }
  73. // ParentCount returns number of parents of the commit.
  74. // 0 if this is the root commit, otherwise 1,2, etc.
  75. func (c *Commit) ParentCount() int {
  76. return len(c.parents)
  77. }
  78. func isImageFile(data []byte) (string, bool) {
  79. contentType := http.DetectContentType(data)
  80. if strings.Index(contentType, "image/") != -1 {
  81. return contentType, true
  82. }
  83. return contentType, false
  84. }
  85. // IsImageFile is a file image type
  86. func (c *Commit) IsImageFile(name string) bool {
  87. blob, err := c.GetBlobByPath(name)
  88. if err != nil {
  89. return false
  90. }
  91. dataRc, err := blob.DataAsync()
  92. if err != nil {
  93. return false
  94. }
  95. defer dataRc.Close()
  96. buf := make([]byte, 1024)
  97. n, _ := dataRc.Read(buf)
  98. buf = buf[:n]
  99. _, isImage := isImageFile(buf)
  100. return isImage
  101. }
  102. // GetCommitByPath return the commit of relative path object.
  103. func (c *Commit) GetCommitByPath(relpath string) (*Commit, error) {
  104. return c.repo.getCommitByPathWithID(c.ID, relpath)
  105. }
  106. // AddChanges marks local changes to be ready for commit.
  107. func AddChanges(repoPath string, all bool, files ...string) error {
  108. cmd := NewCommand("add")
  109. if all {
  110. cmd.AddArguments("--all")
  111. }
  112. _, err := cmd.AddArguments(files...).RunInDir(repoPath)
  113. return err
  114. }
  115. // CommitChangesOptions the options when a commit created
  116. type CommitChangesOptions struct {
  117. Committer *Signature
  118. Author *Signature
  119. Message string
  120. }
  121. // CommitChanges commits local changes with given committer, author and message.
  122. // If author is nil, it will be the same as committer.
  123. func CommitChanges(repoPath string, opts CommitChangesOptions) error {
  124. cmd := NewCommand()
  125. if opts.Committer != nil {
  126. cmd.AddArguments("-c", "user.name="+opts.Committer.Name, "-c", "user.email="+opts.Committer.Email)
  127. }
  128. cmd.AddArguments("commit")
  129. if opts.Author == nil {
  130. opts.Author = opts.Committer
  131. }
  132. if opts.Author != nil {
  133. cmd.AddArguments(fmt.Sprintf("--author='%s <%s>'", opts.Author.Name, opts.Author.Email))
  134. }
  135. cmd.AddArguments("-m", opts.Message)
  136. _, err := cmd.RunInDir(repoPath)
  137. // No stderr but exit status 1 means nothing to commit.
  138. if err != nil && err.Error() == "exit status 1" {
  139. return nil
  140. }
  141. return err
  142. }
  143. func commitsCount(repoPath, revision, relpath string) (int64, error) {
  144. var cmd *Command
  145. cmd = NewCommand("rev-list", "--count")
  146. cmd.AddArguments(revision)
  147. if len(relpath) > 0 {
  148. cmd.AddArguments("--", relpath)
  149. }
  150. stdout, err := cmd.RunInDir(repoPath)
  151. if err != nil {
  152. return 0, err
  153. }
  154. return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
  155. }
  156. // CommitsCount returns number of total commits of until given revision.
  157. func CommitsCount(repoPath, revision string) (int64, error) {
  158. return commitsCount(repoPath, revision, "")
  159. }
  160. // CommitsCount returns number of total commits of until current revision.
  161. func (c *Commit) CommitsCount() (int64, error) {
  162. return CommitsCount(c.repo.Path, c.ID.String())
  163. }
  164. // CommitsByRange returns the specific page commits before current revision, every page's number default by CommitsRangeSize
  165. func (c *Commit) CommitsByRange(page int) (*list.List, error) {
  166. return c.repo.commitsByRange(c.ID, page)
  167. }
  168. // CommitsBefore returns all the commits before current revision
  169. func (c *Commit) CommitsBefore() (*list.List, error) {
  170. return c.repo.getCommitsBefore(c.ID)
  171. }
  172. // CommitsBeforeLimit returns num commits before current revision
  173. func (c *Commit) CommitsBeforeLimit(num int) (*list.List, error) {
  174. return c.repo.getCommitsBeforeLimit(c.ID, num)
  175. }
  176. // CommitsBeforeUntil returns the commits between commitID to current revision
  177. func (c *Commit) CommitsBeforeUntil(commitID string) (*list.List, error) {
  178. endCommit, err := c.repo.GetCommit(commitID)
  179. if err != nil {
  180. return nil, err
  181. }
  182. return c.repo.CommitsBetween(c, endCommit)
  183. }
  184. // SearchCommits returns the commits match the keyword before current revision
  185. func (c *Commit) SearchCommits(keyword string, all bool) (*list.List, error) {
  186. return c.repo.searchCommits(c.ID, keyword, all)
  187. }
  188. // GetFilesChangedSinceCommit get all changed file names between pastCommit to current revision
  189. func (c *Commit) GetFilesChangedSinceCommit(pastCommit string) ([]string, error) {
  190. return c.repo.getFilesChanged(pastCommit, c.ID.String())
  191. }
  192. // GetSubModules get all the sub modules of current revision git tree
  193. func (c *Commit) GetSubModules() (*ObjectCache, error) {
  194. if c.submoduleCache != nil {
  195. return c.submoduleCache, nil
  196. }
  197. entry, err := c.GetTreeEntryByPath(".gitmodules")
  198. if err != nil {
  199. if _, ok := err.(ErrNotExist); ok {
  200. return nil, nil
  201. }
  202. return nil, err
  203. }
  204. rd, err := entry.Blob().Data()
  205. if err != nil {
  206. return nil, err
  207. }
  208. scanner := bufio.NewScanner(rd)
  209. c.submoduleCache = newObjectCache()
  210. var ismodule bool
  211. var path string
  212. for scanner.Scan() {
  213. if strings.HasPrefix(scanner.Text(), "[submodule") {
  214. ismodule = true
  215. continue
  216. }
  217. if ismodule {
  218. fields := strings.Split(scanner.Text(), "=")
  219. k := strings.TrimSpace(fields[0])
  220. if k == "path" {
  221. path = strings.TrimSpace(fields[1])
  222. } else if k == "url" {
  223. c.submoduleCache.Set(path, &SubModule{path, strings.TrimSpace(fields[1])})
  224. ismodule = false
  225. }
  226. }
  227. }
  228. return c.submoduleCache, nil
  229. }
  230. // GetSubModule get the sub module according entryname
  231. func (c *Commit) GetSubModule(entryname string) (*SubModule, error) {
  232. modules, err := c.GetSubModules()
  233. if err != nil {
  234. return nil, err
  235. }
  236. if modules != nil {
  237. module, has := modules.Get(entryname)
  238. if has {
  239. return module.(*SubModule), nil
  240. }
  241. }
  242. return nil, nil
  243. }
  244. // GetFullCommitID returns full length (40) of commit ID by given short SHA in a repository.
  245. func GetFullCommitID(repoPath, shortID string) (string, error) {
  246. if len(shortID) >= 40 {
  247. return shortID, nil
  248. }
  249. commitID, err := NewCommand("rev-parse", shortID).RunInDir(repoPath)
  250. if err != nil {
  251. if strings.Contains(err.Error(), "exit status 128") {
  252. return "", ErrNotExist{shortID, ""}
  253. }
  254. return "", err
  255. }
  256. return strings.TrimSpace(commitID), nil
  257. }