@@ -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