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.

254 lines
8.4 KiB

  1. // Copyright 2017 Gitea. 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 models
  5. import (
  6. "fmt"
  7. "strings"
  8. "time"
  9. "code.gitea.io/git"
  10. "code.gitea.io/gitea/modules/log"
  11. "code.gitea.io/gitea/modules/setting"
  12. api "code.gitea.io/sdk/gitea"
  13. "github.com/go-xorm/xorm"
  14. )
  15. // CommitStatusState holds the state of a Status
  16. // It can be "pending", "success", "error", "failure", and "warning"
  17. type CommitStatusState string
  18. // IsWorseThan returns true if this State is worse than the given State
  19. func (css CommitStatusState) IsWorseThan(css2 CommitStatusState) bool {
  20. switch css {
  21. case CommitStatusError:
  22. return true
  23. case CommitStatusFailure:
  24. return css2 != CommitStatusError
  25. case CommitStatusWarning:
  26. return css2 != CommitStatusError && css2 != CommitStatusFailure
  27. case CommitStatusSuccess:
  28. return css2 != CommitStatusError && css2 != CommitStatusFailure && css2 != CommitStatusWarning
  29. default:
  30. return css2 != CommitStatusError && css2 != CommitStatusFailure && css2 != CommitStatusWarning && css2 != CommitStatusSuccess
  31. }
  32. }
  33. const (
  34. // CommitStatusPending is for when the Status is Pending
  35. CommitStatusPending CommitStatusState = "pending"
  36. // CommitStatusSuccess is for when the Status is Success
  37. CommitStatusSuccess CommitStatusState = "success"
  38. // CommitStatusError is for when the Status is Error
  39. CommitStatusError CommitStatusState = "error"
  40. // CommitStatusFailure is for when the Status is Failure
  41. CommitStatusFailure CommitStatusState = "failure"
  42. // CommitStatusWarning is for when the Status is Warning
  43. CommitStatusWarning CommitStatusState = "warning"
  44. )
  45. // CommitStatus holds a single Status of a single Commit
  46. type CommitStatus struct {
  47. ID int64 `xorm:"pk autoincr"`
  48. Index int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
  49. RepoID int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
  50. Repo *Repository `xorm:"-"`
  51. State CommitStatusState `xorm:"VARCHAR(7) NOT NULL"`
  52. SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"`
  53. TargetURL string `xorm:"TEXT"`
  54. Description string `xorm:"TEXT"`
  55. Context string `xorm:"TEXT"`
  56. Creator *User `xorm:"-"`
  57. CreatorID int64
  58. Created time.Time `xorm:"-"`
  59. CreatedUnix int64 `xorm:"INDEX"`
  60. Updated time.Time `xorm:"-"`
  61. UpdatedUnix int64 `xorm:"INDEX"`
  62. }
  63. // BeforeInsert is invoked from XORM before inserting an object of this type.
  64. func (status *CommitStatus) BeforeInsert() {
  65. status.CreatedUnix = time.Now().Unix()
  66. status.UpdatedUnix = status.CreatedUnix
  67. }
  68. // BeforeUpdate is invoked from XORM before updating this object.
  69. func (status *CommitStatus) BeforeUpdate() {
  70. status.UpdatedUnix = time.Now().Unix()
  71. }
  72. // AfterSet is invoked from XORM after setting the value of a field of
  73. // this object.
  74. func (status *CommitStatus) AfterSet(colName string, _ xorm.Cell) {
  75. switch colName {
  76. case "created_unix":
  77. status.Created = time.Unix(status.CreatedUnix, 0).Local()
  78. case "updated_unix":
  79. status.Updated = time.Unix(status.UpdatedUnix, 0).Local()
  80. }
  81. }
  82. func (status *CommitStatus) loadRepo(e Engine) (err error) {
  83. if status.Repo == nil {
  84. status.Repo, err = getRepositoryByID(e, status.RepoID)
  85. if err != nil {
  86. return fmt.Errorf("getRepositoryByID [%d]: %v", status.RepoID, err)
  87. }
  88. }
  89. if status.Creator == nil && status.CreatorID > 0 {
  90. status.Creator, err = getUserByID(e, status.CreatorID)
  91. if err != nil {
  92. return fmt.Errorf("getUserByID [%d]: %v", status.CreatorID, err)
  93. }
  94. }
  95. return nil
  96. }
  97. // APIURL returns the absolute APIURL to this commit-status.
  98. func (status *CommitStatus) APIURL() string {
  99. status.loadRepo(x)
  100. return fmt.Sprintf("%sapi/v1/%s/statuses/%s",
  101. setting.AppURL, status.Repo.FullName(), status.SHA)
  102. }
  103. // APIFormat assumes some fields assigned with values:
  104. // Required - Repo, Creator
  105. func (status *CommitStatus) APIFormat() *api.Status {
  106. status.loadRepo(x)
  107. apiStatus := &api.Status{
  108. Created: status.Created,
  109. Updated: status.Created,
  110. State: api.StatusState(status.State),
  111. TargetURL: status.TargetURL,
  112. Description: status.Description,
  113. ID: status.Index,
  114. URL: status.APIURL(),
  115. Context: status.Context,
  116. }
  117. if status.Creator != nil {
  118. apiStatus.Creator = status.Creator.APIFormat()
  119. }
  120. return apiStatus
  121. }
  122. // GetCommitStatuses returns all statuses for a given commit.
  123. func GetCommitStatuses(repo *Repository, sha string, page int) ([]*CommitStatus, error) {
  124. statuses := make([]*CommitStatus, 0, 10)
  125. return statuses, x.Limit(10, page*10).Where("repo_id = ?", repo.ID).And("sha = ?", sha).Find(&statuses)
  126. }
  127. // GetLatestCommitStatus returns all statuses with a unique context for a given commit.
  128. func GetLatestCommitStatus(repo *Repository, sha string, page int) ([]*CommitStatus, error) {
  129. statuses := make([]*CommitStatus, 0, 10)
  130. return statuses, x.Limit(10, page*10).
  131. Where("repo_id = ?", repo.ID).And("sha = ?", sha).Select("*").
  132. GroupBy("context").Desc("created_unix").Find(&statuses)
  133. }
  134. // GetCommitStatus populates a given status for a given commit.
  135. // NOTE: If ID or Index isn't given, and only Context, TargetURL and/or Description
  136. // is given, the CommitStatus created _last_ will be returned.
  137. func GetCommitStatus(repo *Repository, sha string, status *CommitStatus) (*CommitStatus, error) {
  138. conds := &CommitStatus{
  139. Context: status.Context,
  140. State: status.State,
  141. TargetURL: status.TargetURL,
  142. Description: status.Description,
  143. }
  144. has, err := x.Where("repo_id = ?", repo.ID).And("sha = ?", sha).Desc("created_unix").Get(conds)
  145. if err != nil {
  146. return nil, fmt.Errorf("GetCommitStatus[%s, %s]: %v", repo.RepoPath(), sha, err)
  147. }
  148. if !has {
  149. return nil, fmt.Errorf("GetCommitStatus[%s, %s]: not found", repo.RepoPath(), sha)
  150. }
  151. return conds, nil
  152. }
  153. // NewCommitStatusOptions holds options for creating a CommitStatus
  154. type NewCommitStatusOptions struct {
  155. Repo *Repository
  156. Creator *User
  157. SHA string
  158. CommitStatus *CommitStatus
  159. }
  160. func newCommitStatus(sess *xorm.Session, opts NewCommitStatusOptions) error {
  161. opts.CommitStatus.Description = strings.TrimSpace(opts.CommitStatus.Description)
  162. opts.CommitStatus.Context = strings.TrimSpace(opts.CommitStatus.Context)
  163. opts.CommitStatus.TargetURL = strings.TrimSpace(opts.CommitStatus.TargetURL)
  164. opts.CommitStatus.SHA = opts.SHA
  165. opts.CommitStatus.CreatorID = opts.Creator.ID
  166. if opts.Repo == nil {
  167. return fmt.Errorf("newCommitStatus[nil, %s]: no repository specified", opts.SHA)
  168. }
  169. opts.CommitStatus.RepoID = opts.Repo.ID
  170. if opts.Creator == nil {
  171. return fmt.Errorf("newCommitStatus[%s, %s]: no user specified", opts.Repo.RepoPath(), opts.SHA)
  172. }
  173. gitRepo, err := git.OpenRepository(opts.Repo.RepoPath())
  174. if err != nil {
  175. return fmt.Errorf("OpenRepository[%s]: %v", opts.Repo.RepoPath(), err)
  176. }
  177. if _, err := gitRepo.GetCommit(opts.SHA); err != nil {
  178. return fmt.Errorf("GetCommit[%s]: %v", opts.SHA, err)
  179. }
  180. // Get the next Status Index
  181. var nextIndex int64
  182. lastCommitStatus := &CommitStatus{
  183. SHA: opts.SHA,
  184. RepoID: opts.Repo.ID,
  185. }
  186. has, err := sess.Desc("index").Limit(1).Get(lastCommitStatus)
  187. if err != nil {
  188. sess.Rollback()
  189. return fmt.Errorf("newCommitStatus[%s, %s]: %v", opts.Repo.RepoPath(), opts.SHA, err)
  190. }
  191. if has {
  192. log.Debug("newCommitStatus[%s, %s]: found", opts.Repo.RepoPath(), opts.SHA)
  193. nextIndex = lastCommitStatus.Index
  194. }
  195. opts.CommitStatus.Index = nextIndex + 1
  196. log.Debug("newCommitStatus[%s, %s]: %d", opts.Repo.RepoPath(), opts.SHA, opts.CommitStatus.Index)
  197. // Insert new CommitStatus
  198. if _, err = sess.Insert(opts.CommitStatus); err != nil {
  199. sess.Rollback()
  200. return fmt.Errorf("newCommitStatus[%s, %s]: %v", opts.Repo.RepoPath(), opts.SHA, err)
  201. }
  202. return nil
  203. }
  204. // NewCommitStatus creates a new CommitStatus given a bunch of parameters
  205. // NOTE: All text-values will be trimmed from whitespaces.
  206. // Requires: Repo, Creator, SHA
  207. func NewCommitStatus(repo *Repository, creator *User, sha string, status *CommitStatus) error {
  208. sess := x.NewSession()
  209. defer sess.Close()
  210. if err := sess.Begin(); err != nil {
  211. return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %v", repo.ID, creator.ID, sha, err)
  212. }
  213. if err := newCommitStatus(sess, NewCommitStatusOptions{
  214. Repo: repo,
  215. Creator: creator,
  216. SHA: sha,
  217. CommitStatus: status,
  218. }); err != nil {
  219. return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %v", repo.ID, creator.ID, sha, err)
  220. }
  221. return sess.Commit()
  222. }