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.

git_commit.go 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. // Copyright 2020 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package convert
  4. import (
  5. "context"
  6. "net/url"
  7. "time"
  8. repo_model "code.gitea.io/gitea/models/repo"
  9. user_model "code.gitea.io/gitea/models/user"
  10. ctx "code.gitea.io/gitea/modules/context"
  11. "code.gitea.io/gitea/modules/git"
  12. "code.gitea.io/gitea/modules/log"
  13. api "code.gitea.io/gitea/modules/structs"
  14. "code.gitea.io/gitea/modules/util"
  15. "code.gitea.io/gitea/services/gitdiff"
  16. )
  17. // ToCommitUser convert a git.Signature to an api.CommitUser
  18. func ToCommitUser(sig *git.Signature) *api.CommitUser {
  19. return &api.CommitUser{
  20. Identity: api.Identity{
  21. Name: sig.Name,
  22. Email: sig.Email,
  23. },
  24. Date: sig.When.UTC().Format(time.RFC3339),
  25. }
  26. }
  27. // ToCommitMeta convert a git.Tag to an api.CommitMeta
  28. func ToCommitMeta(repo *repo_model.Repository, tag *git.Tag) *api.CommitMeta {
  29. return &api.CommitMeta{
  30. SHA: tag.Object.String(),
  31. URL: util.URLJoin(repo.APIURL(), "git/commits", tag.ID.String()),
  32. Created: tag.Tagger.When,
  33. }
  34. }
  35. // ToPayloadCommit convert a git.Commit to api.PayloadCommit
  36. func ToPayloadCommit(ctx context.Context, repo *repo_model.Repository, c *git.Commit) *api.PayloadCommit {
  37. authorUsername := ""
  38. if author, err := user_model.GetUserByEmail(ctx, c.Author.Email); err == nil {
  39. authorUsername = author.Name
  40. } else if !user_model.IsErrUserNotExist(err) {
  41. log.Error("GetUserByEmail: %v", err)
  42. }
  43. committerUsername := ""
  44. if committer, err := user_model.GetUserByEmail(ctx, c.Committer.Email); err == nil {
  45. committerUsername = committer.Name
  46. } else if !user_model.IsErrUserNotExist(err) {
  47. log.Error("GetUserByEmail: %v", err)
  48. }
  49. return &api.PayloadCommit{
  50. ID: c.ID.String(),
  51. Message: c.Message(),
  52. URL: util.URLJoin(repo.HTMLURL(), "commit", c.ID.String()),
  53. Author: &api.PayloadUser{
  54. Name: c.Author.Name,
  55. Email: c.Author.Email,
  56. UserName: authorUsername,
  57. },
  58. Committer: &api.PayloadUser{
  59. Name: c.Committer.Name,
  60. Email: c.Committer.Email,
  61. UserName: committerUsername,
  62. },
  63. Timestamp: c.Author.When,
  64. Verification: ToVerification(ctx, c),
  65. }
  66. }
  67. type ToCommitOptions struct {
  68. Stat bool
  69. Verification bool
  70. Files bool
  71. }
  72. func ParseCommitOptions(ctx *ctx.APIContext) ToCommitOptions {
  73. return ToCommitOptions{
  74. Stat: ctx.FormString("stat") == "" || ctx.FormBool("stat"),
  75. Files: ctx.FormString("files") == "" || ctx.FormBool("files"),
  76. Verification: ctx.FormString("verification") == "" || ctx.FormBool("verification"),
  77. }
  78. }
  79. // ToCommit convert a git.Commit to api.Commit
  80. func ToCommit(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, commit *git.Commit, userCache map[string]*user_model.User, opts ToCommitOptions) (*api.Commit, error) {
  81. var apiAuthor, apiCommitter *api.User
  82. // Retrieve author and committer information
  83. var cacheAuthor *user_model.User
  84. var ok bool
  85. if userCache == nil {
  86. cacheAuthor = (*user_model.User)(nil)
  87. ok = false
  88. } else {
  89. cacheAuthor, ok = userCache[commit.Author.Email]
  90. }
  91. if ok {
  92. apiAuthor = ToUser(ctx, cacheAuthor, nil)
  93. } else {
  94. author, err := user_model.GetUserByEmail(ctx, commit.Author.Email)
  95. if err != nil && !user_model.IsErrUserNotExist(err) {
  96. return nil, err
  97. } else if err == nil {
  98. apiAuthor = ToUser(ctx, author, nil)
  99. if userCache != nil {
  100. userCache[commit.Author.Email] = author
  101. }
  102. }
  103. }
  104. var cacheCommitter *user_model.User
  105. if userCache == nil {
  106. cacheCommitter = (*user_model.User)(nil)
  107. ok = false
  108. } else {
  109. cacheCommitter, ok = userCache[commit.Committer.Email]
  110. }
  111. if ok {
  112. apiCommitter = ToUser(ctx, cacheCommitter, nil)
  113. } else {
  114. committer, err := user_model.GetUserByEmail(ctx, commit.Committer.Email)
  115. if err != nil && !user_model.IsErrUserNotExist(err) {
  116. return nil, err
  117. } else if err == nil {
  118. apiCommitter = ToUser(ctx, committer, nil)
  119. if userCache != nil {
  120. userCache[commit.Committer.Email] = committer
  121. }
  122. }
  123. }
  124. // Retrieve parent(s) of the commit
  125. apiParents := make([]*api.CommitMeta, commit.ParentCount())
  126. for i := 0; i < commit.ParentCount(); i++ {
  127. sha, _ := commit.ParentID(i)
  128. apiParents[i] = &api.CommitMeta{
  129. URL: repo.APIURL() + "/git/commits/" + url.PathEscape(sha.String()),
  130. SHA: sha.String(),
  131. }
  132. }
  133. res := &api.Commit{
  134. CommitMeta: &api.CommitMeta{
  135. URL: repo.APIURL() + "/git/commits/" + url.PathEscape(commit.ID.String()),
  136. SHA: commit.ID.String(),
  137. Created: commit.Committer.When,
  138. },
  139. HTMLURL: repo.HTMLURL() + "/commit/" + url.PathEscape(commit.ID.String()),
  140. RepoCommit: &api.RepoCommit{
  141. URL: repo.APIURL() + "/git/commits/" + url.PathEscape(commit.ID.String()),
  142. Author: &api.CommitUser{
  143. Identity: api.Identity{
  144. Name: commit.Author.Name,
  145. Email: commit.Author.Email,
  146. },
  147. Date: commit.Author.When.Format(time.RFC3339),
  148. },
  149. Committer: &api.CommitUser{
  150. Identity: api.Identity{
  151. Name: commit.Committer.Name,
  152. Email: commit.Committer.Email,
  153. },
  154. Date: commit.Committer.When.Format(time.RFC3339),
  155. },
  156. Message: commit.Message(),
  157. Tree: &api.CommitMeta{
  158. URL: repo.APIURL() + "/git/trees/" + url.PathEscape(commit.ID.String()),
  159. SHA: commit.ID.String(),
  160. Created: commit.Committer.When,
  161. },
  162. },
  163. Author: apiAuthor,
  164. Committer: apiCommitter,
  165. Parents: apiParents,
  166. }
  167. // Retrieve verification for commit
  168. if opts.Verification {
  169. res.RepoCommit.Verification = ToVerification(ctx, commit)
  170. }
  171. // Retrieve files affected by the commit
  172. if opts.Files {
  173. fileStatus, err := git.GetCommitFileStatus(gitRepo.Ctx, repo.RepoPath(), commit.ID.String())
  174. if err != nil {
  175. return nil, err
  176. }
  177. affectedFileList := make([]*api.CommitAffectedFiles, 0, len(fileStatus.Added)+len(fileStatus.Removed)+len(fileStatus.Modified))
  178. for filestatus, files := range map[string][]string{"added": fileStatus.Added, "removed": fileStatus.Removed, "modified": fileStatus.Modified} {
  179. for _, filename := range files {
  180. affectedFileList = append(affectedFileList, &api.CommitAffectedFiles{
  181. Filename: filename,
  182. Status: filestatus,
  183. })
  184. }
  185. }
  186. res.Files = affectedFileList
  187. }
  188. // Get diff stats for commit
  189. if opts.Stat {
  190. diff, err := gitdiff.GetDiff(gitRepo, &gitdiff.DiffOptions{
  191. AfterCommitID: commit.ID.String(),
  192. })
  193. if err != nil {
  194. return nil, err
  195. }
  196. res.Stats = &api.CommitStats{
  197. Total: diff.TotalAddition + diff.TotalDeletion,
  198. Additions: diff.TotalAddition,
  199. Deletions: diff.TotalDeletion,
  200. }
  201. }
  202. return res, nil
  203. }