Skip to content

Commit c845f3a

Browse files
committed
Merge branch 'feature/close_issue_with_state' of github.com:sillyguodong/gitea into feature/close_issue_with_state
2 parents f4b8f6f + 0a4c7f2 commit c845f3a

File tree

25 files changed

+472
-75
lines changed

25 files changed

+472
-75
lines changed

models/issues/comment.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,6 +1297,11 @@ func (c *Comment) HasOriginalAuthor() bool {
12971297
return c.OriginalAuthor != "" && c.OriginalAuthorID != 0
12981298
}
12991299

1300+
func (c *Comment) GetIssueClosedStatus() int {
1301+
n, _ := strconv.Atoi(c.Content)
1302+
return n
1303+
}
1304+
13001305
// InsertIssueComments inserts many comments of issues.
13011306
func InsertIssueComments(ctx context.Context, comments []*Comment) error {
13021307
if len(comments) == 0 {

models/issues/dependency_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ func TestCreateIssueDependency(t *testing.T) {
4949
assert.False(t, left)
5050

5151
// Close #2 and check again
52-
_, err = issues_model.ChangeIssueStatus(db.DefaultContext, issue2, user1, true)
52+
issue2.IsClosed = true
53+
_, err = issues_model.ChangeIssueStatus(db.DefaultContext, issue2, user1)
5354
assert.NoError(t, err)
5455

5556
left, err = issues_model.IssueNoDependenciesLeft(db.DefaultContext, issue1)

models/issues/issue.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ func (err ErrIssueWasClosed) Error() string {
9494
return fmt.Sprintf("Issue [%d] %d was already closed", err.ID, err.Index)
9595
}
9696

97+
type IssueClosedStatus int8
98+
99+
const (
100+
IssueClosedStatusCommon IssueClosedStatus = iota // 0 close issue without any state.
101+
IssueClosedStatusArchived // 1
102+
IssueClosedStatusResolved // 2
103+
IssueClosedStatusStale // 3
104+
)
105+
97106
var ErrIssueAlreadyChanged = util.NewInvalidArgumentErrorf("the issue is already changed")
98107

99108
// Issue represents an issue or pull request of repository.
@@ -121,6 +130,7 @@ type Issue struct {
121130
Assignee *user_model.User `xorm:"-"`
122131
isAssigneeLoaded bool `xorm:"-"`
123132
IsClosed bool `xorm:"INDEX"`
133+
ClosedStatus IssueClosedStatus `xorm:"NOT NULL DEFAULT 0"`
124134
IsRead bool `xorm:"-"`
125135
IsPull bool `xorm:"INDEX"` // Indicates whether is a pull request or not.
126136
PullRequest *PullRequest `xorm:"-"`

models/issues/issue_update.go

Lines changed: 87 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package issues
66
import (
77
"context"
88
"fmt"
9+
"strconv"
910
"strings"
1011

1112
"code.gitea.io/gitea/models/db"
@@ -33,26 +34,27 @@ func UpdateIssueCols(ctx context.Context, issue *Issue, cols ...string) error {
3334
return nil
3435
}
3536

36-
func changeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User, isClosed, isMergePull bool) (*Comment, error) {
37+
func changeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User, isMergePull bool) (*Comment, error) {
3738
// Reload the issue
3839
currentIssue, err := GetIssueByID(ctx, issue.ID)
3940
if err != nil {
4041
return nil, err
4142
}
4243

4344
// Nothing should be performed if current status is same as target status
44-
if currentIssue.IsClosed == isClosed {
45-
if !issue.IsPull {
46-
return nil, ErrIssueWasClosed{
45+
if currentIssue.IsClosed == issue.IsClosed {
46+
if issue.IsPull {
47+
return nil, ErrPullWasClosed{
4748
ID: issue.ID,
4849
}
4950
}
50-
return nil, ErrPullWasClosed{
51-
ID: issue.ID,
51+
if currentIssue.ClosedStatus == issue.ClosedStatus {
52+
return nil, ErrIssueWasClosed{
53+
ID: issue.ID,
54+
}
5255
}
5356
}
5457

55-
issue.IsClosed = isClosed
5658
return doChangeIssueStatus(ctx, issue, doer, isMergePull)
5759
}
5860

@@ -76,7 +78,7 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
7678
issue.ClosedUnix = 0
7779
}
7880

79-
if err := UpdateIssueCols(ctx, issue, "is_closed", "closed_unix"); err != nil {
81+
if err := UpdateIssueCols(ctx, issue, "is_closed", "closed_status", "closed_unix"); err != nil {
8082
return nil, err
8183
}
8284

@@ -104,30 +106,36 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
104106

105107
// New action comment
106108
cmtType := CommentTypeClose
109+
var content string
110+
if !issue.IsPull && issue.IsClosed {
111+
content = strconv.Itoa(int(issue.ClosedStatus))
112+
}
113+
107114
if !issue.IsClosed {
108115
cmtType = CommentTypeReopen
109116
} else if isMergePull {
110117
cmtType = CommentTypeMergePull
111118
}
112119

113120
return CreateComment(ctx, &CreateCommentOptions{
114-
Type: cmtType,
115-
Doer: doer,
116-
Repo: issue.Repo,
117-
Issue: issue,
121+
Type: cmtType,
122+
Doer: doer,
123+
Repo: issue.Repo,
124+
Issue: issue,
125+
Content: content,
118126
})
119127
}
120128

121129
// ChangeIssueStatus changes issue status to open or closed.
122-
func ChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User, isClosed bool) (*Comment, error) {
130+
func ChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User) (*Comment, error) {
123131
if err := issue.LoadRepo(ctx); err != nil {
124132
return nil, err
125133
}
126134
if err := issue.LoadPoster(ctx); err != nil {
127135
return nil, err
128136
}
129137

130-
return changeIssueStatus(ctx, issue, doer, isClosed, false)
138+
return changeIssueStatus(ctx, issue, doer, false)
131139
}
132140

133141
// ChangeIssueTitle changes the title of this issue, as the given user.
@@ -434,6 +442,71 @@ func UpdateIssueMentions(ctx context.Context, issueID int64, mentions []*user_mo
434442
return nil
435443
}
436444

445+
// UpdateIssueByAPI updates all allowed fields of given issue.
446+
// If the issue status is changed a statusChangeComment is returned
447+
// similarly if the title is changed the titleChanged bool is set to true
448+
func UpdateIssueByAPI(issue *Issue, doer *user_model.User) (statusChangeComment *Comment, titleChanged bool, err error) {
449+
ctx, committer, err := db.TxContext(db.DefaultContext)
450+
if err != nil {
451+
return nil, false, err
452+
}
453+
defer committer.Close()
454+
455+
if err := issue.LoadRepo(ctx); err != nil {
456+
return nil, false, fmt.Errorf("loadRepo: %w", err)
457+
}
458+
459+
// Reload the issue
460+
currentIssue, err := GetIssueByID(ctx, issue.ID)
461+
if err != nil {
462+
return nil, false, err
463+
}
464+
465+
if _, err := db.GetEngine(ctx).ID(issue.ID).Cols(
466+
"name", "content", "milestone_id", "priority",
467+
"deadline_unix", "updated_unix", "is_locked").
468+
Update(issue); err != nil {
469+
return nil, false, err
470+
}
471+
472+
titleChanged = currentIssue.Title != issue.Title
473+
if titleChanged {
474+
opts := &CreateCommentOptions{
475+
Type: CommentTypeChangeTitle,
476+
Doer: doer,
477+
Repo: issue.Repo,
478+
Issue: issue,
479+
OldTitle: currentIssue.Title,
480+
NewTitle: issue.Title,
481+
}
482+
_, err := CreateComment(ctx, opts)
483+
if err != nil {
484+
return nil, false, fmt.Errorf("createComment: %w", err)
485+
}
486+
}
487+
488+
if issue.IsPull {
489+
if currentIssue.IsClosed != issue.IsClosed {
490+
statusChangeComment, err = doChangeIssueStatus(ctx, issue, doer, false)
491+
if err != nil {
492+
return nil, false, err
493+
}
494+
}
495+
} else {
496+
if currentIssue.IsClosed != issue.IsClosed || currentIssue.ClosedStatus != issue.ClosedStatus {
497+
statusChangeComment, err = doChangeIssueStatus(ctx, issue, doer, false)
498+
if err != nil {
499+
return nil, false, err
500+
}
501+
}
502+
}
503+
504+
if err := issue.AddCrossReferences(ctx, doer, true); err != nil {
505+
return nil, false, err
506+
}
507+
return statusChangeComment, titleChanged, committer.Commit()
508+
}
509+
437510
// UpdateIssueDeadline updates an issue deadline and adds comments. Setting a deadline to 0 means deleting it.
438511
func UpdateIssueDeadline(ctx context.Context, issue *Issue, deadlineUnix timeutil.TimeStamp, doer *user_model.User) (err error) {
439512
// if the deadline hasn't changed do nothing

models/issues/issue_xref_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ func TestXRef_ResolveCrossReferences(t *testing.T) {
9898
i1 := testCreateIssue(t, 1, 2, "title1", "content1", false)
9999
i2 := testCreateIssue(t, 1, 2, "title2", "content2", false)
100100
i3 := testCreateIssue(t, 1, 2, "title3", "content3", false)
101-
_, err := issues_model.ChangeIssueStatus(db.DefaultContext, i3, d, true)
101+
i3.IsClosed = true
102+
_, err := issues_model.ChangeIssueStatus(db.DefaultContext, i3, d)
102103
assert.NoError(t, err)
103104

104105
pr := testCreatePR(t, 1, 2, "titlepr", fmt.Sprintf("closes #%d", i1.Index))

models/issues/pull.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,8 @@ func (pr *PullRequest) SetMerged(ctx context.Context) (bool, error) {
543543
return false, err
544544
}
545545

546-
if _, err := changeIssueStatus(ctx, pr.Issue, pr.Merger, true, true); err != nil {
546+
pr.Issue.IsClosed = true
547+
if _, err := changeIssueStatus(ctx, pr.Issue, pr.Merger, true); err != nil {
547548
return false, fmt.Errorf("Issue.changeStatus: %w", err)
548549
}
549550

models/migrations/migrations.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,8 @@ var migrations = []Migration{
601601
NewMigration("Add metadata column for comment table", v1_23.AddCommentMetaDataColumn),
602602
// v304 -> v305
603603
NewMigration("Add index for release sha1", v1_23.AddIndexForReleaseSha1),
604+
// v305 -> v306
605+
NewMigration("Add column of closed_status to issue table", v1_23.AddClosedStatusToIssue),
604606
}
605607

606608
// GetCurrentDBVersion returns the current db version

models/migrations/v1_23/v305.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package v1_23 //nolint
5+
6+
import (
7+
issues_model "code.gitea.io/gitea/models/issues"
8+
9+
"xorm.io/xorm"
10+
)
11+
12+
func AddClosedStatusToIssue(x *xorm.Engine) error {
13+
type Issue struct {
14+
ClosedStatus issues_model.IssueClosedStatus `xorm:"NOT NULL DEFAULT 0"`
15+
}
16+
17+
return x.Sync(new(Issue))
18+
}

modules/structs/issue.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ const (
2525
StateAll StateType = "all"
2626
)
2727

28+
const (
29+
ClosedStatusArchived = "archived"
30+
ClosedStatusResolved = "resolved"
31+
ClosedStatusStale = "stale"
32+
)
33+
2834
// PullRequestMeta PR info if an issue is a PR
2935
type PullRequestMeta struct {
3036
HasMerged bool `json:"merged"`
@@ -113,6 +119,7 @@ type EditIssueOption struct {
113119
// swagger:strfmt date-time
114120
Deadline *time.Time `json:"due_date"`
115121
RemoveDeadline *bool `json:"unset_due_date"`
122+
ClosedStatus *string `json:"closed_status"`
116123
}
117124

118125
// EditDeadlineOption options for creating a deadline

options/locale/locale_en-US.ini

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,9 @@ issue.action.force_push = <b>%[1]s</b> force-pushed the <b>%[2]s</b> from %[3]s
498498
issue.action.push_1 = <b>@%[1]s</b> pushed %[3]d commit to %[2]s
499499
issue.action.push_n = <b>@%[1]s</b> pushed %[3]d commits to %[2]s
500500
issue.action.close = <b>@%[1]s</b> closed #%[2]d.
501+
issue.action.close_as_archived = <b>@%[1]s</b> closed as archived #%[2]d.
502+
issue.action.close_as_resolved = <b>@%[1]s</b> closed as resolved #%[2]d.
503+
issue.action.close_as_stale = <b>@%[1]s</b> closed as stale #%[2]d.
501504
issue.action.reopen = <b>@%[1]s</b> reopened #%[2]d.
502505
issue.action.merge = <b>@%[1]s</b> merged #%[2]d into %[3]s.
503506
issue.action.approve = <b>@%[1]s</b> approved this pull request.
@@ -1575,6 +1578,9 @@ issues.reopen_comment_issue = Reopen with Comment
15751578
issues.create_comment = Comment
15761579
issues.comment.blocked_user = Cannot create or edit comment because you are blocked by the poster or repository owner.
15771580
issues.closed_at = `closed this issue <a id="%[1]s" href="#%[1]s">%[2]s</a>`
1581+
issues.closed_as_archived_at = `closed this issue as archived <a id="%[1]s" href="#%[1]s">%[2]s</a>`
1582+
issues.closed_as_resolved_at = `closed this issue as resolved <a id="%[1]s" href="#%[1]s">%[2]s</a>`
1583+
issues.closed_as_stale_at = `closed this issue as stale <a id="%[1]s" href="#%[1]s">%[2]s</a>`
15781584
issues.reopened_at = `reopened this issue <a id="%[1]s" href="#%[1]s">%[2]s</a>`
15791585
issues.commit_ref_at = `referenced this issue from a commit <a id="%[1]s" href="#%[1]s">%[2]s</a>`
15801586
issues.ref_issue_from = `<a href="%[3]s">referenced this issue %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
@@ -1596,6 +1602,19 @@ issues.role.first_time_contributor = First-time contributor
15961602
issues.role.first_time_contributor_helper = This is the first contribution of this user to the repository.
15971603
issues.role.contributor = Contributor
15981604
issues.role.contributor_helper = This user has previously committed to the repository.
1605+
issues.close_as.reopen = Reopen
1606+
issues.close_as.common = Close Issue
1607+
issues.close_as.archived = Close as archived
1608+
issues.close_as.resolved = Close as resolved
1609+
issues.close_as.stale = Close as stale
1610+
issues.comment_and_close_as.reopen = Comment and Reopen
1611+
issues.comment_and_close_as.common = Comment and Close Issue
1612+
issues.comment_and_close_as.archived = Comment and Close as archived
1613+
issues.comment_and_close_as.resolved = Comment and Close as resolved
1614+
issues.comment_and_close_as.stale = Comment and Close as stale
1615+
issues.poster = Poster
1616+
issues.collaborator = Collaborator
1617+
issues.owner = Owner
15991618
issues.re_request_review=Re-request review
16001619
issues.is_stale = There have been changes to this PR since this review
16011620
issues.remove_request_review=Remove review request
@@ -1898,6 +1917,7 @@ pulls.update_branch_success = Branch update was successful
18981917
pulls.update_not_allowed = You are not allowed to update branch
18991918
pulls.outdated_with_base_branch = This branch is out-of-date with the base branch
19001919
pulls.close = Close Pull Request
1920+
pulls.comment_and_close = Comment and close Pull Request
19011921
pulls.closed_at = `closed this pull request <a id="%[1]s" href="#%[1]s">%[2]s</a>`
19021922
pulls.reopened_at = `reopened this pull request <a id="%[1]s" href="#%[1]s">%[2]s</a>`
19031923
pulls.cmd_instruction_hint = `View <a class="show-instruction">command line instructions</a>.`

0 commit comments

Comments
 (0)