2018-07-18 00:23:58 +03:00
// Copyright 2018 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2018-07-18 00:23:58 +03:00
2022-06-13 12:37:59 +03:00
package issues
2018-07-18 00:23:58 +03:00
import (
2022-05-03 22:46:28 +03:00
"context"
2022-06-13 12:37:59 +03:00
"fmt"
2022-05-03 22:46:28 +03:00
2021-09-19 14:49:59 +03:00
"code.gitea.io/gitea/models/db"
2021-11-24 12:49:20 +03:00
user_model "code.gitea.io/gitea/models/user"
2019-08-15 17:46:21 +03:00
"code.gitea.io/gitea/modules/timeutil"
2022-10-18 08:50:37 +03:00
"code.gitea.io/gitea/modules/util"
2018-07-18 00:23:58 +03:00
)
2022-06-13 12:37:59 +03:00
// ErrDependencyExists represents a "DependencyAlreadyExists" kind of error.
type ErrDependencyExists struct {
IssueID int64
DependencyID int64
}
// IsErrDependencyExists checks if an error is a ErrDependencyExists.
func IsErrDependencyExists ( err error ) bool {
_ , ok := err . ( ErrDependencyExists )
return ok
}
func ( err ErrDependencyExists ) Error ( ) string {
return fmt . Sprintf ( "issue dependency does already exist [issue id: %d, dependency id: %d]" , err . IssueID , err . DependencyID )
}
2022-10-18 08:50:37 +03:00
func ( err ErrDependencyExists ) Unwrap ( ) error {
return util . ErrAlreadyExist
}
2022-06-13 12:37:59 +03:00
// ErrDependencyNotExists represents a "DependencyAlreadyExists" kind of error.
type ErrDependencyNotExists struct {
IssueID int64
DependencyID int64
}
// IsErrDependencyNotExists checks if an error is a ErrDependencyExists.
func IsErrDependencyNotExists ( err error ) bool {
_ , ok := err . ( ErrDependencyNotExists )
return ok
}
func ( err ErrDependencyNotExists ) Error ( ) string {
return fmt . Sprintf ( "issue dependency does not exist [issue id: %d, dependency id: %d]" , err . IssueID , err . DependencyID )
}
2022-10-18 08:50:37 +03:00
func ( err ErrDependencyNotExists ) Unwrap ( ) error {
return util . ErrNotExist
}
2022-06-13 12:37:59 +03:00
// ErrCircularDependency represents a "DependencyCircular" kind of error.
type ErrCircularDependency struct {
IssueID int64
DependencyID int64
}
// IsErrCircularDependency checks if an error is a ErrCircularDependency.
func IsErrCircularDependency ( err error ) bool {
_ , ok := err . ( ErrCircularDependency )
return ok
}
func ( err ErrCircularDependency ) Error ( ) string {
return fmt . Sprintf ( "circular dependencies exists (two issues blocking each other) [issue id: %d, dependency id: %d]" , err . IssueID , err . DependencyID )
}
// ErrDependenciesLeft represents an error where the issue you're trying to close still has dependencies left.
type ErrDependenciesLeft struct {
IssueID int64
}
// IsErrDependenciesLeft checks if an error is a ErrDependenciesLeft.
func IsErrDependenciesLeft ( err error ) bool {
_ , ok := err . ( ErrDependenciesLeft )
return ok
}
func ( err ErrDependenciesLeft ) Error ( ) string {
return fmt . Sprintf ( "issue has open dependencies [issue id: %d]" , err . IssueID )
}
// ErrUnknownDependencyType represents an error where an unknown dependency type was passed
type ErrUnknownDependencyType struct {
Type DependencyType
}
// IsErrUnknownDependencyType checks if an error is ErrUnknownDependencyType
func IsErrUnknownDependencyType ( err error ) bool {
_ , ok := err . ( ErrUnknownDependencyType )
return ok
}
func ( err ErrUnknownDependencyType ) Error ( ) string {
return fmt . Sprintf ( "unknown dependency type [type: %d]" , err . Type )
}
2022-10-18 08:50:37 +03:00
func ( err ErrUnknownDependencyType ) Unwrap ( ) error {
return util . ErrInvalidArgument
}
2018-07-18 00:23:58 +03:00
// IssueDependency represents an issue dependency
type IssueDependency struct {
2019-08-15 17:46:21 +03:00
ID int64 ` xorm:"pk autoincr" `
UserID int64 ` xorm:"NOT NULL" `
IssueID int64 ` xorm:"UNIQUE(issue_dependency) NOT NULL" `
DependencyID int64 ` xorm:"UNIQUE(issue_dependency) NOT NULL" `
CreatedUnix timeutil . TimeStamp ` xorm:"created" `
UpdatedUnix timeutil . TimeStamp ` xorm:"updated" `
2018-07-18 00:23:58 +03:00
}
2021-09-19 14:49:59 +03:00
func init ( ) {
db . RegisterModel ( new ( IssueDependency ) )
}
2018-07-18 00:23:58 +03:00
// DependencyType Defines Dependency Type Constants
type DependencyType int
// Define Dependency Types
const (
DependencyTypeBlockedBy DependencyType = iota
DependencyTypeBlocking
)
// CreateIssueDependency creates a new dependency for an issue
2023-10-11 07:24:07 +03:00
func CreateIssueDependency ( ctx context . Context , user * user_model . User , issue , dep * Issue ) error {
ctx , committer , err := db . TxContext ( ctx )
2021-11-19 16:39:57 +03:00
if err != nil {
2018-07-18 00:23:58 +03:00
return err
}
2021-11-19 16:39:57 +03:00
defer committer . Close ( )
2018-07-18 00:23:58 +03:00
2023-03-28 20:23:25 +03:00
// Check if it already exists
2022-05-20 17:08:52 +03:00
exists , err := issueDepExists ( ctx , issue . ID , dep . ID )
2018-07-18 00:23:58 +03:00
if err != nil {
return err
}
if exists {
return ErrDependencyExists { issue . ID , dep . ID }
}
// And if it would be circular
2022-05-20 17:08:52 +03:00
circular , err := issueDepExists ( ctx , dep . ID , issue . ID )
2018-07-18 00:23:58 +03:00
if err != nil {
return err
}
if circular {
return ErrCircularDependency { issue . ID , dep . ID }
}
2021-11-21 18:41:00 +03:00
if err := db . Insert ( ctx , & IssueDependency {
2018-07-18 00:23:58 +03:00
UserID : user . ID ,
IssueID : issue . ID ,
DependencyID : dep . ID ,
} ) ; err != nil {
return err
}
// Add comment referencing the new dependency
2021-11-19 16:39:57 +03:00
if err = createIssueDependencyComment ( ctx , user , issue , dep , true ) ; err != nil {
2018-07-18 00:23:58 +03:00
return err
}
2021-11-19 16:39:57 +03:00
return committer . Commit ( )
2018-07-18 00:23:58 +03:00
}
// RemoveIssueDependency removes a dependency from an issue
2023-10-11 07:24:07 +03:00
func RemoveIssueDependency ( ctx context . Context , user * user_model . User , issue , dep * Issue , depType DependencyType ) ( err error ) {
ctx , committer , err := db . TxContext ( ctx )
2021-11-19 16:39:57 +03:00
if err != nil {
2018-07-18 00:23:58 +03:00
return err
}
2021-11-19 16:39:57 +03:00
defer committer . Close ( )
2018-07-18 00:23:58 +03:00
var issueDepToDelete IssueDependency
switch depType {
case DependencyTypeBlockedBy :
issueDepToDelete = IssueDependency { IssueID : issue . ID , DependencyID : dep . ID }
case DependencyTypeBlocking :
issueDepToDelete = IssueDependency { IssueID : dep . ID , DependencyID : issue . ID }
default :
return ErrUnknownDependencyType { depType }
}
2021-11-19 16:39:57 +03:00
affected , err := db . GetEngine ( ctx ) . Delete ( & issueDepToDelete )
2018-07-18 00:23:58 +03:00
if err != nil {
return err
}
// If we deleted nothing, the dependency did not exist
if affected <= 0 {
return ErrDependencyNotExists { issue . ID , dep . ID }
}
// Add comment referencing the removed dependency
2021-11-19 16:39:57 +03:00
if err = createIssueDependencyComment ( ctx , user , issue , dep , false ) ; err != nil {
2018-07-18 00:23:58 +03:00
return err
}
2021-11-19 16:39:57 +03:00
return committer . Commit ( )
2018-07-18 00:23:58 +03:00
}
// Check if the dependency already exists
2022-05-20 17:08:52 +03:00
func issueDepExists ( ctx context . Context , issueID , depID int64 ) ( bool , error ) {
return db . GetEngine ( ctx ) . Where ( "(issue_id = ? AND dependency_id = ?)" , issueID , depID ) . Exist ( & IssueDependency { } )
2018-07-18 00:23:58 +03:00
}
// IssueNoDependenciesLeft checks if issue can be closed
2022-05-03 22:46:28 +03:00
func IssueNoDependenciesLeft ( ctx context . Context , issue * Issue ) ( bool , error ) {
exists , err := db . GetEngine ( ctx ) .
2018-07-18 00:23:58 +03:00
Table ( "issue_dependency" ) .
Select ( "issue.*" ) .
Join ( "INNER" , "issue" , "issue.id = issue_dependency.dependency_id" ) .
Where ( "issue_dependency.issue_id = ?" , issue . ID ) .
And ( "issue.is_closed = ?" , "0" ) .
Exist ( & Issue { } )
return ! exists , err
}