// Copyright 2018-2022 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package models import ( "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/timeutil" ) // IssueParent represents an issue parent type IssueParent struct { ID int64 `xorm:"pk autoincr"` UserID int64 `xorm:"NOT NULL"` IssueID int64 `xorm:"UNIQUE(issue_parent) NOT NULL"` ParentID int64 `xorm:"UNIQUE(issue_parent) NOT NULL"` CreatedUnix timeutil.TimeStamp `xorm:"created"` UpdatedUnix timeutil.TimeStamp `xorm:"updated"` } func init() { db.RegisterModel(new(IssueParent)) } // ParentType Defines Parent Type Constants type ParentType int // Define Parent Types const ( ParentTypeFather ParentType = iota ParentTypeChild ) // CreateIssueParent creates a new parent for an issue func CreateIssueParent(user *user_model.User, issue, parent *Issue) error { ctx, committer, err := db.TxContext() if err != nil { return err } defer committer.Close() sess := db.GetEngine(ctx) // Check if it aleready exists exists, err := issueParentExists(sess, issue.ID, parent.ID) if err != nil { return err } if exists { return ErrParentExists{issue.ID, parent.ID} } // And if it would be circular circular, err := issueParentExists(sess, parent.ID, issue.ID) if err != nil { return err } if circular { return ErrCircularParent{issue.ID, parent.ID} } if err := db.Insert(ctx, &IssueParent{ UserID: user.ID, IssueID: issue.ID, ParentID: parent.ID, }); err != nil { return err } // Add comment referencing the new parent if err = createIssueParentComment(ctx, user, issue, parent, true); err != nil { return err } return committer.Commit() } // RemoveIssueParent removes a parent from an issue func RemoveIssueParent(user *user_model.User, issue, parent *Issue, parentType ParentType) (err error) { ctx, committer, err := db.TxContext() if err != nil { return err } defer committer.Close() var issueParentToDelete IssueParent switch parentType { case ParentTypeFather: issueParentToDelete = IssueParent{IssueID: issue.ID, ParentID: parent.ID} case ParentTypeChild: issueParentToDelete = IssueParent{IssueID: parent.ID, ParentID: issue.ID} default: return ErrUnknownParentType{parentType} } affected, err := db.GetEngine(ctx).Delete(&issueParentToDelete) if err != nil { return err } // If we deleted nothing, the parent did not exist if affected <= 0 { return ErrParentNotExists{issue.ID, parent.ID} } // Add comment referencing the removed parent if err = createIssueParentComment(ctx, user, issue, parent, false); err != nil { return err } return committer.Commit() } // Check if the parent already exists func issueParentExists(e db.Engine, issueID, depID int64) (bool, error) { return e.Where("(issue_id = ? AND parent_id = ?)", issueID, depID).Exist(&IssueParent{}) } // IssueNoParentsLeft checks if issue can be closed func IssueNoParentsLeft(issue *Issue) (bool, error) { return issueNoParentsLeft(db.GetEngine(db.DefaultContext), issue) } func issueNoParentsLeft(e db.Engine, issue *Issue) (bool, error) { exists, err := e. Table("issue_parent"). Select("issue.*"). Join("INNER", "issue", "issue.id = issue_parent.parent_id"). Where("issue_parent.issue_id = ?", issue.ID). And("issue.is_closed = ?", "0"). Exist(&Issue{}) return !exists, err }