2019-09-30 16:50:44 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2019-09-30 16:50:44 +03:00
package issue
import (
2023-04-14 21:18:28 +03:00
"context"
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"
2024-03-04 11:16:03 +03:00
"code.gitea.io/gitea/modules/container"
2020-05-15 01:55:43 +03:00
"code.gitea.io/gitea/modules/git"
2024-03-25 10:51:23 +03:00
"code.gitea.io/gitea/modules/log"
2022-06-13 12:37:59 +03:00
"code.gitea.io/gitea/modules/storage"
2023-09-05 21:37:47 +03:00
notify_service "code.gitea.io/gitea/services/notify"
2019-09-30 16:50:44 +03:00
)
// NewIssue creates new issue with labels for repository.
2024-03-04 14:24:02 +03:00
func NewIssue ( ctx context . Context , repo * repo_model . Repository , issue * issues_model . Issue , labelIDs [ ] int64 , uuids [ ] string , assigneeIDs [ ] int64 , projectID int64 ) error {
2024-03-04 11:16:03 +03:00
if err := issue . LoadPoster ( ctx ) ; err != nil {
return err
}
if user_model . IsUserBlockedBy ( ctx , issue . Poster , repo . OwnerID ) || user_model . IsUserBlockedBy ( ctx , issue . Poster , assigneeIDs ... ) {
return user_model . ErrBlockedUser
}
2024-03-04 14:24:02 +03:00
if err := db . WithTx ( ctx , func ( ctx context . Context ) error {
if err := issues_model . NewIssue ( ctx , repo , issue , labelIDs , uuids ) ; err != nil {
2019-10-28 19:45:43 +03:00
return err
}
2024-03-04 14:24:02 +03:00
for _ , assigneeID := range assigneeIDs {
if _ , err := AddAssigneeIfNotAssigned ( ctx , issue , issue . Poster , assigneeID , true ) ; err != nil {
return err
}
}
if projectID > 0 {
if err := issues_model . ChangeProjectAssign ( ctx , issue , issue . Poster , projectID ) ; err != nil {
return err
}
}
return nil
} ) ; err != nil {
return err
2019-10-28 19:45:43 +03:00
}
2023-04-14 21:18:28 +03:00
mentions , err := issues_model . FindAndUpdateIssueMentions ( ctx , issue , issue . Poster , issue . Content )
2021-01-02 20:04:02 +03:00
if err != nil {
return err
}
2023-09-05 21:37:47 +03:00
notify_service . NewIssue ( ctx , issue , mentions )
2021-01-17 17:15:57 +03:00
if len ( issue . Labels ) > 0 {
2023-09-05 21:37:47 +03:00
notify_service . IssueChangeLabels ( ctx , issue . Poster , issue , issue . Labels , nil )
2021-01-17 17:15:57 +03:00
}
if issue . Milestone != nil {
2023-09-05 21:37:47 +03:00
notify_service . IssueChangeMilestone ( ctx , issue . Poster , issue , 0 )
2021-01-17 17:15:57 +03:00
}
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.
2023-07-07 08:31:56 +03:00
func ChangeTitle ( ctx context . Context , issue * issues_model . Issue , doer * user_model . User , title string ) error {
2019-10-11 09:44:43 +03:00
oldTitle := issue . Title
issue . Title = title
2023-10-06 21:00:53 +03:00
if oldTitle == title {
return nil
}
2024-03-04 11:16:03 +03:00
if err := issue . LoadRepo ( ctx ) ; err != nil {
return err
}
if user_model . IsUserBlockedBy ( ctx , doer , issue . PosterID , issue . Repo . OwnerID ) {
if isAdmin , _ := access_model . IsUserRepoAdmin ( ctx , issue . Repo , doer ) ; ! isAdmin {
return user_model . ErrBlockedUser
}
}
2024-03-25 10:51:23 +03:00
if err := issues_model . ChangeIssueTitle ( ctx , issue , doer , oldTitle ) ; err != nil {
return err
}
2024-03-19 08:28:43 +03:00
2024-03-25 10:51:23 +03:00
var reviewNotifers [ ] * ReviewRequestNotifier
if issue . IsPull && issues_model . HasWorkInProgressPrefix ( oldTitle ) && ! issues_model . HasWorkInProgressPrefix ( title ) {
var err error
reviewNotifers , err = PullRequestCodeOwnersReview ( ctx , issue , issue . PullRequest )
if err != nil {
log . Error ( "PullRequestCodeOwnersReview: %v" , err )
2024-03-19 08:28:43 +03:00
}
2023-06-08 11:56:05 +03:00
}
2023-09-05 21:37:47 +03:00
notify_service . IssueChangeTitle ( ctx , doer , issue , oldTitle )
2024-03-19 08:28:43 +03:00
ReviewRequestNotify ( ctx , issue , issue . Poster , reviewNotifers )
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.
2023-04-14 21:18:28 +03:00
func ChangeIssueRef ( ctx context . Context , 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
2023-09-29 15:12:54 +03:00
if err := issues_model . ChangeIssueRef ( ctx , issue , doer , oldRef ) ; err != nil {
2020-09-08 19:29:51 +03:00
return err
}
2023-09-05 21:37:47 +03:00
notify_service . IssueChangeRef ( ctx , doer , issue , oldRef )
2020-09-08 19:29:51 +03:00
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.
2023-04-14 21:18:28 +03:00
func UpdateAssignees ( ctx context . Context , issue * issues_model . Issue , oneAssignee string , multipleAssignees [ ] string , doer * user_model . User ) ( err error ) {
2024-03-04 11:16:03 +03:00
uniqueAssignees := container . SetOf ( multipleAssignees ... )
2019-10-25 17:46:37 +03:00
// Keep the old assignee thingy for compatibility reasons
if oneAssignee != "" {
2024-03-04 11:16:03 +03:00
uniqueAssignees . Add ( oneAssignee )
2019-10-25 17:46:37 +03:00
}
// Loop through all assignees to add them
2024-03-04 11:16:03 +03:00
allNewAssignees := make ( [ ] * user_model . User , 0 , len ( uniqueAssignees ) )
for _ , assigneeName := range uniqueAssignees . Values ( ) {
2023-04-14 21:18:28 +03:00
assignee , err := user_model . GetUserByName ( ctx , assigneeName )
2019-10-25 17:46:37 +03:00
if err != nil {
return err
}
2024-03-04 11:16:03 +03:00
if user_model . IsUserBlockedBy ( ctx , doer , assignee . ID ) {
return user_model . ErrBlockedUser
}
2019-10-25 17:46:37 +03:00
allNewAssignees = append ( allNewAssignees , assignee )
}
// Delete all old assignees not passed
2023-04-14 21:18:28 +03:00
if err = DeleteNotPassedAssignee ( ctx , 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)
2023-08-10 05:39:21 +03:00
_ , err = AddAssigneeIfNotAssigned ( ctx , issue , doer , assignee . ID , true )
2019-10-25 17:46:37 +03:00
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
2023-04-14 21:18:28 +03:00
func DeleteIssue ( ctx context . Context , 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
2023-04-14 21:18:28 +03:00
if err := issue . LoadAttributes ( ctx ) ; err != nil {
2022-03-01 03:20:15 +03:00
return err
}
2023-04-14 21:18:28 +03:00
if err := issue . LoadPullRequest ( ctx ) ; err != nil {
2022-03-01 03:20:15 +03:00
return err
}
// delete entries in database
2023-04-14 21:18:28 +03:00
if err := deleteIssue ( ctx , issue ) ; err != nil {
2022-03-01 03:20:15 +03:00
return err
}
// delete pull request related git data
2023-04-14 21:18:28 +03:00
if issue . IsPull && gitRepo != nil {
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
}
}
2023-05-25 16:17:19 +03: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-05 21:37:47 +03:00
notify_service . DeleteIssue ( ctx , doer , issue )
2022-03-01 03:20:15 +03:00
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
2023-08-10 05:39:21 +03: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-14 21:18:28 +03:00
assignee , err := user_model . GetUserByID ( ctx , assigneeID )
2019-10-25 17:46:37 +03:00
if err != nil {
2023-08-10 05:39:21 +03:00
return nil , err
2019-10-25 17:46:37 +03:00
}
// Check if the user is already assigned
2023-04-14 21:18:28 +03:00
isAssigned , err := issues_model . IsUserAssignedToIssue ( ctx , issue , assignee )
2019-10-25 17:46:37 +03:00
if err != nil {
2023-08-10 05:39:21 +03:00
return nil , err
2019-10-25 17:46:37 +03:00
}
if isAssigned {
// nothing to to
2023-08-10 05:39:21 +03:00
return nil , nil
2019-10-25 17:46:37 +03:00
}
2023-04-14 21:18:28 +03:00
valid , err := access_model . CanBeAssigned ( ctx , assignee , issue . Repo , issue . IsPull )
2019-10-25 17:46:37 +03:00
if err != nil {
2023-08-10 05:39:21 +03:00
return nil , err
2019-10-25 17:46:37 +03:00
}
if ! valid {
2023-08-10 05:39:21 +03:00
return nil , repo_model . ErrUserDoesNotHaveAccessToRepo { UserID : assigneeID , RepoName : issue . Repo . Name }
2019-10-25 17:46:37 +03:00
}
2023-08-10 05:39:21 +03:00
if notify {
_ , comment , err = ToggleAssigneeWithNotify ( ctx , issue , doer , assigneeID )
return comment , err
2019-10-25 17:46:37 +03:00
}
2023-08-10 05:39:21 +03:00
_ , comment , err = issues_model . ToggleIssueAssignee ( ctx , issue , doer , assigneeID )
return comment , err
2019-10-25 17:46:37 +03:00
}
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 != "" {
2023-05-26 04:04:48 +03:00
issueRefEndNames [ issue . ID ] = git . RefName ( issue . Ref ) . ShortName ( )
2022-11-22 15:58:49 +03:00
issueRefURLs [ issue . ID ] = git . RefURL ( repoLink , issue . Ref )
2020-05-15 01:55:43 +03:00
}
}
return issueRefEndNames , issueRefURLs
}
2022-06-13 12:37:59 +03:00
// deleteIssue deletes the issue
2023-04-14 21:18:28 +03:00
func deleteIssue ( ctx context . Context , issue * issues_model . Issue ) error {
ctx , committer , err := db . TxContext ( ctx )
2022-06-13 12:37:59 +03: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 10:53:09 +03:00
// update the total issue numbers
if err := repo_model . UpdateRepoIssueNumbers ( ctx , issue . RepoID , issue . IsPull , false ) ; err != nil {
2022-06-13 12:37:59 +03:00
return err
}
2022-12-06 10:53:09 +03: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 12:37:59 +03:00
2022-10-22 18:08:10 +03: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 13:23:59 +03:00
if err := activities_model . DeleteIssueActions ( ctx , issue . RepoID , issue . ID , issue . Index ) ; 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
2023-10-02 05:30:10 +03: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 12:37:59 +03:00
) ; err != nil {
return err
}
return committer . Commit ( )
}