2019-09-30 16:50:44 +03:00
// Copyright 2019 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 issue
import (
2022-03-01 03:20:15 +03:00
"fmt"
2022-08-25 05:31:57 +03:00
activities_model "code.gitea.io/gitea/models/activities"
2021-09-19 14:49:59 +03:00
"code.gitea.io/gitea/models/db"
2022-06-13 12:37:59 +03:00
issues_model "code.gitea.io/gitea/models/issues"
2022-05-11 13:09:36 +03:00
access_model "code.gitea.io/gitea/models/perm/access"
2022-06-13 12:37:59 +03:00
project_model "code.gitea.io/gitea/models/project"
2021-12-10 04:27:50 +03:00
repo_model "code.gitea.io/gitea/models/repo"
2022-10-17 02:29:26 +03:00
system_model "code.gitea.io/gitea/models/system"
2021-11-24 12:49:20 +03:00
user_model "code.gitea.io/gitea/models/user"
2020-05-15 01:55:43 +03:00
"code.gitea.io/gitea/modules/git"
2019-10-25 17:46:37 +03:00
"code.gitea.io/gitea/modules/notification"
2022-06-13 12:37:59 +03:00
"code.gitea.io/gitea/modules/storage"
2020-05-15 01:55:43 +03:00
"code.gitea.io/gitea/modules/util"
2019-09-30 16:50:44 +03:00
)
// NewIssue creates new issue with labels for repository.
2022-06-13 12:37:59 +03:00
func NewIssue ( repo * repo_model . Repository , issue * issues_model . Issue , labelIDs [ ] int64 , uuids [ ] string , assigneeIDs [ ] int64 ) error {
if err := issues_model . NewIssue ( repo , issue , labelIDs , uuids ) ; err != nil {
2019-09-30 16:50:44 +03:00
return err
}
2019-10-28 19:45:43 +03:00
for _ , assigneeID := range assigneeIDs {
if err := AddAssigneeIfNotAssigned ( issue , issue . Poster , assigneeID ) ; err != nil {
return err
}
}
2022-06-13 12:37:59 +03:00
mentions , err := issues_model . FindAndUpdateIssueMentions ( db . DefaultContext , issue , issue . Poster , issue . Content )
2021-01-02 20:04:02 +03:00
if err != nil {
return err
}
notification . NotifyNewIssue ( issue , mentions )
2021-01-17 17:15:57 +03:00
if len ( issue . Labels ) > 0 {
notification . NotifyIssueChangeLabels ( issue . Poster , issue , issue . Labels , nil )
}
if issue . Milestone != nil {
notification . NotifyIssueChangeMilestone ( issue . Poster , issue , 0 )
}
2019-09-30 16:50:44 +03:00
return nil
}
2019-10-11 09:44:43 +03:00
// ChangeTitle changes the title of this issue, as the given user.
2022-06-13 12:37:59 +03:00
func ChangeTitle ( issue * issues_model . Issue , doer * user_model . User , title string ) ( err error ) {
2019-10-11 09:44:43 +03:00
oldTitle := issue . Title
issue . Title = title
2022-06-13 12:37:59 +03:00
if err = issues_model . ChangeIssueTitle ( issue , doer , oldTitle ) ; err != nil {
2019-10-11 09:44:43 +03:00
return
}
2019-10-28 05:11:50 +03:00
notification . NotifyIssueChangeTitle ( doer , issue , oldTitle )
2019-10-11 09:44:43 +03:00
return nil
}
2019-10-25 17:46:37 +03:00
2020-09-08 19:29:51 +03:00
// ChangeIssueRef changes the branch of this issue, as the given user.
2022-06-13 12:37:59 +03:00
func ChangeIssueRef ( issue * issues_model . Issue , doer * user_model . User , ref string ) error {
2020-09-08 19:29:51 +03:00
oldRef := issue . Ref
issue . Ref = ref
2022-06-13 12:37:59 +03:00
if err := issues_model . ChangeIssueRef ( issue , doer , oldRef ) ; err != nil {
2020-09-08 19:29:51 +03:00
return err
}
notification . NotifyIssueChangeRef ( doer , issue , oldRef )
return nil
}
2019-10-25 17:46:37 +03:00
// UpdateAssignees is a helper function to add or delete one or multiple issue assignee(s)
// Deleting is done the GitHub way (quote from their api documentation):
// https://developer.github.com/v3/issues/#edit-an-issue
// "assignees" (array): Logins for Users to assign to this issue.
// Pass one or more user logins to replace the set of assignees on this Issue.
// Send an empty array ([]) to clear all assignees from the Issue.
2022-06-13 12:37:59 +03:00
func UpdateAssignees ( issue * issues_model . Issue , oneAssignee string , multipleAssignees [ ] string , doer * user_model . User ) ( err error ) {
2021-11-24 12:49:20 +03:00
var allNewAssignees [ ] * user_model . User
2019-10-25 17:46:37 +03:00
// Keep the old assignee thingy for compatibility reasons
if oneAssignee != "" {
// Prevent double adding assignees
var isDouble bool
for _ , assignee := range multipleAssignees {
if assignee == oneAssignee {
isDouble = true
break
}
}
if ! isDouble {
multipleAssignees = append ( multipleAssignees , oneAssignee )
}
}
// Loop through all assignees to add them
for _ , assigneeName := range multipleAssignees {
2022-05-20 17:08:52 +03:00
assignee , err := user_model . GetUserByName ( db . DefaultContext , assigneeName )
2019-10-25 17:46:37 +03:00
if err != nil {
return err
}
allNewAssignees = append ( allNewAssignees , assignee )
}
// Delete all old assignees not passed
2019-10-28 05:11:50 +03:00
if err = DeleteNotPassedAssignee ( issue , doer , allNewAssignees ) ; err != nil {
2019-10-25 17:46:37 +03:00
return err
}
// Add all new assignees
// Update the assignee. The function will check if the user exists, is already
// assigned (which he shouldn't as we deleted all assignees before) and
// has access to the repo.
for _ , assignee := range allNewAssignees {
// Extra method to prevent double adding (which would result in removing)
err = AddAssigneeIfNotAssigned ( issue , doer , assignee . ID )
if err != nil {
return err
}
}
2022-06-20 13:02:49 +03:00
return err
2019-10-25 17:46:37 +03:00
}
2022-03-01 03:20:15 +03:00
// DeleteIssue deletes an issue
2022-06-13 12:37:59 +03:00
func DeleteIssue ( doer * user_model . User , gitRepo * git . Repository , issue * issues_model . Issue ) error {
2022-03-01 03:20:15 +03:00
// load issue before deleting it
2022-06-13 12:37:59 +03:00
if err := issue . LoadAttributes ( db . DefaultContext ) ; err != nil {
2022-03-01 03:20:15 +03:00
return err
}
if err := issue . LoadPullRequest ( ) ; err != nil {
return err
}
// delete entries in database
2022-06-13 12:37:59 +03:00
if err := deleteIssue ( issue ) ; err != nil {
2022-03-01 03:20:15 +03:00
return err
}
// delete pull request related git data
if issue . IsPull {
2022-06-19 13:05:15 +03:00
if err := gitRepo . RemoveReference ( fmt . Sprintf ( "%s%d/head" , git . PullPrefix , issue . PullRequest . Index ) ) ; err != nil {
2022-03-01 03:20:15 +03:00
return err
}
}
notification . NotifyDeleteIssue ( doer , issue )
return nil
}
2019-10-25 17:46:37 +03:00
// AddAssigneeIfNotAssigned adds an assignee only if he isn't already assigned to the issue.
// Also checks for access of assigned user
2022-06-13 12:37:59 +03:00
func AddAssigneeIfNotAssigned ( issue * issues_model . Issue , doer * user_model . User , assigneeID int64 ) ( err error ) {
2021-11-24 12:49:20 +03:00
assignee , err := user_model . GetUserByID ( assigneeID )
2019-10-25 17:46:37 +03:00
if err != nil {
return err
}
// Check if the user is already assigned
2022-06-13 12:37:59 +03:00
isAssigned , err := issues_model . IsUserAssignedToIssue ( db . DefaultContext , issue , assignee )
2019-10-25 17:46:37 +03:00
if err != nil {
return err
}
if isAssigned {
// nothing to to
return nil
}
2022-05-11 13:09:36 +03:00
valid , err := access_model . CanBeAssigned ( db . DefaultContext , assignee , issue . Repo , issue . IsPull )
2019-10-25 17:46:37 +03:00
if err != nil {
return err
}
if ! valid {
2022-06-13 12:37:59 +03:00
return repo_model . ErrUserDoesNotHaveAccessToRepo { UserID : assigneeID , RepoName : issue . Repo . Name }
2019-10-25 17:46:37 +03:00
}
2019-10-28 05:11:50 +03:00
_ , _ , err = ToggleAssignee ( issue , doer , assigneeID )
2019-10-25 17:46:37 +03:00
if err != nil {
return err
}
return nil
}
2020-05-15 01:55:43 +03:00
// GetRefEndNamesAndURLs retrieves the ref end names (e.g. refs/heads/branch-name -> branch-name)
// and their respective URLs.
2022-06-13 12:37:59 +03:00
func GetRefEndNamesAndURLs ( issues [ ] * issues_model . Issue , repoLink string ) ( map [ int64 ] string , map [ int64 ] string ) {
2022-01-20 20:46:10 +03:00
issueRefEndNames := make ( map [ int64 ] string , len ( issues ) )
issueRefURLs := make ( map [ int64 ] string , len ( issues ) )
2020-05-15 01:55:43 +03:00
for _ , issue := range issues {
if issue . Ref != "" {
issueRefEndNames [ issue . ID ] = git . RefEndName ( issue . Ref )
issueRefURLs [ issue . ID ] = git . RefURL ( repoLink , util . PathEscapeSegments ( issue . Ref ) )
}
}
return issueRefEndNames , issueRefURLs
}
2022-06-13 12:37:59 +03:00
// deleteIssue deletes the issue
func deleteIssue ( issue * issues_model . Issue ) error {
ctx , committer , err := db . TxContext ( )
if err != nil {
return err
}
defer committer . Close ( )
e := db . GetEngine ( ctx )
if _ , err := e . ID ( issue . ID ) . NoAutoCondition ( ) . Delete ( issue ) ; err != nil {
return err
}
if err := repo_model . UpdateRepoIssueNumbers ( ctx , issue . RepoID , issue . IsPull , issue . IsClosed ) ; err != nil {
return err
}
2022-08-25 05:31:57 +03:00
if err := activities_model . DeleteIssueActions ( ctx , issue . RepoID , issue . ID ) ; err != nil {
2022-06-13 12:37:59 +03:00
return err
}
// find attachments related to this issue and remove them
if err := issue . LoadAttributes ( ctx ) ; err != nil {
return err
}
for i := range issue . Attachments {
2022-10-17 02:29:26 +03:00
system_model . RemoveStorageWithNotice ( ctx , storage . Attachments , "Delete issue attachment" , issue . Attachments [ i ] . RelativePath ( ) )
2022-06-13 12:37:59 +03:00
}
// delete all database data still assigned to this issue
if err := issues_model . DeleteInIssue ( ctx , issue . ID ,
& issues_model . ContentHistory { } ,
& issues_model . Comment { } ,
& issues_model . IssueLabel { } ,
& issues_model . IssueDependency { } ,
& issues_model . IssueAssignees { } ,
& issues_model . IssueUser { } ,
2022-08-25 05:31:57 +03:00
& activities_model . Notification { } ,
2022-06-13 12:37:59 +03:00
& issues_model . Reaction { } ,
& issues_model . IssueWatch { } ,
& issues_model . Stopwatch { } ,
& issues_model . TrackedTime { } ,
& project_model . ProjectIssue { } ,
& repo_model . Attachment { } ,
& issues_model . PullRequest { } ,
) ; err != nil {
return err
}
// References to this issue in other issues
if _ , err := db . DeleteByBean ( ctx , & issues_model . Comment {
RefIssueID : issue . ID ,
} ) ; err != nil {
return err
}
// Delete dependencies for issues in other repositories
if _ , err := db . DeleteByBean ( ctx , & issues_model . IssueDependency {
DependencyID : issue . ID ,
} ) ; err != nil {
return err
}
// delete from dependent issues
if _ , err := db . DeleteByBean ( ctx , & issues_model . Comment {
DependentIssueID : issue . ID ,
} ) ; err != nil {
return err
}
return committer . Commit ( )
}