Skip to content
Merged
16 changes: 0 additions & 16 deletions models/issues/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,22 +78,6 @@ func (err ErrNewIssueInsert) Error() string {
return err.OriginalError.Error()
}

// ErrIssueWasClosed is used when close a closed issue
type ErrIssueWasClosed struct {
ID int64
Index int64
}

// IsErrIssueWasClosed checks if an error is a ErrIssueWasClosed.
func IsErrIssueWasClosed(err error) bool {
_, ok := err.(ErrIssueWasClosed)
return ok
}

func (err ErrIssueWasClosed) Error() string {
return fmt.Sprintf("Issue [%d] %d was already closed", err.ID, err.Index)
}

var ErrIssueAlreadyChanged = util.NewInvalidArgumentErrorf("the issue is already changed")

// Issue represents an issue or pull request of repository.
Expand Down
139 changes: 106 additions & 33 deletions models/issues/issue_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,44 @@ import (

// UpdateIssueCols updates cols of issue
func UpdateIssueCols(ctx context.Context, issue *Issue, cols ...string) error {
if _, err := db.GetEngine(ctx).ID(issue.ID).Cols(cols...).Update(issue); err != nil {
return err
}
return nil
_, err := db.GetEngine(ctx).ID(issue.ID).Cols(cols...).Update(issue)
return err
}

func ChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User, isClosed, isMergePull bool) (*Comment, error) {
// Reload the issue
currentIssue, err := GetIssueByID(ctx, issue.ID)
if err != nil {
return nil, err
}
// ErrIssueWasClosed is used when close a closed issue
type ErrIssueWasClosed struct {
ID int64
Index int64
}

// IsErrIssueWasClosed checks if an error is a ErrIssueWasClosed.
func IsErrIssueWasClosed(err error) bool {
_, ok := err.(ErrIssueWasClosed)
return ok
}

func (err ErrIssueWasClosed) Error() string {
return fmt.Sprintf("Issue [%d] %d was already closed", err.ID, err.Index)
}

// ErrPullWasClosed is used close a closed pull request
type ErrPullWasClosed struct {
ID int64
Index int64
}

// Nothing should be performed if current status is same as target status
if currentIssue.IsClosed == isClosed {
// IsErrPullWasClosed checks if an error is a ErrErrPullWasClosed.
func IsErrPullWasClosed(err error) bool {
_, ok := err.(ErrPullWasClosed)
return ok
}

func (err ErrPullWasClosed) Error() string {
return fmt.Sprintf("Pull request [%d] %d was already closed", err.ID, err.Index)
}

func closeIssue(ctx context.Context, issue *Issue, doer *user_model.User, isMergePull bool) (*Comment, error) {
if issue.IsClosed {
if !issue.IsPull {
return nil, ErrIssueWasClosed{
ID: issue.ID,
Expand All @@ -53,13 +76,8 @@ func ChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User,
}
}

issue.IsClosed = isClosed
return doChangeIssueStatus(ctx, issue, doer, isMergePull)
}

func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User, isMergePull bool) (*Comment, error) {
// Check for open dependencies
if issue.IsClosed && issue.Repo.IsDependenciesEnabled(ctx) {
if issue.Repo.IsDependenciesEnabled(ctx) {
// 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
noDeps, err := IssueNoDependenciesLeft(ctx, issue)
if err != nil {
Expand All @@ -71,16 +89,79 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
}
}

if issue.IsClosed {
issue.ClosedUnix = timeutil.TimeStampNow()
} else {
issue.ClosedUnix = 0
issue.IsClosed = true
issue.ClosedUnix = timeutil.TimeStampNow()

if cnt, err := db.GetEngine(ctx).ID(issue.ID).Cols("is_closed", "closed_unix").
Where("is_closed == ?", false).
Update(issue); err != nil {
return nil, err
} else if cnt != 1 {
return nil, ErrIssueAlreadyChanged
}

if err := UpdateIssueCols(ctx, issue, "is_closed", "closed_unix"); err != nil {
return updateIssueNumbers(ctx, issue, doer, util.Iif(isMergePull, CommentTypeMergePull, CommentTypeClose))
}

// ErrIssueWasOpened is used when reopen an opened issue
type ErrIssueWasOpened struct {
ID int64
Index int64
}

// IsErrIssueWasOpened checks if an error is a ErrIssueWasOpened.
func IsErrIssueWasOpened(err error) bool {
_, ok := err.(ErrIssueWasOpened)
return ok
}

func (err ErrIssueWasOpened) Error() string {
return fmt.Sprintf("Issue [%d] %d was already opened", err.ID, err.Index)
}

// ErrPullWasOpened is used reopen an opened pull request
type ErrPullWasOpened struct {
ID int64
Index int64
}

// ErrPullWasOpened checks if an error is a ErrPullWasOpened.
func IsErrPullWasOpened(err error) bool {
_, ok := err.(ErrPullWasOpened)
return ok
}

func (err ErrPullWasOpened) Error() string {
return fmt.Sprintf("Pull request [%d] %d was already opened", err.ID, err.Index)
}

func SetIssueAsReopen(ctx context.Context, issue *Issue, doer *user_model.User, isMergePull bool) (*Comment, error) {
if !issue.IsClosed {
if !issue.IsPull {
return nil, ErrIssueWasOpened{
ID: issue.ID,
}
}
return nil, ErrPullWasOpened{
ID: issue.ID,
}
}

issue.IsClosed = false
issue.ClosedUnix = 0

if cnt, err := db.GetEngine(ctx).ID(issue.ID).Cols("is_closed", "closed_unix").
Where("is_closed == ?", true).
Update(issue); err != nil {
return nil, err
} else if cnt != 1 {
return nil, ErrIssueAlreadyChanged
}

return updateIssueNumbers(ctx, issue, doer, CommentTypeReopen)
}

func updateIssueNumbers(ctx context.Context, issue *Issue, doer *user_model.User, cmtType CommentType) (*Comment, error) {
// Update issue count of labels
if err := issue.LoadLabels(ctx); err != nil {
return nil, err
Expand All @@ -103,14 +184,6 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
return nil, err
}

// New action comment
cmtType := CommentTypeClose
if !issue.IsClosed {
cmtType = CommentTypeReopen
} else if isMergePull {
cmtType = CommentTypeMergePull
}

return CreateComment(ctx, &CreateCommentOptions{
Type: cmtType,
Doer: doer,
Expand All @@ -134,7 +207,7 @@ func CloseIssue(ctx context.Context, issue *Issue, doer *user_model.User) (*Comm
}
defer committer.Close()

comment, err := ChangeIssueStatus(ctx, issue, doer, true, false)
comment, err := closeIssue(ctx, issue, doer, false)
if err != nil {
return nil, err
}
Expand All @@ -159,7 +232,7 @@ func ReopenIssue(ctx context.Context, issue *Issue, doer *user_model.User) (*Com
}
defer committer.Close()

comment, err := ChangeIssueStatus(ctx, issue, doer, false, false)
comment, err := SetIssueAsReopen(ctx, issue, doer, false)
if err != nil {
return nil, err
}
Expand Down
16 changes: 0 additions & 16 deletions models/issues/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,22 +80,6 @@ func (err ErrPullRequestAlreadyExists) Unwrap() error {
return util.ErrAlreadyExist
}

// ErrPullWasClosed is used close a closed pull request
type ErrPullWasClosed struct {
ID int64
Index int64
}

// IsErrPullWasClosed checks if an error is a ErrErrPullWasClosed.
func IsErrPullWasClosed(err error) bool {
_, ok := err.(ErrPullWasClosed)
return ok
}

func (err ErrPullWasClosed) Error() string {
return fmt.Sprintf("Pull request [%d] %d was already closed", err.ID, err.Index)
}

// PullRequestType defines pull request type
type PullRequestType int

Expand Down
2 changes: 2 additions & 0 deletions routers/private/hook_post_receive.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,8 @@ func handlePullRequestMerging(ctx *gitea_context.PrivateContext, opts *private.H
pr.MergedUnix = timeutil.TimeStampNow()
pr.Merger = pusher
pr.MergerID = pusher.ID
// reset the conflicted files as there cannot be any if we're merged
pr.ConflictedFiles = []string{}
err = db.WithTx(ctx, func(ctx context.Context) error {
// Removing an auto merge pull and ignore if not exist
if err := pull_model.DeleteScheduledAutoMerge(ctx, pr.ID); err != nil && !db.IsErrNotExist(err) {
Expand Down
2 changes: 2 additions & 0 deletions services/pull/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,8 @@ func manuallyMerged(ctx context.Context, pr *issues_model.PullRequest) bool {
}
pr.Merger = merger
pr.MergerID = merger.ID
// reset the conflicted files as there cannot be any if we're merged
pr.ConflictedFiles = []string{}

if merged, err := SetMerged(ctx, pr); err != nil {
log.Error("%-v setMerged : %v", pr, err)
Expand Down
38 changes: 16 additions & 22 deletions services/pull/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,8 @@ func MergedManually(ctx context.Context, pr *issues_model.PullRequest, doer *use
pr.Status = issues_model.PullRequestStatusManuallyMerged
pr.Merger = doer
pr.MergerID = doer.ID
// reset the conflicted files as there cannot be any if we're merged
pr.ConflictedFiles = []string{}

var merged bool
if merged, err = SetMerged(ctx, pr); err != nil {
Expand Down Expand Up @@ -667,32 +669,18 @@ func SetMerged(ctx context.Context, pr *issues_model.PullRequest) (bool, error)
}

pr.HasMerged = true
sess := db.GetEngine(ctx)

if _, err := sess.Exec("UPDATE `issue` SET `repo_id` = `repo_id` WHERE `id` = ?", pr.IssueID); err != nil {
return false, err
}

if _, err := sess.Exec("UPDATE `pull_request` SET `issue_id` = `issue_id` WHERE `id` = ?", pr.ID); err != nil {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return false, err
}
defer committer.Close()

pr.Issue = nil
if err := pr.LoadIssue(ctx); err != nil {
return false, err
}

if tmpPr, err := issues_model.GetPullRequestByID(ctx, pr.ID); err != nil {
return false, err
} else if tmpPr.HasMerged {
if pr.Issue.IsClosed {
return false, nil
}
return false, fmt.Errorf("PullRequest[%d] already merged but it's associated issue [%d] is not closed", pr.Index, pr.IssueID)
} else if pr.Issue.IsClosed {
return false, fmt.Errorf("PullRequest[%d] already closed", pr.Index)
}

if err := pr.Issue.LoadRepo(ctx); err != nil {
return false, err
}
Expand All @@ -701,16 +689,22 @@ func SetMerged(ctx context.Context, pr *issues_model.PullRequest) (bool, error)
return false, err
}

if _, err := issues_model.ChangeIssueStatus(ctx, pr.Issue, pr.Merger, true, true); err != nil {
if _, err := issues_model.SetIssueAsReopen(ctx, pr.Issue, pr.Merger, true); err != nil {
return false, fmt.Errorf("ChangeIssueStatus: %w", err)
}

// reset the conflicted files as there cannot be any if we're merged
pr.ConflictedFiles = []string{}

// We need to save all of the data used to compute this merge as it may have already been changed by TestPatch. FIXME: need to set some state to prevent TestPatch from running whilst we are merging.
if _, err := sess.Where("id = ?", pr.ID).Cols("has_merged, status, merge_base, merged_commit_id, merger_id, merged_unix, conflicted_files").Update(pr); err != nil {
if cnt, err := db.GetEngine(ctx).Where("id = ?", pr.ID).
And("has_merged = ?", false).
Cols("has_merged, status, merge_base, merged_commit_id, merger_id, merged_unix, conflicted_files").
Update(pr); err != nil {
return false, fmt.Errorf("failed to update pr[%d]: %w", pr.ID, err)
} else if cnt != 1 {
return false, issues_model.ErrIssueAlreadyChanged
}

if err := committer.Commit(); err != nil {
return false, err
}

return true, nil
Expand Down
Loading