Skip to content

Use CloseIssue and ReopenIssue instead of ChangeStatus #32467

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Dec 25, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion models/issues/dependency_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ 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)

left, err = issues_model.IssueNoDependenciesLeft(db.DefaultContext, issue1)
Expand Down
42 changes: 40 additions & 2 deletions models/issues/issue_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,16 +119,54 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
})
}

// 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
}
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, true, false)
if err != nil {
return nil, err
}
if err := committer.Commit(); err != nil {
return nil, err
}
return comment, nil
}

// ChangeIssueStatus changes issue status to open or closed.
func ChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User, isClosed bool) (*Comment, error) {
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
}

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, 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.
Expand Down
2 changes: 1 addition & 1 deletion models/issues/issue_xref_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
19 changes: 12 additions & 7 deletions routers/api/v1/repo/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -912,24 +912,29 @@ func EditIssue(ctx *context.APIContext) {
}
}

var isClosed bool
var closeOrReopen bool
switch state := api.StateType(*form.State); state {
case api.StateOpen:
isClosed = false
closeOrReopen = false
case api.StateClosed:
isClosed = true
closeOrReopen = true
default:
ctx.Error(http.StatusPreconditionFailed, "UnknownIssueStateError", fmt.Sprintf("unknown state: %s", state))
return
}

if issue.IsClosed != isClosed {
if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", isClosed); err != nil {
if closeOrReopen && !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 because it still has open dependencies")
return
}
ctx.Error(http.StatusInternalServerError, "ChangeStatus", err)
ctx.Error(http.StatusInternalServerError, "CloseIssue", err)
return
}
} else if !closeOrReopen && issue.IsClosed {
if err := issue_service.ReopenIssue(ctx, issue, ctx.Doer, ""); err != nil {
ctx.Error(http.StatusInternalServerError, "ReopenIssue", err)
return
}
}
Expand Down
17 changes: 11 additions & 6 deletions routers/api/v1/repo/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -753,24 +753,29 @@ func EditPullRequest(ctx *context.APIContext) {
return
}

var isClosed bool
var closeOrReopen bool
switch state := api.StateType(*form.State); state {
case api.StateOpen:
isClosed = false
closeOrReopen = false
case api.StateClosed:
isClosed = true
closeOrReopen = true
default:
ctx.Error(http.StatusPreconditionFailed, "UnknownPRStateError", fmt.Sprintf("unknown state: %s", state))
return
}

if issue.IsClosed != isClosed {
if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", isClosed); err != nil {
if closeOrReopen && !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 pull request because it still has open dependencies")
return
}
ctx.Error(http.StatusInternalServerError, "ChangeStatus", err)
ctx.Error(http.StatusInternalServerError, "CloseIssue", err)
return
}
} else if !closeOrReopen && issue.IsClosed {
if err := issue_service.ReopenIssue(ctx, issue, ctx.Doer, ""); err != nil {
ctx.Error(http.StatusInternalServerError, "ReopenIssue", err)
return
}
}
Expand Down
36 changes: 20 additions & 16 deletions routers/web/repo/issue_comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,25 +154,29 @@ 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"))
closeOrReopen := form.Status == "close"
if closeOrReopen && !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 !closeOrReopen && 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)
}
}
}
Expand Down
17 changes: 12 additions & 5 deletions routers/web/repo/issue_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,14 +435,16 @@ func UpdateIssueStatus(ctx *context.Context) {
return
}

var isClosed bool
var closeOrReopen bool // true: close, false: reopen
switch action := ctx.FormString("action"); action {
case "open":
isClosed = false
closeOrReopen = false
case "close":
isClosed = true
closeOrReopen = true
default:
log.Warn("Unrecognized action: %s", action)
ctx.JSONOK()
return
}

if _, err := issues.LoadRepositories(ctx); err != nil {
Expand All @@ -458,8 +460,8 @@ 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 closeOrReopen && !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),
Expand All @@ -469,6 +471,11 @@ func UpdateIssueStatus(ctx *context.Context) {
ctx.ServerError("ChangeStatus", err)
return
}
} else if !closeOrReopen && issue.IsClosed {
if err := issue_service.ReopenIssue(ctx, issue, ctx.Doer, ""); err != nil {
ctx.ServerError("ReopenIssue", err)
return
}
}
}
ctx.JSONOK()
Expand Down
19 changes: 12 additions & 7 deletions services/issue/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,15 +188,20 @@ 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 {

closeOrReopen := ref.Action == references.XRefActionCloses
refIssue.Repo = refRepo
if closeOrReopen && !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 !closeOrReopen && refIssue.IsClosed {
if err := ReopenIssue(ctx, refIssue, doer, c.Sha1); err != nil {
return err
}
}
Expand Down
46 changes: 33 additions & 13 deletions services/issue/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 and 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
}
10 changes: 7 additions & 3 deletions services/pull/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,14 +242,18 @@ 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 {
closeOrReopen := ref.RefAction == references.XRefActionCloses
if closeOrReopen && !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 !closeOrReopen && ref.Issue.IsClosed {
if err = issue_service.ReopenIssue(ctx, ref.Issue, doer, pr.MergedCommitID); err != nil {
return err
}
}
}
return nil
Expand Down
4 changes: 2 additions & 2 deletions services/pull/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -685,7 +685,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)
}
}
Expand Down Expand Up @@ -719,7 +719,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)
}
}
Expand Down
Loading