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  }