aboutsummaryrefslogtreecommitdiffstats
path: root/models
diff options
context:
space:
mode:
authorOtto Richter (fnetX) <git@fralix.ovh>2022-03-01 01:20:15 +0100
committerGitHub <noreply@github.com>2022-03-01 01:20:15 +0100
commit062fd4c217cc7302f56acf043d6214a9db46ee2f (patch)
tree4aaa51baaee1d7ddfab00a88a2d22f5911af1312 /models
parent6859b6919800cbf2958dbfbe76fca42f4dcbb194 (diff)
downloadgitea-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.go114
-rw-r--r--models/issue_comment.go4
-rw-r--r--models/issue_test.go52
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())