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