@@ -28,38 +28,40 @@ import (
2828
2929// UpdateIssueCols updates cols of issue
3030func UpdateIssueCols (ctx context.Context , issue * Issue , cols ... string ) error {
31- if _ , err := db .GetEngine (ctx ).ID (issue .ID ).Cols (cols ... ).Update (issue ); err != nil {
32- return err
33- }
34- return nil
31+ _ , err := db .GetEngine (ctx ).ID (issue .ID ).Cols (cols ... ).Update (issue )
32+ return err
3533}
3634
37- func ChangeIssueStatus (ctx context.Context , issue * Issue , doer * user_model.User , isClosed , isMergePull bool ) (* Comment , error ) {
38- // Reload the issue
39- currentIssue , err := GetIssueByID (ctx , issue .ID )
40- if err != nil {
41- return nil , err
42- }
35+ // ErrIssueIsClosed is used when close a closed issue
36+ type ErrIssueIsClosed struct {
37+ ID int64
38+ RepoID int64
39+ Index int64
40+ IsPull bool
41+ }
4342
44- // Nothing should be performed if current status is same as target status
45- if currentIssue .IsClosed == isClosed {
46- if ! issue .IsPull {
47- return nil , ErrIssueWasClosed {
48- ID : issue .ID ,
49- }
50- }
51- return nil , ErrPullWasClosed {
52- ID : issue .ID ,
53- }
54- }
43+ // IsErrIssueIsClosed checks if an error is a ErrIssueIsClosed.
44+ func IsErrIssueIsClosed (err error ) bool {
45+ _ , ok := err .(ErrIssueIsClosed )
46+ return ok
47+ }
5548
56- issue . IsClosed = isClosed
57- return doChangeIssueStatus ( ctx , issue , doer , isMergePull )
49+ func ( err ErrIssueIsClosed ) Error () string {
50+ return fmt . Sprintf ( "%s [id: %d, repo_id: %d, index: %d] is already closed" , util . Iif ( err . IsPull , "Pull Request" , "Issue" ), err . ID , err . RepoID , err . Index )
5851}
5952
60- func doChangeIssueStatus (ctx context.Context , issue * Issue , doer * user_model.User , isMergePull bool ) (* Comment , error ) {
53+ func SetIssueAsClosed (ctx context.Context , issue * Issue , doer * user_model.User , isMergePull bool ) (* Comment , error ) {
54+ if issue .IsClosed {
55+ return nil , ErrIssueIsClosed {
56+ ID : issue .ID ,
57+ RepoID : issue .RepoID ,
58+ Index : issue .Index ,
59+ IsPull : issue .IsPull ,
60+ }
61+ }
62+
6163 // Check for open dependencies
62- if issue .IsClosed && issue . Repo .IsDependenciesEnabled (ctx ) {
64+ if issue .Repo .IsDependenciesEnabled (ctx ) {
6365 // only check if dependencies are enabled and we're about to close an issue, otherwise reopening an issue would fail when there are unsatisfied dependencies
6466 noDeps , err := IssueNoDependenciesLeft (ctx , issue )
6567 if err != nil {
@@ -71,16 +73,63 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
7173 }
7274 }
7375
74- if issue .IsClosed {
75- issue .ClosedUnix = timeutil .TimeStampNow ()
76- } else {
77- issue .ClosedUnix = 0
76+ issue .IsClosed = true
77+ issue .ClosedUnix = timeutil .TimeStampNow ()
78+
79+ if cnt , err := db .GetEngine (ctx ).ID (issue .ID ).Cols ("is_closed" , "closed_unix" ).
80+ Where ("is_closed = ?" , false ).
81+ Update (issue ); err != nil {
82+ return nil , err
83+ } else if cnt != 1 {
84+ return nil , ErrIssueAlreadyChanged
7885 }
7986
80- if err := UpdateIssueCols (ctx , issue , "is_closed" , "closed_unix" ); err != nil {
87+ return updateIssueNumbers (ctx , issue , doer , util .Iif (isMergePull , CommentTypeMergePull , CommentTypeClose ))
88+ }
89+
90+ // ErrIssueIsOpen is used when reopen an opened issue
91+ type ErrIssueIsOpen struct {
92+ ID int64
93+ RepoID int64
94+ IsPull bool
95+ Index int64
96+ }
97+
98+ // IsErrIssueIsOpen checks if an error is a ErrIssueIsOpen.
99+ func IsErrIssueIsOpen (err error ) bool {
100+ _ , ok := err .(ErrIssueIsOpen )
101+ return ok
102+ }
103+
104+ func (err ErrIssueIsOpen ) Error () string {
105+ return fmt .Sprintf ("%s [id: %d, repo_id: %d, index: %d] is already open" , util .Iif (err .IsPull , "Pull Request" , "Issue" ), err .ID , err .RepoID , err .Index )
106+ }
107+
108+ func setIssueAsReopen (ctx context.Context , issue * Issue , doer * user_model.User ) (* Comment , error ) {
109+ if ! issue .IsClosed {
110+ return nil , ErrIssueIsOpen {
111+ ID : issue .ID ,
112+ RepoID : issue .RepoID ,
113+ Index : issue .Index ,
114+ IsPull : issue .IsPull ,
115+ }
116+ }
117+
118+ issue .IsClosed = false
119+ issue .ClosedUnix = 0
120+
121+ if cnt , err := db .GetEngine (ctx ).ID (issue .ID ).Cols ("is_closed" , "closed_unix" ).
122+ Where ("is_closed = ?" , true ).
123+ Update (issue ); err != nil {
81124 return nil , err
125+ } else if cnt != 1 {
126+ return nil , ErrIssueAlreadyChanged
82127 }
83128
129+ return updateIssueNumbers (ctx , issue , doer , CommentTypeReopen )
130+ }
131+
132+ func updateIssueNumbers (ctx context.Context , issue * Issue , doer * user_model.User , cmtType CommentType ) (* Comment , error ) {
84133 // Update issue count of labels
85134 if err := issue .LoadLabels (ctx ); err != nil {
86135 return nil , err
@@ -103,14 +152,6 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
103152 return nil , err
104153 }
105154
106- // New action comment
107- cmtType := CommentTypeClose
108- if ! issue .IsClosed {
109- cmtType = CommentTypeReopen
110- } else if isMergePull {
111- cmtType = CommentTypeMergePull
112- }
113-
114155 return CreateComment (ctx , & CreateCommentOptions {
115156 Type : cmtType ,
116157 Doer : doer ,
@@ -134,7 +175,7 @@ func CloseIssue(ctx context.Context, issue *Issue, doer *user_model.User) (*Comm
134175 }
135176 defer committer .Close ()
136177
137- comment , err := ChangeIssueStatus (ctx , issue , doer , true , false )
178+ comment , err := SetIssueAsClosed (ctx , issue , doer , false )
138179 if err != nil {
139180 return nil , err
140181 }
@@ -159,7 +200,7 @@ func ReopenIssue(ctx context.Context, issue *Issue, doer *user_model.User) (*Com
159200 }
160201 defer committer .Close ()
161202
162- comment , err := ChangeIssueStatus (ctx , issue , doer , false , false )
203+ comment , err := setIssueAsReopen (ctx , issue , doer )
163204 if err != nil {
164205 return nil , err
165206 }
0 commit comments