2019-09-30 21:50:44 +08:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2019-09-30 21:50:44 +08:00
package issue
import (
2023-04-15 02:18:28 +08:00
"context"
2022-03-01 01:20:15 +01:00
"fmt"
2022-08-25 10:31:57 +08:00
activities_model "code.gitea.io/gitea/models/activities"
2021-09-19 19:49:59 +08:00
"code.gitea.io/gitea/models/db"
2022-06-13 17:37:59 +08:00
issues_model "code.gitea.io/gitea/models/issues"
2022-05-11 18:09:36 +08:00
access_model "code.gitea.io/gitea/models/perm/access"
2022-06-13 17:37:59 +08:00
project_model "code.gitea.io/gitea/models/project"
2021-12-10 09:27:50 +08:00
repo_model "code.gitea.io/gitea/models/repo"
2022-10-17 07:29:26 +08:00
system_model "code.gitea.io/gitea/models/system"
2021-11-24 17:49:20 +08:00
user_model "code.gitea.io/gitea/models/user"
2020-05-15 00:55:43 +02:00
"code.gitea.io/gitea/modules/git"
2022-06-13 17:37:59 +08:00
"code.gitea.io/gitea/modules/storage"
2023-09-06 02:37:47 +08:00
notify_service "code.gitea.io/gitea/services/notify"
2019-09-30 21:50:44 +08:00
)
// NewIssue creates new issue with labels for repository.
2023-04-15 02:18:28 +08:00
func NewIssue ( ctx context . Context , repo * repo_model . Repository , issue * issues_model . Issue , labelIDs [ ] int64 , uuids [ ] string , assigneeIDs [ ] int64 ) error {
2023-09-29 14:12:54 +02:00
if err := issues_model . NewIssue ( ctx , repo , issue , labelIDs , uuids ) ; err != nil {
2019-09-30 21:50:44 +08:00
return err
}
2019-10-29 00:45:43 +08:00
for _ , assigneeID := range assigneeIDs {
2023-08-10 10:39:21 +08:00
if _ , err := AddAssigneeIfNotAssigned ( ctx , issue , issue . Poster , assigneeID , true ) ; err != nil {
2019-10-29 00:45:43 +08:00
return err
}
}
2023-04-15 02:18:28 +08:00
mentions , err := issues_model . FindAndUpdateIssueMentions ( ctx , issue , issue . Poster , issue . Content )
2021-01-02 18:04:02 +01:00
if err != nil {
return err
}
2023-09-06 02:37:47 +08:00
notify_service . NewIssue ( ctx , issue , mentions )
2021-01-17 15:15:57 +01:00
if len ( issue . Labels ) > 0 {
2023-09-06 02:37:47 +08:00
notify_service . IssueChangeLabels ( ctx , issue . Poster , issue , issue . Labels , nil )
2021-01-17 15:15:57 +01:00
}
if issue . Milestone != nil {
2023-09-06 02:37:47 +08:00
notify_service . IssueChangeMilestone ( ctx , issue . Poster , issue , 0 )
2021-01-17 15:15:57 +01:00
}
2019-09-30 21:50:44 +08:00
return nil
}
2019-10-11 14:44:43 +08:00
// ChangeTitle changes the title of this issue, as the given user.
2023-07-07 07:31:56 +02:00
func ChangeTitle ( ctx context . Context , issue * issues_model . Issue , doer * user_model . User , title string ) error {
2019-10-11 14:44:43 +08:00
oldTitle := issue . Title
issue . Title = title
2023-07-07 07:31:56 +02:00
if err := issues_model . ChangeIssueTitle ( ctx , issue , doer , oldTitle ) ; err != nil {
return err
2019-10-11 14:44:43 +08:00
}
2023-06-08 11:56:05 +03:00
if issue . IsPull && issues_model . HasWorkInProgressPrefix ( oldTitle ) && ! issues_model . HasWorkInProgressPrefix ( title ) {
2023-07-07 07:31:56 +02:00
if err := issues_model . PullRequestCodeOwnersReview ( ctx , issue , issue . PullRequest ) ; err != nil {
return err
2023-06-08 11:56:05 +03:00
}
}
2023-09-06 02:37:47 +08:00
notify_service . IssueChangeTitle ( ctx , doer , issue , oldTitle )
2019-10-11 14:44:43 +08:00
return nil
}
2019-10-25 16:46:37 +02:00
2020-09-08 18:29:51 +02:00
// ChangeIssueRef changes the branch of this issue, as the given user.
2023-04-15 02:18:28 +08:00
func ChangeIssueRef ( ctx context . Context , issue * issues_model . Issue , doer * user_model . User , ref string ) error {
2020-09-08 18:29:51 +02:00
oldRef := issue . Ref
issue . Ref = ref
2023-09-29 14:12:54 +02:00
if err := issues_model . ChangeIssueRef ( ctx , issue , doer , oldRef ) ; err != nil {
2020-09-08 18:29:51 +02:00
return err
}
2023-09-06 02:37:47 +08:00
notify_service . IssueChangeRef ( ctx , doer , issue , oldRef )
2020-09-08 18:29:51 +02:00
return nil
}
2019-10-25 16:46:37 +02: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.
2023-04-15 02:18:28 +08:00
func UpdateAssignees ( ctx context . Context , issue * issues_model . Issue , oneAssignee string , multipleAssignees [ ] string , doer * user_model . User ) ( err error ) {
2021-11-24 17:49:20 +08:00
var allNewAssignees [ ] * user_model . User
2019-10-25 16:46:37 +02: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 {
2023-04-15 02:18:28 +08:00
assignee , err := user_model . GetUserByName ( ctx , assigneeName )
2019-10-25 16:46:37 +02:00
if err != nil {
return err
}
allNewAssignees = append ( allNewAssignees , assignee )
}
// Delete all old assignees not passed
2023-04-15 02:18:28 +08:00
if err = DeleteNotPassedAssignee ( ctx , issue , doer , allNewAssignees ) ; err != nil {
2019-10-25 16:46:37 +02: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)
2023-08-10 10:39:21 +08:00
_ , err = AddAssigneeIfNotAssigned ( ctx , issue , doer , assignee . ID , true )
2019-10-25 16:46:37 +02:00
if err != nil {
return err
}
}
2022-06-20 12:02:49 +02:00
return err
2019-10-25 16:46:37 +02:00
}
2022-03-01 01:20:15 +01:00
// DeleteIssue deletes an issue
2023-04-15 02:18:28 +08:00
func DeleteIssue ( ctx context . Context , doer * user_model . User , gitRepo * git . Repository , issue * issues_model . Issue ) error {
2022-03-01 01:20:15 +01:00
// load issue before deleting it
2023-04-15 02:18:28 +08:00
if err := issue . LoadAttributes ( ctx ) ; err != nil {
2022-03-01 01:20:15 +01:00
return err
}
2023-04-15 02:18:28 +08:00
if err := issue . LoadPullRequest ( ctx ) ; err != nil {
2022-03-01 01:20:15 +01:00
return err
}
// delete entries in database
2023-04-15 02:18:28 +08:00
if err := deleteIssue ( ctx , issue ) ; err != nil {
2022-03-01 01:20:15 +01:00
return err
}
// delete pull request related git data
2023-04-15 02:18:28 +08:00
if issue . IsPull && gitRepo != nil {
2022-06-19 18:05:15 +08:00
if err := gitRepo . RemoveReference ( fmt . Sprintf ( "%s%d/head" , git . PullPrefix , issue . PullRequest . Index ) ) ; err != nil {
2022-03-01 01:20:15 +01:00
return err
}
}
2023-05-25 15:17:19 +02:00
// If the Issue is pinned, we should unpin it before deletion to avoid problems with other pinned Issues
if issue . IsPinned ( ) {
if err := issue . Unpin ( ctx , doer ) ; err != nil {
return err
}
}
2023-09-06 02:37:47 +08:00
notify_service . DeleteIssue ( ctx , doer , issue )
2022-03-01 01:20:15 +01:00
return nil
}
2019-10-25 16:46:37 +02:00
// AddAssigneeIfNotAssigned adds an assignee only if he isn't already assigned to the issue.
// Also checks for access of assigned user
2023-08-10 10:39:21 +08:00
func AddAssigneeIfNotAssigned ( ctx context . Context , issue * issues_model . Issue , doer * user_model . User , assigneeID int64 , notify bool ) ( comment * issues_model . Comment , err error ) {
2023-04-15 02:18:28 +08:00
assignee , err := user_model . GetUserByID ( ctx , assigneeID )
2019-10-25 16:46:37 +02:00
if err != nil {
2023-08-10 10:39:21 +08:00
return nil , err
2019-10-25 16:46:37 +02:00
}
// Check if the user is already assigned
2023-04-15 02:18:28 +08:00
isAssigned , err := issues_model . IsUserAssignedToIssue ( ctx , issue , assignee )
2019-10-25 16:46:37 +02:00
if err != nil {
2023-08-10 10:39:21 +08:00
return nil , err
2019-10-25 16:46:37 +02:00
}
if isAssigned {
// nothing to to
2023-08-10 10:39:21 +08:00
return nil , nil
2019-10-25 16:46:37 +02:00
}
2023-04-15 02:18:28 +08:00
valid , err := access_model . CanBeAssigned ( ctx , assignee , issue . Repo , issue . IsPull )
2019-10-25 16:46:37 +02:00
if err != nil {
2023-08-10 10:39:21 +08:00
return nil , err
2019-10-25 16:46:37 +02:00
}
if ! valid {
2023-08-10 10:39:21 +08:00
return nil , repo_model . ErrUserDoesNotHaveAccessToRepo { UserID : assigneeID , RepoName : issue . Repo . Name }
2019-10-25 16:46:37 +02:00
}
2023-08-10 10:39:21 +08:00
if notify {
_ , comment , err = ToggleAssigneeWithNotify ( ctx , issue , doer , assigneeID )
return comment , err
2019-10-25 16:46:37 +02:00
}
2023-08-10 10:39:21 +08:00
_ , comment , err = issues_model . ToggleIssueAssignee ( ctx , issue , doer , assigneeID )
return comment , err
2019-10-25 16:46:37 +02:00
}
2020-05-15 00:55:43 +02:00
// GetRefEndNamesAndURLs retrieves the ref end names (e.g. refs/heads/branch-name -> branch-name)
// and their respective URLs.
2022-06-13 17:37:59 +08:00
func GetRefEndNamesAndURLs ( issues [ ] * issues_model . Issue , repoLink string ) ( map [ int64 ] string , map [ int64 ] string ) {
2022-01-20 18:46:10 +01:00
issueRefEndNames := make ( map [ int64 ] string , len ( issues ) )
issueRefURLs := make ( map [ int64 ] string , len ( issues ) )
2020-05-15 00:55:43 +02:00
for _ , issue := range issues {
if issue . Ref != "" {
2023-05-26 09:04:48 +08:00
issueRefEndNames [ issue . ID ] = git . RefName ( issue . Ref ) . ShortName ( )
2022-11-22 12:58:49 +00:00
issueRefURLs [ issue . ID ] = git . RefURL ( repoLink , issue . Ref )
2020-05-15 00:55:43 +02:00
}
}
return issueRefEndNames , issueRefURLs
}
2022-06-13 17:37:59 +08:00
// deleteIssue deletes the issue
2023-04-15 02:18:28 +08:00
func deleteIssue ( ctx context . Context , issue * issues_model . Issue ) error {
ctx , committer , err := db . TxContext ( ctx )
2022-06-13 17:37:59 +08:00
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
}
2022-12-06 15:53:09 +08:00
// update the total issue numbers
if err := repo_model . UpdateRepoIssueNumbers ( ctx , issue . RepoID , issue . IsPull , false ) ; err != nil {
2022-06-13 17:37:59 +08:00
return err
}
2022-12-06 15:53:09 +08:00
// if the issue is closed, update the closed issue numbers
if issue . IsClosed {
if err := repo_model . UpdateRepoIssueNumbers ( ctx , issue . RepoID , issue . IsPull , true ) ; err != nil {
return err
}
}
2022-06-13 17:37:59 +08:00
2022-10-22 10:08:10 -05:00
if err := issues_model . UpdateMilestoneCounters ( ctx , issue . MilestoneID ) ; err != nil {
return fmt . Errorf ( "error updating counters for milestone id %d: %w" ,
issue . MilestoneID , err )
}
2023-08-07 18:23:59 +08:00
if err := activities_model . DeleteIssueActions ( ctx , issue . RepoID , issue . ID , issue . Index ) ; err != nil {
2022-06-13 17:37:59 +08: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 07:29:26 +08:00
system_model . RemoveStorageWithNotice ( ctx , storage . Attachments , "Delete issue attachment" , issue . Attachments [ i ] . RelativePath ( ) )
2022-06-13 17:37:59 +08:00
}
// delete all database data still assigned to this issue
2023-10-02 04:30:10 +02:00
if err := db . DeleteBeans ( ctx ,
& issues_model . ContentHistory { IssueID : issue . ID } ,
& issues_model . Comment { IssueID : issue . ID } ,
& issues_model . IssueLabel { IssueID : issue . ID } ,
& issues_model . IssueDependency { IssueID : issue . ID } ,
& issues_model . IssueAssignees { IssueID : issue . ID } ,
& issues_model . IssueUser { IssueID : issue . ID } ,
& activities_model . Notification { IssueID : issue . ID } ,
& issues_model . Reaction { IssueID : issue . ID } ,
& issues_model . IssueWatch { IssueID : issue . ID } ,
& issues_model . Stopwatch { IssueID : issue . ID } ,
& issues_model . TrackedTime { IssueID : issue . ID } ,
& project_model . ProjectIssue { IssueID : issue . ID } ,
& repo_model . Attachment { IssueID : issue . ID } ,
& issues_model . PullRequest { IssueID : issue . ID } ,
& issues_model . Comment { RefIssueID : issue . ID } ,
& issues_model . IssueDependency { DependencyID : issue . ID } ,
& issues_model . Comment { DependentIssueID : issue . ID } ,
2022-06-13 17:37:59 +08:00
) ; err != nil {
return err
}
return committer . Commit ( )
}