mirror of
https://github.com/go-gitea/gitea
synced 2025-07-22 18:28:37 +00:00
Move and merge some functions about issue
This commit is contained in:
@@ -1136,33 +1136,16 @@ func UpdateCommentInvalidate(ctx context.Context, c *Comment) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateComment updates information of comment.
|
// UpdateComment updates information of comment.
|
||||||
func UpdateComment(ctx context.Context, c *Comment, contentVersion int, doer *user_model.User) error {
|
func UpdateComment(ctx context.Context, c *Comment, contentVersion int) error {
|
||||||
ctx, committer, err := db.TxContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer committer.Close()
|
|
||||||
sess := db.GetEngine(ctx)
|
|
||||||
|
|
||||||
c.ContentVersion = contentVersion + 1
|
c.ContentVersion = contentVersion + 1
|
||||||
|
|
||||||
affected, err := sess.ID(c.ID).AllCols().Where("content_version = ?", contentVersion).Update(c)
|
affected, err := db.GetEngine(ctx).ID(c.ID).AllCols().Where("content_version = ?", contentVersion).Update(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if affected == 0 {
|
if affected == 0 {
|
||||||
return ErrCommentAlreadyChanged
|
return ErrCommentAlreadyChanged
|
||||||
}
|
}
|
||||||
if err := c.LoadIssue(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := c.AddCrossReferences(ctx, doer, true); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := committer.Commit(); err != nil {
|
|
||||||
return fmt.Errorf("Commit: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1192,7 +1175,7 @@ func DeleteComment(ctx context.Context, comment *Comment) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := comment.neuterCrossReferences(ctx); err != nil {
|
if err := neuterCrossReferences(ctx, comment.IssueID, comment.ID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -94,8 +94,6 @@ func (err ErrIssueWasClosed) Error() string {
|
|||||||
return fmt.Sprintf("Issue [%d] %d was already closed", err.ID, err.Index)
|
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.
|
// Issue represents an issue or pull request of repository.
|
||||||
type Issue struct {
|
type Issue struct {
|
||||||
ID int64 `xorm:"pk autoincr"`
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
@@ -216,6 +216,40 @@ func TestIssue_loadTotalTimes(t *testing.T) {
|
|||||||
assert.Equal(t, int64(3682), ms.TotalTrackedTime)
|
assert.Equal(t, int64(3682), ms.TotalTrackedTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// createIssue creates new issue with labels for repository.
|
||||||
|
func createIssue(ctx context.Context, repo *repo_model.Repository, issue *issues_model.Issue, labelIDs []int64, uuids []string) (err error) {
|
||||||
|
ctx, committer, err := db.TxContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer committer.Close()
|
||||||
|
|
||||||
|
idx, err := db.GetNextResourceIndex(ctx, "issue_index", repo.ID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("generate issue index failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
issue.Index = idx
|
||||||
|
|
||||||
|
if err = issues_model.NewIssueWithIndex(ctx, issue.Poster, issues_model.NewIssueOptions{
|
||||||
|
Repo: repo,
|
||||||
|
Issue: issue,
|
||||||
|
LabelIDs: labelIDs,
|
||||||
|
Attachments: uuids,
|
||||||
|
}); err != nil {
|
||||||
|
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || issues_model.IsErrNewIssueInsert(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fmt.Errorf("newIssue: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = committer.Commit(); err != nil {
|
||||||
|
return fmt.Errorf("Commit: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func testInsertIssue(t *testing.T, title, content string, expectIndex int64) *issues_model.Issue {
|
func testInsertIssue(t *testing.T, title, content string, expectIndex int64) *issues_model.Issue {
|
||||||
var newIssue issues_model.Issue
|
var newIssue issues_model.Issue
|
||||||
t.Run(title, func(t *testing.T) {
|
t.Run(title, func(t *testing.T) {
|
||||||
@@ -229,7 +263,7 @@ func testInsertIssue(t *testing.T, title, content string, expectIndex int64) *is
|
|||||||
Title: title,
|
Title: title,
|
||||||
Content: content,
|
Content: content,
|
||||||
}
|
}
|
||||||
err := issues_model.NewIssue(db.DefaultContext, repo, &issue, nil, nil)
|
err := createIssue(db.DefaultContext, repo, &issue, nil, nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
has, err := db.GetEngine(db.DefaultContext).ID(issue.ID).Get(&newIssue)
|
has, err := db.GetEngine(db.DefaultContext).ID(issue.ID).Get(&newIssue)
|
||||||
|
@@ -33,7 +33,7 @@ func UpdateIssueCols(ctx context.Context, issue *Issue, cols ...string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func changeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User, isClosed, isMergePull bool) (*Comment, error) {
|
func ChangeIssuePullStatus(ctx context.Context, issue *Issue, doer *user_model.User, isClosed, isMergePull bool) (*Comment, error) {
|
||||||
// Reload the issue
|
// Reload the issue
|
||||||
currentIssue, err := GetIssueByID(ctx, issue.ID)
|
currentIssue, err := GetIssueByID(ctx, issue.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -127,41 +127,7 @@ func ChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return changeIssueStatus(ctx, issue, doer, isClosed, false)
|
return ChangeIssuePullStatus(ctx, issue, doer, isClosed, false)
|
||||||
}
|
|
||||||
|
|
||||||
// ChangeIssueTitle changes the title of this issue, as the given user.
|
|
||||||
func ChangeIssueTitle(ctx context.Context, issue *Issue, doer *user_model.User, oldTitle string) (err error) {
|
|
||||||
ctx, committer, err := db.TxContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer committer.Close()
|
|
||||||
|
|
||||||
if err = UpdateIssueCols(ctx, issue, "name"); err != nil {
|
|
||||||
return fmt.Errorf("updateIssueCols: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = issue.LoadRepo(ctx); err != nil {
|
|
||||||
return fmt.Errorf("loadRepo: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := &CreateCommentOptions{
|
|
||||||
Type: CommentTypeChangeTitle,
|
|
||||||
Doer: doer,
|
|
||||||
Repo: issue.Repo,
|
|
||||||
Issue: issue,
|
|
||||||
OldTitle: oldTitle,
|
|
||||||
NewTitle: issue.Title,
|
|
||||||
}
|
|
||||||
if _, err = CreateComment(ctx, opts); err != nil {
|
|
||||||
return fmt.Errorf("createComment: %w", err)
|
|
||||||
}
|
|
||||||
if err = issue.AddCrossReferences(ctx, doer, true); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return committer.Commit()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChangeIssueRef changes the branch of this issue, as the given user.
|
// ChangeIssueRef changes the branch of this issue, as the given user.
|
||||||
@@ -234,48 +200,6 @@ func UpdateIssueAttachments(ctx context.Context, issueID int64, uuids []string)
|
|||||||
return committer.Commit()
|
return committer.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChangeIssueContent changes issue content, as the given user.
|
|
||||||
func ChangeIssueContent(ctx context.Context, issue *Issue, doer *user_model.User, content string, contentVersion int) (err error) {
|
|
||||||
ctx, committer, err := db.TxContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer committer.Close()
|
|
||||||
|
|
||||||
hasContentHistory, err := HasIssueContentHistory(ctx, issue.ID, 0)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("HasIssueContentHistory: %w", err)
|
|
||||||
}
|
|
||||||
if !hasContentHistory {
|
|
||||||
if err = SaveIssueContentHistory(ctx, issue.PosterID, issue.ID, 0,
|
|
||||||
issue.CreatedUnix, issue.Content, true); err != nil {
|
|
||||||
return fmt.Errorf("SaveIssueContentHistory: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
issue.Content = content
|
|
||||||
issue.ContentVersion = contentVersion + 1
|
|
||||||
|
|
||||||
affected, err := db.GetEngine(ctx).ID(issue.ID).Cols("content", "content_version").Where("content_version = ?", contentVersion).Update(issue)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if affected == 0 {
|
|
||||||
return ErrIssueAlreadyChanged
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = SaveIssueContentHistory(ctx, doer.ID, issue.ID, 0,
|
|
||||||
timeutil.TimeStampNow(), issue.Content, false); err != nil {
|
|
||||||
return fmt.Errorf("SaveIssueContentHistory: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = issue.AddCrossReferences(ctx, doer, true); err != nil {
|
|
||||||
return fmt.Errorf("addCrossReferences: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return committer.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewIssueOptions represents the options of a new issue.
|
// NewIssueOptions represents the options of a new issue.
|
||||||
type NewIssueOptions struct {
|
type NewIssueOptions struct {
|
||||||
Repo *repo_model.Repository
|
Repo *repo_model.Repository
|
||||||
@@ -365,6 +289,10 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := UpdateIssueAttachments(ctx, opts.Issue.ID, opts.Attachments); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if len(opts.Attachments) > 0 {
|
if len(opts.Attachments) > 0 {
|
||||||
attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, opts.Attachments)
|
attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, opts.Attachments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -385,40 +313,6 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue
|
|||||||
return opts.Issue.AddCrossReferences(ctx, doer, false)
|
return opts.Issue.AddCrossReferences(ctx, doer, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewIssue creates new issue with labels for repository.
|
|
||||||
func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) {
|
|
||||||
ctx, committer, err := db.TxContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer committer.Close()
|
|
||||||
|
|
||||||
idx, err := db.GetNextResourceIndex(ctx, "issue_index", repo.ID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("generate issue index failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
issue.Index = idx
|
|
||||||
|
|
||||||
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
|
|
||||||
Repo: repo,
|
|
||||||
Issue: issue,
|
|
||||||
LabelIDs: labelIDs,
|
|
||||||
Attachments: uuids,
|
|
||||||
}); err != nil {
|
|
||||||
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return fmt.Errorf("newIssue: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = committer.Commit(); err != nil {
|
|
||||||
return fmt.Errorf("Commit: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateIssueMentions updates issue-user relations for mentioned users.
|
// UpdateIssueMentions updates issue-user relations for mentioned users.
|
||||||
func UpdateIssueMentions(ctx context.Context, issueID int64, mentions []*user_model.User) error {
|
func UpdateIssueMentions(ctx context.Context, issueID int64, mentions []*user_model.User) error {
|
||||||
if len(mentions) == 0 {
|
if len(mentions) == 0 {
|
||||||
|
@@ -68,10 +68,10 @@ func (issue *Issue) AddCrossReferences(stdCtx context.Context, doer *user_model.
|
|||||||
OrigIssue: issue,
|
OrigIssue: issue,
|
||||||
RemoveOld: removeOld,
|
RemoveOld: removeOld,
|
||||||
}
|
}
|
||||||
return issue.createCrossReferences(stdCtx, ctx, issue.Title, issue.Content)
|
return createCrossReferences(stdCtx, ctx, issue.Title, issue.Content)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (issue *Issue) createCrossReferences(stdCtx context.Context, ctx *crossReferencesContext, plaincontent, mdcontent string) error {
|
func createCrossReferences(stdCtx context.Context, ctx *crossReferencesContext, plaincontent, mdcontent string) error {
|
||||||
xreflist, err := ctx.OrigIssue.getCrossReferences(stdCtx, ctx, plaincontent, mdcontent)
|
xreflist, err := ctx.OrigIssue.getCrossReferences(stdCtx, ctx, plaincontent, mdcontent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -248,11 +248,7 @@ func (c *Comment) AddCrossReferences(stdCtx context.Context, doer *user_model.Us
|
|||||||
OrigComment: c,
|
OrigComment: c,
|
||||||
RemoveOld: removeOld,
|
RemoveOld: removeOld,
|
||||||
}
|
}
|
||||||
return c.Issue.createCrossReferences(stdCtx, ctx, "", c.Content)
|
return createCrossReferences(stdCtx, ctx, "", c.Content)
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Comment) neuterCrossReferences(ctx context.Context) error {
|
|
||||||
return neuterCrossReferences(ctx, c.IssueID, c.ID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadRefComment loads comment that created this reference from database
|
// LoadRefComment loads comment that created this reference from database
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
package issues_test
|
package issues_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -81,15 +82,53 @@ func TestXRef_NeuterCrossReferences(t *testing.T) {
|
|||||||
assert.Equal(t, issues_model.CommentTypeIssueRef, ref.Type)
|
assert.Equal(t, issues_model.CommentTypeIssueRef, ref.Type)
|
||||||
assert.Equal(t, references.XRefActionNone, ref.RefAction)
|
assert.Equal(t, references.XRefActionNone, ref.RefAction)
|
||||||
|
|
||||||
d := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
|
||||||
i.Title = "title2, no mentions"
|
|
||||||
assert.NoError(t, issues_model.ChangeIssueTitle(db.DefaultContext, i, d, title))
|
|
||||||
|
|
||||||
ref = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0})
|
ref = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0})
|
||||||
assert.Equal(t, issues_model.CommentTypeIssueRef, ref.Type)
|
assert.Equal(t, issues_model.CommentTypeIssueRef, ref.Type)
|
||||||
assert.Equal(t, references.XRefActionNeutered, ref.RefAction)
|
assert.Equal(t, references.XRefActionNeutered, ref.RefAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newPullRequest creates new pull request with labels for repository.
|
||||||
|
func newPullRequest(ctx context.Context, repo *repo_model.Repository, issue *issues_model.Issue, labelIDs []int64, uuids []string, pr *issues_model.PullRequest) (err error) {
|
||||||
|
ctx, committer, err := db.TxContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer committer.Close()
|
||||||
|
|
||||||
|
idx, err := db.GetNextResourceIndex(ctx, "issue_index", repo.ID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("generate pull request index failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
issue.Index = idx
|
||||||
|
|
||||||
|
if err = issues_model.NewIssueWithIndex(ctx, issue.Poster, issues_model.NewIssueOptions{
|
||||||
|
Repo: repo,
|
||||||
|
Issue: issue,
|
||||||
|
LabelIDs: labelIDs,
|
||||||
|
Attachments: uuids,
|
||||||
|
IsPull: true,
|
||||||
|
}); err != nil {
|
||||||
|
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || issues_model.IsErrNewIssueInsert(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fmt.Errorf("newIssue: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pr.Index = issue.Index
|
||||||
|
pr.BaseRepo = repo
|
||||||
|
pr.IssueID = issue.ID
|
||||||
|
if err = db.Insert(ctx, pr); err != nil {
|
||||||
|
return fmt.Errorf("insert pull repo: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = committer.Commit(); err != nil {
|
||||||
|
return fmt.Errorf("Commit: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestXRef_ResolveCrossReferences(t *testing.T) {
|
func TestXRef_ResolveCrossReferences(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
@@ -163,7 +202,7 @@ func testCreatePR(t *testing.T, repo, doer int64, title, content string) *issues
|
|||||||
d := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: doer})
|
d := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: doer})
|
||||||
i := &issues_model.Issue{RepoID: r.ID, PosterID: d.ID, Poster: d, Title: title, Content: content, IsPull: true}
|
i := &issues_model.Issue{RepoID: r.ID, PosterID: d.ID, Poster: d, Title: title, Content: content, IsPull: true}
|
||||||
pr := &issues_model.PullRequest{HeadRepoID: repo, BaseRepoID: repo, HeadBranch: "head", BaseBranch: "base", Status: issues_model.PullRequestStatusMergeable}
|
pr := &issues_model.PullRequest{HeadRepoID: repo, BaseRepoID: repo, HeadBranch: "head", BaseBranch: "base", Status: issues_model.PullRequestStatusMergeable}
|
||||||
assert.NoError(t, issues_model.NewPullRequest(db.DefaultContext, r, i, nil, nil, pr))
|
assert.NoError(t, newPullRequest(db.DefaultContext, r, i, nil, nil, pr))
|
||||||
pr.Issue = i
|
pr.Issue = i
|
||||||
return pr
|
return pr
|
||||||
}
|
}
|
||||||
|
@@ -499,107 +499,6 @@ func (pr *PullRequest) IsFromFork() bool {
|
|||||||
return pr.HeadRepoID != pr.BaseRepoID
|
return pr.HeadRepoID != pr.BaseRepoID
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetMerged sets a pull request to merged and closes the corresponding issue
|
|
||||||
func (pr *PullRequest) SetMerged(ctx context.Context) (bool, error) {
|
|
||||||
if pr.HasMerged {
|
|
||||||
return false, fmt.Errorf("PullRequest[%d] already merged", pr.Index)
|
|
||||||
}
|
|
||||||
if pr.MergedCommitID == "" || pr.MergedUnix == 0 || pr.Merger == nil {
|
|
||||||
return false, fmt.Errorf("Unable to merge PullRequest[%d], some required fields are empty", pr.Index)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pr.Issue = nil
|
|
||||||
if err := pr.LoadIssue(ctx); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if tmpPr, err := 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
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pr.Issue.Repo.LoadOwner(ctx); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := changeIssueStatus(ctx, pr.Issue, pr.Merger, true, true); err != nil {
|
|
||||||
return false, fmt.Errorf("Issue.changeStatus: %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 {
|
|
||||||
return false, fmt.Errorf("Failed to update pr[%d]: %w", pr.ID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPullRequest creates new pull request with labels for repository.
|
|
||||||
func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string, pr *PullRequest) (err error) {
|
|
||||||
ctx, committer, err := db.TxContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer committer.Close()
|
|
||||||
|
|
||||||
idx, err := db.GetNextResourceIndex(ctx, "issue_index", repo.ID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("generate pull request index failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
issue.Index = idx
|
|
||||||
|
|
||||||
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
|
|
||||||
Repo: repo,
|
|
||||||
Issue: issue,
|
|
||||||
LabelIDs: labelIDs,
|
|
||||||
Attachments: uuids,
|
|
||||||
IsPull: true,
|
|
||||||
}); err != nil {
|
|
||||||
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return fmt.Errorf("newIssue: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pr.Index = issue.Index
|
|
||||||
pr.BaseRepo = repo
|
|
||||||
pr.IssueID = issue.ID
|
|
||||||
if err = db.Insert(ctx, pr); err != nil {
|
|
||||||
return fmt.Errorf("insert pull repo: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = committer.Commit(); err != nil {
|
|
||||||
return fmt.Errorf("Commit: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrUserMustCollaborator represents an error that the user must be a collaborator to a given repo.
|
// ErrUserMustCollaborator represents an error that the user must be a collaborator to a given repo.
|
||||||
type ErrUserMustCollaborator struct {
|
type ErrUserMustCollaborator struct {
|
||||||
UserID int64
|
UserID int64
|
||||||
|
@@ -812,7 +812,7 @@ func EditIssue(ctx *context.APIContext) {
|
|||||||
if form.Body != nil {
|
if form.Body != nil {
|
||||||
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body, issue.ContentVersion)
|
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body, issue.ContentVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, issues_model.ErrIssueAlreadyChanged) {
|
if errors.Is(err, issue_service.ErrIssueAlreadyChanged) {
|
||||||
ctx.Error(http.StatusBadRequest, "ChangeContent", err)
|
ctx.Error(http.StatusBadRequest, "ChangeContent", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@@ -630,7 +630,7 @@ func EditPullRequest(ctx *context.APIContext) {
|
|||||||
if form.Body != nil {
|
if form.Body != nil {
|
||||||
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body, issue.ContentVersion)
|
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body, issue.ContentVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, issues_model.ErrIssueAlreadyChanged) {
|
if errors.Is(err, issue_service.ErrIssueAlreadyChanged) {
|
||||||
ctx.Error(http.StatusBadRequest, "ChangeContent", err)
|
ctx.Error(http.StatusBadRequest, "ChangeContent", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@@ -368,7 +368,7 @@ func handlePullRequestMerging(ctx *gitea_context.PrivateContext, opts *private.H
|
|||||||
if err := pull_model.DeleteScheduledAutoMerge(ctx, pr.ID); err != nil && !db.IsErrNotExist(err) {
|
if err := pull_model.DeleteScheduledAutoMerge(ctx, pr.ID); err != nil && !db.IsErrNotExist(err) {
|
||||||
return fmt.Errorf("DeleteScheduledAutoMerge[%d]: %v", opts.PullRequestID, err)
|
return fmt.Errorf("DeleteScheduledAutoMerge[%d]: %v", opts.PullRequestID, err)
|
||||||
}
|
}
|
||||||
if _, err := pr.SetMerged(ctx); err != nil {
|
if _, err := pull_service.SetMerged(ctx, pr); err != nil {
|
||||||
return fmt.Errorf("SetMerged failed: %s/%s Error: %v", ownerName, repoName, err)
|
return fmt.Errorf("SetMerged failed: %s/%s Error: %v", ownerName, repoName, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@@ -2286,7 +2286,7 @@ func UpdateIssueContent(ctx *context.Context) {
|
|||||||
if err := issue_service.ChangeContent(ctx, issue, ctx.Doer, ctx.Req.FormValue("content"), ctx.FormInt("content_version")); err != nil {
|
if err := issue_service.ChangeContent(ctx, issue, ctx.Doer, ctx.Req.FormValue("content"), ctx.FormInt("content_version")); err != nil {
|
||||||
if errors.Is(err, user_model.ErrBlockedUser) {
|
if errors.Is(err, user_model.ErrBlockedUser) {
|
||||||
ctx.JSONError(ctx.Tr("repo.issues.edit.blocked_user"))
|
ctx.JSONError(ctx.Tr("repo.issues.edit.blocked_user"))
|
||||||
} else if errors.Is(err, issues_model.ErrIssueAlreadyChanged) {
|
} else if errors.Is(err, issue_service.ErrIssueAlreadyChanged) {
|
||||||
if issue.IsPull {
|
if issue.IsPull {
|
||||||
ctx.JSONError(ctx.Tr("repo.pulls.edit.already_changed"))
|
ctx.JSONError(ctx.Tr("repo.pulls.edit.already_changed"))
|
||||||
} else {
|
} else {
|
||||||
|
@@ -110,7 +110,14 @@ func UpdateComment(ctx context.Context, c *issues_model.Comment, contentVersion
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := issues_model.UpdateComment(ctx, c, contentVersion, doer); err != nil {
|
if err := issues_model.UpdateComment(ctx, c, contentVersion); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.LoadIssue(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.AddCrossReferences(ctx, doer, true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,13 +5,19 @@ package issue
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
notify_service "code.gitea.io/gitea/services/notify"
|
notify_service "code.gitea.io/gitea/services/notify"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrIssueAlreadyChanged = util.NewInvalidArgumentErrorf("the issue is already changed")
|
||||||
|
|
||||||
// ChangeContent changes issue content, as the given user.
|
// ChangeContent changes issue content, as the given user.
|
||||||
func ChangeContent(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, content string, contentVersion int) error {
|
func ChangeContent(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, content string, contentVersion int) error {
|
||||||
if err := issue.LoadRepo(ctx); err != nil {
|
if err := issue.LoadRepo(ctx); err != nil {
|
||||||
@@ -26,9 +32,36 @@ func ChangeContent(ctx context.Context, issue *issues_model.Issue, doer *user_mo
|
|||||||
|
|
||||||
oldContent := issue.Content
|
oldContent := issue.Content
|
||||||
|
|
||||||
if err := issues_model.ChangeIssueContent(ctx, issue, doer, content, contentVersion); err != nil {
|
hasContentHistory, err := issues_model.HasIssueContentHistory(ctx, issue.ID, 0)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("HasIssueContentHistory: %w", err)
|
||||||
|
}
|
||||||
|
if !hasContentHistory {
|
||||||
|
if err = issues_model.SaveIssueContentHistory(ctx, issue.PosterID, issue.ID, 0,
|
||||||
|
issue.CreatedUnix, issue.Content, true); err != nil {
|
||||||
|
return fmt.Errorf("SaveIssueContentHistory: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
issue.Content = content
|
||||||
|
issue.ContentVersion = contentVersion + 1
|
||||||
|
|
||||||
|
affected, err := db.GetEngine(ctx).ID(issue.ID).Cols("content", "content_version").Where("content_version = ?", contentVersion).Update(issue)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if affected == 0 {
|
||||||
|
return ErrIssueAlreadyChanged
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = issues_model.SaveIssueContentHistory(ctx, doer.ID, issue.ID, 0,
|
||||||
|
timeutil.TimeStampNow(), issue.Content, false); err != nil {
|
||||||
|
return fmt.Errorf("SaveIssueContentHistory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = issue.AddCrossReferences(ctx, doer, true); err != nil {
|
||||||
|
return fmt.Errorf("addCrossReferences: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
notify_service.IssueChangeContent(ctx, doer, issue, oldContent)
|
notify_service.IssueChangeContent(ctx, doer, issue, oldContent)
|
||||||
|
|
||||||
|
@@ -33,9 +33,25 @@ func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *issues_mo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
if err := issues_model.NewIssue(ctx, repo, issue, labelIDs, uuids); err != nil {
|
idx, err := db.GetNextResourceIndex(ctx, "issue_index", repo.ID)
|
||||||
return err
|
if err != nil {
|
||||||
|
return fmt.Errorf("generate issue index failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
issue.Index = idx
|
||||||
|
|
||||||
|
if err = issues_model.NewIssueWithIndex(ctx, issue.Poster, issues_model.NewIssueOptions{
|
||||||
|
Repo: repo,
|
||||||
|
Issue: issue,
|
||||||
|
LabelIDs: labelIDs,
|
||||||
|
Attachments: uuids,
|
||||||
|
}); err != nil {
|
||||||
|
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || issues_model.IsErrNewIssueInsert(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fmt.Errorf("newIssue: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
for _, assigneeID := range assigneeIDs {
|
for _, assigneeID := range assigneeIDs {
|
||||||
if _, err := AddAssigneeIfNotAssigned(ctx, issue, issue.Poster, assigneeID, true); err != nil {
|
if _, err := AddAssigneeIfNotAssigned(ctx, issue, issue.Poster, assigneeID, true); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -86,7 +102,26 @@ func ChangeTitle(ctx context.Context, issue *issues_model.Issue, doer *user_mode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := issues_model.ChangeIssueTitle(ctx, issue, doer, oldTitle); err != nil {
|
if err := issues_model.UpdateIssueCols(ctx, issue, "name"); err != nil {
|
||||||
|
return fmt.Errorf("updateIssueCols: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := issue.LoadRepo(ctx); err != nil {
|
||||||
|
return fmt.Errorf("loadRepo: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := &issues_model.CreateCommentOptions{
|
||||||
|
Type: issues_model.CommentTypeChangeTitle,
|
||||||
|
Doer: doer,
|
||||||
|
Repo: issue.Repo,
|
||||||
|
Issue: issue,
|
||||||
|
OldTitle: oldTitle,
|
||||||
|
NewTitle: issue.Title,
|
||||||
|
}
|
||||||
|
if _, err := issues_model.CreateComment(ctx, opts); err != nil {
|
||||||
|
return fmt.Errorf("createComment: %w", err)
|
||||||
|
}
|
||||||
|
if err := issue.AddCrossReferences(ctx, doer, true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -292,7 +292,7 @@ func manuallyMerged(ctx context.Context, pr *issues_model.PullRequest) bool {
|
|||||||
pr.Merger = merger
|
pr.Merger = merger
|
||||||
pr.MergerID = merger.ID
|
pr.MergerID = merger.ID
|
||||||
|
|
||||||
if merged, err := pr.SetMerged(ctx); err != nil {
|
if merged, err := SetMerged(ctx, pr); err != nil {
|
||||||
log.Error("%-v setMerged : %v", pr, err)
|
log.Error("%-v setMerged : %v", pr, err)
|
||||||
return false
|
return false
|
||||||
} else if !merged {
|
} else if !merged {
|
||||||
|
@@ -490,6 +490,65 @@ func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullReques
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetMerged sets a pull request to merged and closes the corresponding issue
|
||||||
|
func SetMerged(ctx context.Context, pr *issues_model.PullRequest) (bool, error) {
|
||||||
|
if pr.HasMerged {
|
||||||
|
return false, fmt.Errorf("PullRequest[%d] already merged", pr.Index)
|
||||||
|
}
|
||||||
|
if pr.MergedCommitID == "" || pr.MergedUnix == 0 || pr.Merger == nil {
|
||||||
|
return false, fmt.Errorf("Unable to merge PullRequest[%d], some required fields are empty", pr.Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pr.Issue.Repo.LoadOwner(ctx); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := issues_model.ChangeIssuePullStatus(ctx, pr.Issue, pr.Merger, true, true); err != nil {
|
||||||
|
return false, fmt.Errorf("Issue.changeStatus: %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 {
|
||||||
|
return false, fmt.Errorf("Failed to update pr[%d]: %w", pr.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
// MergedManually mark pr as merged manually
|
// MergedManually mark pr as merged manually
|
||||||
func MergedManually(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, baseGitRepo *git.Repository, commitID string) error {
|
func MergedManually(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, baseGitRepo *git.Repository, commitID string) error {
|
||||||
releaser, err := globallock.Lock(ctx, getPullWorkingLockKey(pr.ID))
|
releaser, err := globallock.Lock(ctx, getPullWorkingLockKey(pr.ID))
|
||||||
@@ -543,7 +602,7 @@ func MergedManually(ctx context.Context, pr *issues_model.PullRequest, doer *use
|
|||||||
pr.MergerID = doer.ID
|
pr.MergerID = doer.ID
|
||||||
|
|
||||||
var merged bool
|
var merged bool
|
||||||
if merged, err = pr.SetMerged(ctx); err != nil {
|
if merged, err = SetMerged(ctx, pr); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if !merged {
|
} else if !merged {
|
||||||
return fmt.Errorf("SetMerged failed")
|
return fmt.Errorf("SetMerged failed")
|
||||||
|
@@ -104,8 +104,31 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *iss
|
|||||||
|
|
||||||
var reviewNotifiers []*issue_service.ReviewRequestNotifier
|
var reviewNotifiers []*issue_service.ReviewRequestNotifier
|
||||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
if err := issues_model.NewPullRequest(ctx, repo, issue, labelIDs, uuids, pr); err != nil {
|
idx, err := db.GetNextResourceIndex(ctx, "issue_index", repo.ID)
|
||||||
return err
|
if err != nil {
|
||||||
|
return fmt.Errorf("generate pull request index failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
issue.Index = idx
|
||||||
|
|
||||||
|
if err = issues_model.NewIssueWithIndex(ctx, issue.Poster, issues_model.NewIssueOptions{
|
||||||
|
Repo: repo,
|
||||||
|
Issue: issue,
|
||||||
|
LabelIDs: labelIDs,
|
||||||
|
Attachments: uuids,
|
||||||
|
IsPull: true,
|
||||||
|
}); err != nil {
|
||||||
|
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || issues_model.IsErrNewIssueInsert(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fmt.Errorf("newIssue: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pr.Index = issue.Index
|
||||||
|
pr.BaseRepo = repo
|
||||||
|
pr.IssueID = issue.ID
|
||||||
|
if err = db.Insert(ctx, pr); err != nil {
|
||||||
|
return fmt.Errorf("insert pull repo: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, assigneeID := range assigneeIDs {
|
for _, assigneeID := range assigneeIDs {
|
||||||
|
Reference in New Issue
Block a user