* 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
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") |
} | } | ||||
// 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 { |
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 |
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) | ||||
} | } |
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 |