aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLunny Xiao <xiaolunwen@gmail.com>2024-12-24 23:38:30 -0800
committerGitHub <noreply@github.com>2024-12-25 07:38:30 +0000
commit5feb1a6bff6b6931ebe197258032557f55e32c6c (patch)
tree48e3aeb50c12c6bbffa86b0bea7cc9b22c1baf51
parentf44712f22bc7bfce049c64c27f60453ff1e41a5c (diff)
downloadgitea-5feb1a6bff6b6931ebe197258032557f55e32c6c.tar.gz
gitea-5feb1a6bff6b6931ebe197258032557f55e32c6c.zip
Use `CloseIssue` and `ReopenIssue` instead of `ChangeStatus` (#32467)
The behaviors of closing issues and reopening issues are very different. So splitting it into two different functions makes it easier to maintain. - [x] Split ChangeIssueStatus into CloseIssue and ReopenIssue both at the service layer and model layer - [x] Rename `isClosed` to `CloseOrReopen` to make it more readable. - [x] Add transactions for ReopenIssue and CloseIssue --------- Co-authored-by: Zettat123 <zettat123@gmail.com>
-rw-r--r--models/issues/dependency_test.go13
-rw-r--r--models/issues/issue_update.go44
-rw-r--r--models/issues/issue_xref_test.go2
-rw-r--r--routers/api/v1/repo/issue.go47
-rw-r--r--routers/api/v1/repo/pull.go22
-rw-r--r--routers/web/repo/issue_comment.go35
-rw-r--r--routers/web/repo/issue_list.go22
-rw-r--r--services/issue/commit.go18
-rw-r--r--services/issue/status.go46
-rw-r--r--services/pull/merge.go9
-rw-r--r--services/pull/pull.go4
11 files changed, 167 insertions, 95 deletions
diff --git a/models/issues/dependency_test.go b/models/issues/dependency_test.go
index 6eed483cc9..67418039de 100644
--- a/models/issues/dependency_test.go
+++ b/models/issues/dependency_test.go
@@ -49,9 +49,13 @@ func TestCreateIssueDependency(t *testing.T) {
assert.False(t, left)
// Close #2 and check again
- _, err = issues_model.ChangeIssueStatus(db.DefaultContext, issue2, user1, true)
+ _, err = issues_model.CloseIssue(db.DefaultContext, issue2, user1)
assert.NoError(t, err)
+ issue2Closed, err := issues_model.GetIssueByID(db.DefaultContext, 2)
+ assert.NoError(t, err)
+ assert.True(t, issue2Closed.IsClosed)
+
left, err = issues_model.IssueNoDependenciesLeft(db.DefaultContext, issue1)
assert.NoError(t, err)
assert.True(t, left)
@@ -59,4 +63,11 @@ func TestCreateIssueDependency(t *testing.T) {
// Test removing the dependency
err = issues_model.RemoveIssueDependency(db.DefaultContext, user1, issue1, issue2, issues_model.DependencyTypeBlockedBy)
assert.NoError(t, err)
+
+ _, err = issues_model.ReopenIssue(db.DefaultContext, issue2, user1)
+ assert.NoError(t, err)
+
+ issue2Reopened, err := issues_model.GetIssueByID(db.DefaultContext, 2)
+ assert.NoError(t, err)
+ assert.False(t, issue2Reopened.IsClosed)
}
diff --git a/models/issues/issue_update.go b/models/issues/issue_update.go
index 5b929c9115..ceb4a4027e 100644
--- a/models/issues/issue_update.go
+++ b/models/issues/issue_update.go
@@ -119,8 +119,8 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
})
}
-// ChangeIssueStatus changes issue status to open or closed.
-func ChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User, isClosed bool) (*Comment, error) {
+// CloseIssue changes issue status to closed.
+func CloseIssue(ctx context.Context, issue *Issue, doer *user_model.User) (*Comment, error) {
if err := issue.LoadRepo(ctx); err != nil {
return nil, err
}
@@ -128,7 +128,45 @@ func ChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User,
return nil, err
}
- return changeIssueStatus(ctx, issue, doer, isClosed, false)
+ ctx, committer, err := db.TxContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ defer committer.Close()
+
+ comment, err := changeIssueStatus(ctx, issue, doer, true, false)
+ if err != nil {
+ return nil, err
+ }
+ if err := committer.Commit(); err != nil {
+ return nil, err
+ }
+ return comment, nil
+}
+
+// ReopenIssue changes issue status to open.
+func ReopenIssue(ctx context.Context, issue *Issue, doer *user_model.User) (*Comment, error) {
+ if err := issue.LoadRepo(ctx); err != nil {
+ return nil, err
+ }
+ if err := issue.LoadPoster(ctx); err != nil {
+ return nil, err
+ }
+
+ ctx, committer, err := db.TxContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ defer committer.Close()
+
+ comment, err := changeIssueStatus(ctx, issue, doer, false, false)
+ if err != nil {
+ return nil, err
+ }
+ if err := committer.Commit(); err != nil {
+ return nil, err
+ }
+ return comment, nil
}
// ChangeIssueTitle changes the title of this issue, as the given user.
diff --git a/models/issues/issue_xref_test.go b/models/issues/issue_xref_test.go
index f1b1bb2a6b..7f257330b7 100644
--- a/models/issues/issue_xref_test.go
+++ b/models/issues/issue_xref_test.go
@@ -98,7 +98,7 @@ func TestXRef_ResolveCrossReferences(t *testing.T) {
i1 := testCreateIssue(t, 1, 2, "title1", "content1", false)
i2 := testCreateIssue(t, 1, 2, "title2", "content2", false)
i3 := testCreateIssue(t, 1, 2, "title3", "content3", false)
- _, err := issues_model.ChangeIssueStatus(db.DefaultContext, i3, d, true)
+ _, err := issues_model.CloseIssue(db.DefaultContext, i3, d)
assert.NoError(t, err)
pr := testCreatePR(t, 1, 2, "titlepr", fmt.Sprintf("closes #%d", i1.Index))
diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go
index 6f45fce226..86dbcee5f7 100644
--- a/routers/api/v1/repo/issue.go
+++ b/routers/api/v1/repo/issue.go
@@ -733,7 +733,7 @@ func CreateIssue(ctx *context.APIContext) {
}
if form.Closed {
- if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", true); err != nil {
+ if err := issue_service.CloseIssue(ctx, issue, ctx.Doer, ""); err != nil {
if issues_model.IsErrDependenciesLeft(err) {
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
return
@@ -912,27 +912,11 @@ func EditIssue(ctx *context.APIContext) {
}
}
- var isClosed bool
- switch state := api.StateType(*form.State); state {
- case api.StateOpen:
- isClosed = false
- case api.StateClosed:
- isClosed = true
- default:
- ctx.Error(http.StatusPreconditionFailed, "UnknownIssueStateError", fmt.Sprintf("unknown state: %s", state))
+ state := api.StateType(*form.State)
+ closeOrReopenIssue(ctx, issue, state)
+ if ctx.Written() {
return
}
-
- if issue.IsClosed != isClosed {
- if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", isClosed); err != nil {
- if issues_model.IsErrDependenciesLeft(err) {
- ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
- return
- }
- ctx.Error(http.StatusInternalServerError, "ChangeStatus", err)
- return
- }
- }
}
// Refetch from database to assign some automatic values
@@ -1055,3 +1039,26 @@ func UpdateIssueDeadline(ctx *context.APIContext) {
ctx.JSON(http.StatusCreated, api.IssueDeadline{Deadline: deadlineUnix.AsTimePtr()})
}
+
+func closeOrReopenIssue(ctx *context.APIContext, issue *issues_model.Issue, state api.StateType) {
+ if state != api.StateOpen && state != api.StateClosed {
+ ctx.Error(http.StatusPreconditionFailed, "UnknownIssueStateError", fmt.Sprintf("unknown state: %s", state))
+ return
+ }
+
+ if state == api.StateClosed && !issue.IsClosed {
+ if err := issue_service.CloseIssue(ctx, issue, ctx.Doer, ""); err != nil {
+ if issues_model.IsErrDependenciesLeft(err) {
+ ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue or pull request because it still has open dependencies")
+ return
+ }
+ ctx.Error(http.StatusInternalServerError, "CloseIssue", err)
+ return
+ }
+ } else if state == api.StateOpen && issue.IsClosed {
+ if err := issue_service.ReopenIssue(ctx, issue, ctx.Doer, ""); err != nil {
+ ctx.Error(http.StatusInternalServerError, "ReopenIssue", err)
+ return
+ }
+ }
+}
diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go
index ca5120eef5..d0c3459b63 100644
--- a/routers/api/v1/repo/pull.go
+++ b/routers/api/v1/repo/pull.go
@@ -728,27 +728,11 @@ func EditPullRequest(ctx *context.APIContext) {
return
}
- var isClosed bool
- switch state := api.StateType(*form.State); state {
- case api.StateOpen:
- isClosed = false
- case api.StateClosed:
- isClosed = true
- default:
- ctx.Error(http.StatusPreconditionFailed, "UnknownPRStateError", fmt.Sprintf("unknown state: %s", state))
+ state := api.StateType(*form.State)
+ closeOrReopenIssue(ctx, issue, state)
+ if ctx.Written() {
return
}
-
- if issue.IsClosed != isClosed {
- if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", isClosed); err != nil {
- if issues_model.IsErrDependenciesLeft(err) {
- ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies")
- return
- }
- ctx.Error(http.StatusInternalServerError, "ChangeStatus", err)
- return
- }
- }
}
// change pull target branch
diff --git a/routers/web/repo/issue_comment.go b/routers/web/repo/issue_comment.go
index 438822431d..8564c613de 100644
--- a/routers/web/repo/issue_comment.go
+++ b/routers/web/repo/issue_comment.go
@@ -154,25 +154,28 @@ func NewComment(ctx *context.Context) {
if pr != nil {
ctx.Flash.Info(ctx.Tr("repo.pulls.open_unmerged_pull_exists", pr.Index))
} else {
- isClosed := form.Status == "close"
- if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", isClosed); err != nil {
- log.Error("ChangeStatus: %v", err)
-
- if issues_model.IsErrDependenciesLeft(err) {
- if issue.IsPull {
- ctx.JSONError(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
- } else {
- ctx.JSONError(ctx.Tr("repo.issues.dependency.issue_close_blocked"))
+ if form.Status == "close" && !issue.IsClosed {
+ if err := issue_service.CloseIssue(ctx, issue, ctx.Doer, ""); err != nil {
+ log.Error("CloseIssue: %v", err)
+ if issues_model.IsErrDependenciesLeft(err) {
+ if issue.IsPull {
+ ctx.JSONError(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
+ } else {
+ ctx.JSONError(ctx.Tr("repo.issues.dependency.issue_close_blocked"))
+ }
+ return
}
- return
+ } else {
+ if err := stopTimerIfAvailable(ctx, ctx.Doer, issue); err != nil {
+ ctx.ServerError("stopTimerIfAvailable", err)
+ return
+ }
+ log.Trace("Issue [%d] status changed to closed: %v", issue.ID, issue.IsClosed)
}
- } else {
- if err := stopTimerIfAvailable(ctx, ctx.Doer, issue); err != nil {
- ctx.ServerError("CreateOrStopIssueStopwatch", err)
- return
+ } else if form.Status == "reopen" && issue.IsClosed {
+ if err := issue_service.ReopenIssue(ctx, issue, ctx.Doer, ""); err != nil {
+ log.Error("ReopenIssue: %v", err)
}
-
- log.Trace("Issue [%d] status changed to closed: %v", issue.ID, issue.IsClosed)
}
}
}
diff --git a/routers/web/repo/issue_list.go b/routers/web/repo/issue_list.go
index 2c73a24632..2f615a100e 100644
--- a/routers/web/repo/issue_list.go
+++ b/routers/web/repo/issue_list.go
@@ -418,14 +418,11 @@ func UpdateIssueStatus(ctx *context.Context) {
return
}
- var isClosed bool
- switch action := ctx.FormString("action"); action {
- case "open":
- isClosed = false
- case "close":
- isClosed = true
- default:
+ action := ctx.FormString("action")
+ if action != "open" && action != "close" {
log.Warn("Unrecognized action: %s", action)
+ ctx.JSONOK()
+ return
}
if _, err := issues.LoadRepositories(ctx); err != nil {
@@ -441,15 +438,20 @@ func UpdateIssueStatus(ctx *context.Context) {
if issue.IsPull && issue.PullRequest.HasMerged {
continue
}
- if issue.IsClosed != isClosed {
- if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", isClosed); err != nil {
+ if action == "close" && !issue.IsClosed {
+ if err := issue_service.CloseIssue(ctx, issue, ctx.Doer, ""); err != nil {
if issues_model.IsErrDependenciesLeft(err) {
ctx.JSON(http.StatusPreconditionFailed, map[string]any{
"error": ctx.Tr("repo.issues.dependency.issue_batch_close_blocked", issue.Index),
})
return
}
- ctx.ServerError("ChangeStatus", err)
+ ctx.ServerError("CloseIssue", err)
+ return
+ }
+ } else if action == "open" && issue.IsClosed {
+ if err := issue_service.ReopenIssue(ctx, issue, ctx.Doer, ""); err != nil {
+ ctx.ServerError("ReopenIssue", err)
return
}
}
diff --git a/services/issue/commit.go b/services/issue/commit.go
index 0579e0f5c5..963d0359fd 100644
--- a/services/issue/commit.go
+++ b/services/issue/commit.go
@@ -188,15 +188,19 @@ func UpdateIssuesCommit(ctx context.Context, doer *user_model.User, repo *repo_m
continue
}
}
- isClosed := ref.Action == references.XRefActionCloses
- if isClosed && len(ref.TimeLog) > 0 {
- if err := issueAddTime(ctx, refIssue, doer, c.Timestamp, ref.TimeLog); err != nil {
+
+ refIssue.Repo = refRepo
+ if ref.Action == references.XRefActionCloses && !refIssue.IsClosed {
+ if len(ref.TimeLog) > 0 {
+ if err := issueAddTime(ctx, refIssue, doer, c.Timestamp, ref.TimeLog); err != nil {
+ return err
+ }
+ }
+ if err := CloseIssue(ctx, refIssue, doer, c.Sha1); err != nil {
return err
}
- }
- if isClosed != refIssue.IsClosed {
- refIssue.Repo = refRepo
- if err := ChangeStatus(ctx, refIssue, doer, c.Sha1, isClosed); err != nil {
+ } else if ref.Action == references.XRefActionReopens && refIssue.IsClosed {
+ if err := ReopenIssue(ctx, refIssue, doer, c.Sha1); err != nil {
return err
}
}
diff --git a/services/issue/status.go b/services/issue/status.go
index 967c29bd22..e18b891175 100644
--- a/services/issue/status.go
+++ b/services/issue/status.go
@@ -6,34 +6,54 @@ package issue
import (
"context"
+ "code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
notify_service "code.gitea.io/gitea/services/notify"
)
-// ChangeStatus changes issue status to open or closed.
-// closed means the target status
-// Fix me: you should check whether the current issue status is same to the target status before call this function
-// as in function changeIssueStatus we will return WasClosedError, even the issue status and target status are both open
-func ChangeStatus(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, commitID string, closed bool) error {
- comment, err := issues_model.ChangeIssueStatus(ctx, issue, doer, closed)
+// CloseIssue close an issue.
+func CloseIssue(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, commitID string) error {
+ dbCtx, committer, err := db.TxContext(ctx)
if err != nil {
- if issues_model.IsErrDependenciesLeft(err) && closed {
- if err := issues_model.FinishIssueStopwatchIfPossible(ctx, doer, issue); err != nil {
+ return err
+ }
+ defer committer.Close()
+
+ comment, err := issues_model.CloseIssue(dbCtx, issue, doer)
+ if err != nil {
+ if issues_model.IsErrDependenciesLeft(err) {
+ if err := issues_model.FinishIssueStopwatchIfPossible(dbCtx, doer, issue); err != nil {
log.Error("Unable to stop stopwatch for issue[%d]#%d: %v", issue.ID, issue.Index, err)
}
}
return err
}
- if closed {
- if err := issues_model.FinishIssueStopwatchIfPossible(ctx, doer, issue); err != nil {
- return err
- }
+ if err := issues_model.FinishIssueStopwatchIfPossible(dbCtx, doer, issue); err != nil {
+ return err
+ }
+
+ if err := committer.Commit(); err != nil {
+ return err
+ }
+ committer.Close()
+
+ notify_service.IssueChangeStatus(ctx, doer, commitID, issue, comment, true)
+
+ return nil
+}
+
+// ReopenIssue reopen an issue.
+// FIXME: If some issues dependent this one are closed, should we also reopen them?
+func ReopenIssue(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, commitID string) error {
+ comment, err := issues_model.ReopenIssue(ctx, issue, doer)
+ if err != nil {
+ return err
}
- notify_service.IssueChangeStatus(ctx, doer, commitID, issue, comment, closed)
+ notify_service.IssueChangeStatus(ctx, doer, commitID, issue, comment, false)
return nil
}
diff --git a/services/pull/merge.go b/services/pull/merge.go
index fba85f1e51..75011a697c 100644
--- a/services/pull/merge.go
+++ b/services/pull/merge.go
@@ -263,14 +263,17 @@ func handleCloseCrossReferences(ctx context.Context, pr *issues_model.PullReques
if err = ref.Issue.LoadRepo(ctx); err != nil {
return err
}
- isClosed := ref.RefAction == references.XRefActionCloses
- if isClosed != ref.Issue.IsClosed {
- if err = issue_service.ChangeStatus(ctx, ref.Issue, doer, pr.MergedCommitID, isClosed); err != nil {
+ if ref.RefAction == references.XRefActionCloses && !ref.Issue.IsClosed {
+ if err = issue_service.CloseIssue(ctx, ref.Issue, doer, pr.MergedCommitID); err != nil {
// Allow ErrDependenciesLeft
if !issues_model.IsErrDependenciesLeft(err) {
return err
}
}
+ } else if ref.RefAction == references.XRefActionReopens && ref.Issue.IsClosed {
+ if err = issue_service.ReopenIssue(ctx, ref.Issue, doer, pr.MergedCommitID); err != nil {
+ return err
+ }
}
}
return nil
diff --git a/services/pull/pull.go b/services/pull/pull.go
index 0256c2c3f6..ac91fd6409 100644
--- a/services/pull/pull.go
+++ b/services/pull/pull.go
@@ -706,7 +706,7 @@ func CloseBranchPulls(ctx context.Context, doer *user_model.User, repoID int64,
var errs errlist
for _, pr := range prs {
- if err = issue_service.ChangeStatus(ctx, pr.Issue, doer, "", true); err != nil && !issues_model.IsErrPullWasClosed(err) && !issues_model.IsErrDependenciesLeft(err) {
+ if err = issue_service.CloseIssue(ctx, pr.Issue, doer, ""); err != nil && !issues_model.IsErrPullWasClosed(err) && !issues_model.IsErrDependenciesLeft(err) {
errs = append(errs, err)
}
}
@@ -740,7 +740,7 @@ func CloseRepoBranchesPulls(ctx context.Context, doer *user_model.User, repo *re
if pr.BaseRepoID == repo.ID {
continue
}
- if err = issue_service.ChangeStatus(ctx, pr.Issue, doer, "", true); err != nil && !issues_model.IsErrPullWasClosed(err) {
+ if err = issue_service.CloseIssue(ctx, pr.Issue, doer, ""); err != nil && !issues_model.IsErrPullWasClosed(err) {
errs = append(errs, err)
}
}