Browse Source

Only check for conflicts/merging if the PR has not been merged in the interim (#10132) (#10206)

* Only check for conflicts/merging if the PR has not been merged in the interim (#10132)

* Only check for merging if the PR has not been merged in the interim

* fixup! Only check for merging if the PR has not been merged in the interim

* Try to fix test failure

* Use PR2 not PR1 in tests as PR1 merges automatically

* return already merged error

* enforce locking

* move pullrequest checking to after merge

This might improve the chance that the race does not affect us but does not prevent it.

* Remove minor race with getting merge commit id

move check pr after merge

* Remove unnecessary prepareTestEnv - onGiteaRun does this for us

* Add information about when merging occuring

* More logging

Co-authored-by: Lauris BH <lauris@nix.lv>
Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com>

* re order

Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: Lauris BH <lauris@nix.lv>
Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com>
tags/v1.11.1
6543 4 years ago
parent
commit
c95d9603ea
No account linked to committer's email address
5 changed files with 68 additions and 35 deletions
  1. 1
    9
      integrations/pull_merge_test.go
  2. 46
    18
      models/pull.go
  3. 5
    3
      services/pull/check.go
  4. 2
    2
      services/pull/check_test.go
  5. 14
    3
      services/pull/merge.go

+ 1
- 9
integrations/pull_merge_test.go View File



func TestPullRebaseMerge(t *testing.T) { func TestPullRebaseMerge(t *testing.T) {
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
defer prepareTestEnv(t)()

hookTasks, err := models.HookTasks(1, 1) //Retrieve previous hook number hookTasks, err := models.HookTasks(1, 1) //Retrieve previous hook number
assert.NoError(t, err) assert.NoError(t, err)
hookTasksLenBefore := len(hookTasks) hookTasksLenBefore := len(hookTasks)


func TestPullSquash(t *testing.T) { func TestPullSquash(t *testing.T) {
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
defer prepareTestEnv(t)()

hookTasks, err := models.HookTasks(1, 1) //Retrieve previous hook number hookTasks, err := models.HookTasks(1, 1) //Retrieve previous hook number
assert.NoError(t, err) assert.NoError(t, err)
hookTasksLenBefore := len(hookTasks) hookTasksLenBefore := len(hookTasks)


func TestPullCleanUpAfterMerge(t *testing.T) { func TestPullCleanUpAfterMerge(t *testing.T) {
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
defer prepareTestEnv(t)()
session := loginUser(t, "user1") session := loginUser(t, "user1")
testRepoFork(t, session, "user2", "repo1", "user1", "repo1") testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feature/test", "README.md", "Hello, World (Edited)\n")
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feature/test", "README.md", "Hello, World (Edited - TestPullCleanUpAfterMerge)\n")


resp := testPullCreate(t, session, "user1", "repo1", "feature/test", "This is a pull title") resp := testPullCreate(t, session, "user1", "repo1", "feature/test", "This is a pull title")




func TestCantMergeWorkInProgress(t *testing.T) { func TestCantMergeWorkInProgress(t *testing.T) {
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
defer prepareTestEnv(t)()
session := loginUser(t, "user1") session := loginUser(t, "user1")
testRepoFork(t, session, "user2", "repo1", "user1", "repo1") testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")


func TestCantMergeConflict(t *testing.T) { func TestCantMergeConflict(t *testing.T) {
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
defer prepareTestEnv(t)()
session := loginUser(t, "user1") session := loginUser(t, "user1")
testRepoFork(t, session, "user2", "repo1", "user1", "repo1") testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "conflict", "README.md", "Hello, World (Edited Once)\n") testEditFileToNewBranch(t, session, "user1", "repo1", "master", "conflict", "README.md", "Hello, World (Edited Once)\n")


func TestCantMergeUnrelated(t *testing.T) { func TestCantMergeUnrelated(t *testing.T) {
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
defer prepareTestEnv(t)()
session := loginUser(t, "user1") session := loginUser(t, "user1")
testRepoFork(t, session, "user2", "repo1", "user1", "repo1") testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "base", "README.md", "Hello, World (Edited Twice)\n") testEditFileToNewBranch(t, session, "user1", "repo1", "master", "base", "README.md", "Hello, World (Edited Twice)\n")

+ 46
- 18
models/pull.go View File

} }


// SetMerged sets a pull request to merged and closes the corresponding issue // SetMerged sets a pull request to merged and closes the corresponding issue
func (pr *PullRequest) SetMerged() (err error) {
func (pr *PullRequest) SetMerged() (bool, error) {
if pr.HasMerged { if pr.HasMerged {
return fmt.Errorf("PullRequest[%d] already merged", pr.Index)
return false, fmt.Errorf("PullRequest[%d] already merged", pr.Index)
} }
if pr.MergedCommitID == "" || pr.MergedUnix == 0 || pr.Merger == nil { if pr.MergedCommitID == "" || pr.MergedUnix == 0 || pr.Merger == nil {
return fmt.Errorf("Unable to merge PullRequest[%d], some required fields are empty", pr.Index)
return false, fmt.Errorf("Unable to merge PullRequest[%d], some required fields are empty", pr.Index)
} }


pr.HasMerged = true pr.HasMerged = true


sess := x.NewSession() sess := x.NewSession()
defer sess.Close() defer sess.Close()
if err = sess.Begin(); err != nil {
return err
if err := sess.Begin(); err != nil {
return false, err
} }


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


if err = pr.Issue.loadRepo(sess); err != nil {
return err
if _, err := sess.Exec("UPDATE `pull_request` SET `issue_id` = `issue_id` WHERE `id` = ?", pr.ID); err != nil {
return false, err
} }
if err = pr.Issue.Repo.getOwner(sess); err != nil {
return err

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

if tmpPr, err := getPullRequestByID(sess, 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.changeStatus(sess, pr.Merger, true); err != nil {
return fmt.Errorf("Issue.changeStatus: %v", err)
if err := pr.Issue.loadRepo(sess); err != nil {
return false, err
} }
if _, err = sess.ID(pr.ID).Cols("has_merged, status, merged_commit_id, merger_id, merged_unix").Update(pr); err != nil {
return fmt.Errorf("update pull request: %v", err)

if err := pr.Issue.Repo.getOwner(sess); err != nil {
return false, err
} }


if err = sess.Commit(); err != nil {
return fmt.Errorf("Commit: %v", err)
if _, err := pr.Issue.changeStatus(sess, pr.Merger, true); err != nil {
return false, fmt.Errorf("Issue.changeStatus: %v", err)
} }
return nil

if _, err := sess.Where("id = ?", pr.ID).Cols("has_merged, status, merged_commit_id, merger_id, merged_unix").Update(pr); err != nil {
return false, fmt.Errorf("Failed to update pr[%d]: %v", pr.ID, err)
}

if err := sess.Commit(); err != nil {
return false, fmt.Errorf("Commit: %v", err)
}
return true, nil
} }


// NewPullRequest creates new pull request with labels for repository. // NewPullRequest creates new pull request with labels for repository.
return err return err
} }


// UpdateColsIfNotMerged updates specific fields of a pull request if it has not been merged
func (pr *PullRequest) UpdateColsIfNotMerged(cols ...string) error {
_, err := x.Where("id = ? AND has_merged = ?", pr.ID, false).Cols(cols...).Update(pr)
return err
}

// IsWorkInProgress determine if the Pull Request is a Work In Progress by its title // IsWorkInProgress determine if the Pull Request is a Work In Progress by its title
func (pr *PullRequest) IsWorkInProgress() bool { func (pr *PullRequest) IsWorkInProgress() bool {
if err := pr.LoadIssue(); err != nil { if err := pr.LoadIssue(); err != nil {

+ 5
- 3
services/pull/check.go View File

func AddToTaskQueue(pr *models.PullRequest) { func AddToTaskQueue(pr *models.PullRequest) {
go pullRequestQueue.AddFunc(pr.ID, func() { go pullRequestQueue.AddFunc(pr.ID, func() {
pr.Status = models.PullRequestStatusChecking pr.Status = models.PullRequestStatusChecking
if err := pr.UpdateCols("status"); err != nil {
if err := pr.UpdateColsIfNotMerged("status"); err != nil {
log.Error("AddToTaskQueue.UpdateCols[%d].(add to queue): %v", pr.ID, err) log.Error("AddToTaskQueue.UpdateCols[%d].(add to queue): %v", pr.ID, err)
} }
}) })
pr.Merger = merger pr.Merger = merger
pr.MergerID = merger.ID pr.MergerID = merger.ID


if err = pr.SetMerged(); err != nil {
if merged, err := pr.SetMerged(); err != nil {
log.Error("PullRequest[%d].setMerged : %v", pr.ID, err) log.Error("PullRequest[%d].setMerged : %v", pr.ID, err)
return false return false
} else if !merged {
return false
} }


baseGitRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath()) baseGitRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath())
if err != nil { if err != nil {
log.Error("GetPullRequestByID[%s]: %v", prID, err) log.Error("GetPullRequestByID[%s]: %v", prID, err)
continue continue
} else if pr.Status != models.PullRequestStatusChecking {
} else if pr.HasMerged {
continue continue
} else if manuallyMerged(pr) { } else if manuallyMerged(pr) {
continue continue

+ 2
- 2
services/pull/check_test.go View File

func TestPullRequest_AddToTaskQueue(t *testing.T) { func TestPullRequest_AddToTaskQueue(t *testing.T) {
assert.NoError(t, models.PrepareTestDatabase()) assert.NoError(t, models.PrepareTestDatabase())


pr := models.AssertExistsAndLoadBean(t, &models.PullRequest{ID: 1}).(*models.PullRequest)
pr := models.AssertExistsAndLoadBean(t, &models.PullRequest{ID: 2}).(*models.PullRequest)
AddToTaskQueue(pr) AddToTaskQueue(pr)


select { select {
} }


assert.True(t, pullRequestQueue.Exist(pr.ID)) assert.True(t, pullRequestQueue.Exist(pr.ID))
pr = models.AssertExistsAndLoadBean(t, &models.PullRequest{ID: 1}).(*models.PullRequest)
pr = models.AssertExistsAndLoadBean(t, &models.PullRequest{ID: 2}).(*models.PullRequest)
assert.Equal(t, models.PullRequestStatusChecking, pr.Status) assert.Equal(t, models.PullRequestStatusChecking, pr.Status)
} }

+ 14
- 3
services/pull/merge.go View File

outbuf.Reset() outbuf.Reset()
errbuf.Reset() errbuf.Reset()


pr.MergedCommitID, err = baseGitRepo.GetBranchCommitID(pr.BaseBranch)
pr.MergedCommitID, err = git.GetFullCommitID(tmpBasePath, baseBranch)
if err != nil { if err != nil {
return fmt.Errorf("GetBranchCommit: %v", err)
return fmt.Errorf("Failed to get full commit id for the new merge: %v", err)
} }


pr.MergedUnix = timeutil.TimeStampNow() pr.MergedUnix = timeutil.TimeStampNow()
pr.Merger = doer pr.Merger = doer
pr.MergerID = doer.ID pr.MergerID = doer.ID


if err = pr.SetMerged(); err != nil {
if _, err = pr.SetMerged(); err != nil {
log.Error("setMerged [%d]: %v", pr.ID, err) log.Error("setMerged [%d]: %v", pr.ID, err)
} }


if err := pr.LoadIssue(); err != nil {
log.Error("loadIssue [%d]: %v", pr.ID, err)
}

if err := pr.Issue.LoadRepo(); err != nil {
log.Error("loadRepo for issue [%d]: %v", pr.ID, err)
}
if err := pr.Issue.Repo.GetOwner(); err != nil {
log.Error("GetOwner for issue repo [%d]: %v", pr.ID, err)
}

notification.NotifyMergePullRequest(pr, doer, baseGitRepo) notification.NotifyMergePullRequest(pr, doer, baseGitRepo)


// Reset cached commit count // Reset cached commit count

Loading…
Cancel
Save