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.

dependency.go 6.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. // Copyright 2018 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package issues
  4. import (
  5. "context"
  6. "fmt"
  7. "code.gitea.io/gitea/models/db"
  8. user_model "code.gitea.io/gitea/models/user"
  9. "code.gitea.io/gitea/modules/timeutil"
  10. "code.gitea.io/gitea/modules/util"
  11. )
  12. // ErrDependencyExists represents a "DependencyAlreadyExists" kind of error.
  13. type ErrDependencyExists struct {
  14. IssueID int64
  15. DependencyID int64
  16. }
  17. // IsErrDependencyExists checks if an error is a ErrDependencyExists.
  18. func IsErrDependencyExists(err error) bool {
  19. _, ok := err.(ErrDependencyExists)
  20. return ok
  21. }
  22. func (err ErrDependencyExists) Error() string {
  23. return fmt.Sprintf("issue dependency does already exist [issue id: %d, dependency id: %d]", err.IssueID, err.DependencyID)
  24. }
  25. func (err ErrDependencyExists) Unwrap() error {
  26. return util.ErrAlreadyExist
  27. }
  28. // ErrDependencyNotExists represents a "DependencyAlreadyExists" kind of error.
  29. type ErrDependencyNotExists struct {
  30. IssueID int64
  31. DependencyID int64
  32. }
  33. // IsErrDependencyNotExists checks if an error is a ErrDependencyExists.
  34. func IsErrDependencyNotExists(err error) bool {
  35. _, ok := err.(ErrDependencyNotExists)
  36. return ok
  37. }
  38. func (err ErrDependencyNotExists) Error() string {
  39. return fmt.Sprintf("issue dependency does not exist [issue id: %d, dependency id: %d]", err.IssueID, err.DependencyID)
  40. }
  41. func (err ErrDependencyNotExists) Unwrap() error {
  42. return util.ErrNotExist
  43. }
  44. // ErrCircularDependency represents a "DependencyCircular" kind of error.
  45. type ErrCircularDependency struct {
  46. IssueID int64
  47. DependencyID int64
  48. }
  49. // IsErrCircularDependency checks if an error is a ErrCircularDependency.
  50. func IsErrCircularDependency(err error) bool {
  51. _, ok := err.(ErrCircularDependency)
  52. return ok
  53. }
  54. func (err ErrCircularDependency) Error() string {
  55. return fmt.Sprintf("circular dependencies exists (two issues blocking each other) [issue id: %d, dependency id: %d]", err.IssueID, err.DependencyID)
  56. }
  57. // ErrDependenciesLeft represents an error where the issue you're trying to close still has dependencies left.
  58. type ErrDependenciesLeft struct {
  59. IssueID int64
  60. }
  61. // IsErrDependenciesLeft checks if an error is a ErrDependenciesLeft.
  62. func IsErrDependenciesLeft(err error) bool {
  63. _, ok := err.(ErrDependenciesLeft)
  64. return ok
  65. }
  66. func (err ErrDependenciesLeft) Error() string {
  67. return fmt.Sprintf("issue has open dependencies [issue id: %d]", err.IssueID)
  68. }
  69. // ErrUnknownDependencyType represents an error where an unknown dependency type was passed
  70. type ErrUnknownDependencyType struct {
  71. Type DependencyType
  72. }
  73. // IsErrUnknownDependencyType checks if an error is ErrUnknownDependencyType
  74. func IsErrUnknownDependencyType(err error) bool {
  75. _, ok := err.(ErrUnknownDependencyType)
  76. return ok
  77. }
  78. func (err ErrUnknownDependencyType) Error() string {
  79. return fmt.Sprintf("unknown dependency type [type: %d]", err.Type)
  80. }
  81. func (err ErrUnknownDependencyType) Unwrap() error {
  82. return util.ErrInvalidArgument
  83. }
  84. // IssueDependency represents an issue dependency
  85. type IssueDependency struct {
  86. ID int64 `xorm:"pk autoincr"`
  87. UserID int64 `xorm:"NOT NULL"`
  88. IssueID int64 `xorm:"UNIQUE(issue_dependency) NOT NULL"`
  89. DependencyID int64 `xorm:"UNIQUE(issue_dependency) NOT NULL"`
  90. CreatedUnix timeutil.TimeStamp `xorm:"created"`
  91. UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
  92. }
  93. func init() {
  94. db.RegisterModel(new(IssueDependency))
  95. }
  96. // DependencyType Defines Dependency Type Constants
  97. type DependencyType int
  98. // Define Dependency Types
  99. const (
  100. DependencyTypeBlockedBy DependencyType = iota
  101. DependencyTypeBlocking
  102. )
  103. // CreateIssueDependency creates a new dependency for an issue
  104. func CreateIssueDependency(user *user_model.User, issue, dep *Issue) error {
  105. ctx, committer, err := db.TxContext(db.DefaultContext)
  106. if err != nil {
  107. return err
  108. }
  109. defer committer.Close()
  110. // Check if it aleready exists
  111. exists, err := issueDepExists(ctx, issue.ID, dep.ID)
  112. if err != nil {
  113. return err
  114. }
  115. if exists {
  116. return ErrDependencyExists{issue.ID, dep.ID}
  117. }
  118. // And if it would be circular
  119. circular, err := issueDepExists(ctx, dep.ID, issue.ID)
  120. if err != nil {
  121. return err
  122. }
  123. if circular {
  124. return ErrCircularDependency{issue.ID, dep.ID}
  125. }
  126. if err := db.Insert(ctx, &IssueDependency{
  127. UserID: user.ID,
  128. IssueID: issue.ID,
  129. DependencyID: dep.ID,
  130. }); err != nil {
  131. return err
  132. }
  133. // Add comment referencing the new dependency
  134. if err = createIssueDependencyComment(ctx, user, issue, dep, true); err != nil {
  135. return err
  136. }
  137. return committer.Commit()
  138. }
  139. // RemoveIssueDependency removes a dependency from an issue
  140. func RemoveIssueDependency(user *user_model.User, issue, dep *Issue, depType DependencyType) (err error) {
  141. ctx, committer, err := db.TxContext(db.DefaultContext)
  142. if err != nil {
  143. return err
  144. }
  145. defer committer.Close()
  146. var issueDepToDelete IssueDependency
  147. switch depType {
  148. case DependencyTypeBlockedBy:
  149. issueDepToDelete = IssueDependency{IssueID: issue.ID, DependencyID: dep.ID}
  150. case DependencyTypeBlocking:
  151. issueDepToDelete = IssueDependency{IssueID: dep.ID, DependencyID: issue.ID}
  152. default:
  153. return ErrUnknownDependencyType{depType}
  154. }
  155. affected, err := db.GetEngine(ctx).Delete(&issueDepToDelete)
  156. if err != nil {
  157. return err
  158. }
  159. // If we deleted nothing, the dependency did not exist
  160. if affected <= 0 {
  161. return ErrDependencyNotExists{issue.ID, dep.ID}
  162. }
  163. // Add comment referencing the removed dependency
  164. if err = createIssueDependencyComment(ctx, user, issue, dep, false); err != nil {
  165. return err
  166. }
  167. return committer.Commit()
  168. }
  169. // Check if the dependency already exists
  170. func issueDepExists(ctx context.Context, issueID, depID int64) (bool, error) {
  171. return db.GetEngine(ctx).Where("(issue_id = ? AND dependency_id = ?)", issueID, depID).Exist(&IssueDependency{})
  172. }
  173. // IssueNoDependenciesLeft checks if issue can be closed
  174. func IssueNoDependenciesLeft(ctx context.Context, issue *Issue) (bool, error) {
  175. exists, err := db.GetEngine(ctx).
  176. Table("issue_dependency").
  177. Select("issue.*").
  178. Join("INNER", "issue", "issue.id = issue_dependency.dependency_id").
  179. Where("issue_dependency.issue_id = ?", issue.ID).
  180. And("issue.is_closed = ?", "0").
  181. Exist(&Issue{})
  182. return !exists, err
  183. }