diff options
author | Otto Richter (fnetX) <git@fralix.ovh> | 2022-03-01 01:20:15 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-03-01 01:20:15 +0100 |
commit | 062fd4c217cc7302f56acf043d6214a9db46ee2f (patch) | |
tree | 4aaa51baaee1d7ddfab00a88a2d22f5911af1312 /models | |
parent | 6859b6919800cbf2958dbfbe76fca42f4dcbb194 (diff) | |
download | gitea-062fd4c217cc7302f56acf043d6214a9db46ee2f.tar.gz gitea-062fd4c217cc7302f56acf043d6214a9db46ee2f.zip |
[API] Allow removing issues (#18879)
Add new feature to delete issues and pulls via API
Co-authored-by: fnetx <git@fralix.ovh>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Gusted <williamzijl7@hotmail.com>
Co-authored-by: 6543 <6543@obermui.de>
Diffstat (limited to 'models')
-rw-r--r-- | models/issue.go | 114 | ||||
-rw-r--r-- | models/issue_comment.go | 4 | ||||
-rw-r--r-- | models/issue_test.go | 52 |
3 files changed, 167 insertions, 3 deletions
diff --git a/models/issue.go b/models/issue.go index 91d4df32d1..625374faaf 100644 --- a/models/issue.go +++ b/models/issue.go @@ -13,6 +13,7 @@ import ( "strconv" "strings" + admin_model "code.gitea.io/gitea/models/admin" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/perm" @@ -24,6 +25,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/references" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/storage" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" @@ -1990,6 +1992,118 @@ func UpdateIssueDeadline(issue *Issue, deadlineUnix timeutil.TimeStamp, doer *us return committer.Commit() } +// DeleteIssue deletes the issue +func DeleteIssue(issue *Issue) error { + ctx, committer, err := db.TxContext() + if err != nil { + return err + } + defer committer.Close() + + if err := deleteIssue(ctx, issue); err != nil { + return err + } + + return committer.Commit() +} + +func deleteInIssue(e db.Engine, issueID int64, beans ...interface{}) error { + for _, bean := range beans { + if _, err := e.In("issue_id", issueID).Delete(bean); err != nil { + return err + } + } + return nil +} + +func deleteIssue(ctx context.Context, issue *Issue) error { + e := db.GetEngine(ctx) + if _, err := e.ID(issue.ID).NoAutoCondition().Delete(issue); err != nil { + return err + } + + if issue.IsPull { + if _, err := e.ID(issue.RepoID).Decr("num_pulls").Update(new(repo_model.Repository)); err != nil { + return err + } + if issue.IsClosed { + if _, err := e.ID(issue.RepoID).Decr("num_closed_pulls").Update(new(repo_model.Repository)); err != nil { + return err + } + } + } else { + if _, err := e.ID(issue.RepoID).Decr("num_issues").Update(new(repo_model.Repository)); err != nil { + return err + } + if issue.IsClosed { + if _, err := e.ID(issue.RepoID).Decr("num_closed_issues").Update(new(repo_model.Repository)); err != nil { + return err + } + } + } + + // delete actions assigned to this issue + var comments []int64 + if err := e.Table(new(Comment)).In("issue_id", issue.ID).Cols("id").Find(&comments); err != nil { + return err + } + for i := range comments { + if _, err := e.Where("comment_id = ?", comments[i]).Delete(&Action{}); err != nil { + return err + } + } + if _, err := e.Table("action").Where("repo_id = ?", issue.RepoID).In("op_type", ActionCreateIssue, ActionCreatePullRequest). + Where("content LIKE ?", strconv.FormatInt(issue.ID, 10)+"|%").Delete(&Action{}); err != nil { + return err + } + + // find attachments related to this issue and remove them + var attachments []*repo_model.Attachment + if err := e.In("issue_id", issue.ID).Find(&attachments); err != nil { + return err + } + + for i := range attachments { + admin_model.RemoveStorageWithNotice(ctx, storage.Attachments, "Delete issue attachment", attachments[i].RelativePath()) + } + + // delete all database data still assigned to this issue + if err := deleteInIssue(e, issue.ID, + &issues.ContentHistory{}, + &Comment{}, + &IssueLabel{}, + &IssueDependency{}, + &IssueAssignees{}, + &IssueUser{}, + &Reaction{}, + &IssueWatch{}, + &Stopwatch{}, + &TrackedTime{}, + &ProjectIssue{}, + &repo_model.Attachment{}, + &PullRequest{}, + ); err != nil { + return err + } + + // References to this issue in other issues + if _, err := e.In("ref_issue_id", issue.ID).Delete(&Comment{}); err != nil { + return err + } + + // Delete dependencies for issues in other repositories + if _, err := e.In("dependency_id", issue.ID).Delete(&IssueDependency{}); err != nil { + return err + } + + // delete from dependent issues + if _, err := e.In("dependent_issue_id", issue.ID).Delete(&Comment{}); err != nil { + return err + } + + return nil +} + // DependencyInfo represents high level information about an issue which is a dependency of another issue. type DependencyInfo struct { Issue `xorm:"extends"` diff --git a/models/issue_comment.go b/models/issue_comment.go index 31bd041ca7..0af45e80e8 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -1152,9 +1152,7 @@ func DeleteComment(comment *Comment) error { } func deleteComment(e db.Engine, comment *Comment) error { - if _, err := e.Delete(&Comment{ - ID: comment.ID, - }); err != nil { + if _, err := e.ID(comment.ID).NoAutoCondition().Delete(comment); err != nil { return err } diff --git a/models/issue_test.go b/models/issue_test.go index 9344d385a7..7cc0aa61b0 100644 --- a/models/issue_test.go +++ b/models/issue_test.go @@ -397,6 +397,58 @@ func TestIssue_InsertIssue(t *testing.T) { assert.NoError(t, err) } +func TestIssue_DeleteIssue(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + issueIDs, err := GetIssueIDsByRepoID(1) + assert.NoError(t, err) + assert.EqualValues(t, 5, len(issueIDs)) + + issue := &Issue{ + RepoID: 1, + ID: issueIDs[2], + } + + err = DeleteIssue(issue) + assert.NoError(t, err) + issueIDs, err = GetIssueIDsByRepoID(1) + assert.NoError(t, err) + assert.EqualValues(t, 4, len(issueIDs)) + + // check attachment removal + attachments, err := repo_model.GetAttachmentsByIssueID(4) + assert.NoError(t, err) + issue, err = GetIssueByID(4) + assert.NoError(t, err) + err = DeleteIssue(issue) + assert.NoError(t, err) + assert.EqualValues(t, 2, len(attachments)) + for i := range attachments { + attachment, err := repo_model.GetAttachmentByUUID(attachments[i].UUID) + assert.Error(t, err) + assert.True(t, repo_model.IsErrAttachmentNotExist(err)) + assert.Nil(t, attachment) + } + + // check issue dependencies + user, err := user_model.GetUserByID(1) + assert.NoError(t, err) + issue1, err := GetIssueByID(1) + assert.NoError(t, err) + issue2, err := GetIssueByID(2) + assert.NoError(t, err) + err = CreateIssueDependency(user, issue1, issue2) + assert.NoError(t, err) + left, err := IssueNoDependenciesLeft(issue1) + assert.NoError(t, err) + assert.False(t, left) + err = DeleteIssue(&Issue{ID: 2}) + assert.NoError(t, err) + left, err = IssueNoDependenciesLeft(issue1) + assert.NoError(t, err) + assert.True(t, left) +} + func TestIssue_ResolveMentions(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) |