[GITEA] avoid superfluous synchronized pull_request run when opening a PR (#2236)
* Split TestPullRequest out of AddTestPullRequestTask * Before scheduling the task, AddTestPullRequestTask stores the max index of the repository * When the task runs, it does not take into account pull requests that have an index higher than the recorded max index When AddTestPullRequestTask is called with isSync == true, it is the direct consequence of a new commit being pushed. Forgejo knows nothing of this new commit yet. If a PR is created later and its head references the new commit, it will have an index that is higher and must not be taken into account. It would be acting and triggering a notification for a PR based on an event that happened before it existed. Refs: https://codeberg.org/forgejo/forgejo/issues/2009 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2236 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Co-authored-by: Earl Warren <contact@earl-warren.org> Co-committed-by: Earl Warren <contact@earl-warren.org> (cherry picked from commit b3be895a30b32bfae4acfa32db54406e1dd1dc21)
This commit is contained in:
parent
c335d076aa
commit
036f1eddc5
9 changed files with 333 additions and 91 deletions
|
@ -181,7 +181,7 @@ func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.U
|
|||
}
|
||||
|
||||
defer func() {
|
||||
go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false, "", "")
|
||||
AddTestPullRequestTask(ctx, doer, pr.BaseRepo.ID, pr.BaseBranch, false, "", "")
|
||||
}()
|
||||
|
||||
pr.MergedCommitID, err = doMergeAndPush(ctx, pr, doer, mergeStyle, expectedHeadCommitID, message)
|
||||
|
|
|
@ -287,113 +287,129 @@ func checkForInvalidation(ctx context.Context, requests issues_model.PullRequest
|
|||
|
||||
// AddTestPullRequestTask adds new test tasks by given head/base repository and head/base branch,
|
||||
// and generate new patch for testing as needed.
|
||||
func AddTestPullRequestTask(doer *user_model.User, repoID int64, branch string, isSync bool, oldCommitID, newCommitID string) {
|
||||
log.Trace("AddTestPullRequestTask [head_repo_id: %d, head_branch: %s]: finding pull requests", repoID, branch)
|
||||
graceful.GetManager().RunWithShutdownContext(func(ctx context.Context) {
|
||||
func AddTestPullRequestTask(ctx context.Context, doer *user_model.User, repoID int64, branch string, isSync bool, oldCommitID, newCommitID string) {
|
||||
// When TestPullRequest runs it must ignore any PR with an index > maxPR because they
|
||||
// would have been created after the goroutine started. They are in the future.
|
||||
// This guards the following race:
|
||||
// * commit A is pushed
|
||||
// * goroutine starts but does not run TestPullRequest yet
|
||||
// * a pull request with commit A as the head is created
|
||||
// * goroutine continues and runs TestPullRequest
|
||||
maxPR, err := issues_model.GetMaxIssueIndexForRepo(ctx, repoID)
|
||||
if err != nil {
|
||||
log.Error("AddTestPullRequestTask GetMaxIssueIndexForRepo(%d): %v", repoID, err)
|
||||
return
|
||||
}
|
||||
log.Trace("AddTestPullRequestTask [head_repo_id: %d, head_branch: %s]: only pull requests with index <= %d will be considered", repoID, branch, maxPR)
|
||||
go graceful.GetManager().RunWithShutdownContext(func(ctx context.Context) {
|
||||
// There is no sensible way to shut this down ":-("
|
||||
// If you don't let it run all the way then you will lose data
|
||||
// TODO: graceful: AddTestPullRequestTask needs to become a queue!
|
||||
// TODO: graceful: TestPullRequest needs to become a queue!
|
||||
|
||||
// GetUnmergedPullRequestsByHeadInfo() only return open and unmerged PR.
|
||||
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, repoID, branch)
|
||||
if err != nil {
|
||||
log.Error("Find pull requests [head_repo_id: %d, head_branch: %s]: %v", repoID, branch, err)
|
||||
return
|
||||
}
|
||||
TestPullRequest(ctx, doer, repoID, maxPR, branch, isSync, oldCommitID, newCommitID)
|
||||
})
|
||||
}
|
||||
|
||||
for _, pr := range prs {
|
||||
log.Trace("Updating PR[%d]: composing new test task", pr.ID)
|
||||
if pr.Flow == issues_model.PullRequestFlowGithub {
|
||||
if err := PushToBaseRepo(ctx, pr); err != nil {
|
||||
log.Error("PushToBaseRepo: %v", err)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
func TestPullRequest(ctx context.Context, doer *user_model.User, repoID, maxPR int64, branch string, isSync bool, oldCommitID, newCommitID string) {
|
||||
// GetUnmergedPullRequestsByHeadInfo() only return open and unmerged PR.
|
||||
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfoMax(ctx, repoID, maxPR, branch)
|
||||
if err != nil {
|
||||
log.Error("Find pull requests [head_repo_id: %d, head_branch: %s]: %v", repoID, branch, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, pr := range prs {
|
||||
log.Trace("Updating PR[id=%d,index=%d]: composing new test task", pr.ID, pr.Index)
|
||||
if pr.Flow == issues_model.PullRequestFlowGithub {
|
||||
if err := PushToBaseRepo(ctx, pr); err != nil {
|
||||
log.Error("PushToBaseRepo: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
AddToTaskQueue(ctx, pr)
|
||||
comment, err := CreatePushPullComment(ctx, doer, pr, oldCommitID, newCommitID)
|
||||
if err == nil && comment != nil {
|
||||
notify_service.PullRequestPushCommits(ctx, doer, pr, comment)
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
if isSync {
|
||||
requests := issues_model.PullRequestList(prs)
|
||||
if err = requests.LoadAttributes(ctx); err != nil {
|
||||
log.Error("PullRequestList.LoadAttributes: %v", err)
|
||||
}
|
||||
if invalidationErr := checkForInvalidation(ctx, requests, repoID, doer, branch); invalidationErr != nil {
|
||||
log.Error("checkForInvalidation: %v", invalidationErr)
|
||||
}
|
||||
if err == nil {
|
||||
for _, pr := range prs {
|
||||
objectFormat, _ := git.GetObjectFormatOfRepo(ctx, pr.BaseRepo.RepoPath())
|
||||
if newCommitID != "" && newCommitID != objectFormat.EmptyObjectID().String() {
|
||||
changed, err := checkIfPRContentChanged(ctx, pr, oldCommitID, newCommitID)
|
||||
if err != nil {
|
||||
log.Error("checkIfPRContentChanged: %v", err)
|
||||
}
|
||||
if changed {
|
||||
// Mark old reviews as stale if diff to mergebase has changed
|
||||
if err := issues_model.MarkReviewsAsStale(ctx, pr.IssueID); err != nil {
|
||||
log.Error("MarkReviewsAsStale: %v", err)
|
||||
}
|
||||
AddToTaskQueue(ctx, pr)
|
||||
comment, err := CreatePushPullComment(ctx, doer, pr, oldCommitID, newCommitID)
|
||||
if err == nil && comment != nil {
|
||||
notify_service.PullRequestPushCommits(ctx, doer, pr, comment)
|
||||
}
|
||||
}
|
||||
|
||||
// dismiss all approval reviews if protected branch rule item enabled.
|
||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
||||
if err != nil {
|
||||
log.Error("GetFirstMatchProtectedBranchRule: %v", err)
|
||||
}
|
||||
if pb != nil && pb.DismissStaleApprovals {
|
||||
if err := DismissApprovalReviews(ctx, doer, pr); err != nil {
|
||||
log.Error("DismissApprovalReviews: %v", err)
|
||||
}
|
||||
}
|
||||
if isSync {
|
||||
requests := issues_model.PullRequestList(prs)
|
||||
if err = requests.LoadAttributes(ctx); err != nil {
|
||||
log.Error("PullRequestList.LoadAttributes: %v", err)
|
||||
}
|
||||
if invalidationErr := checkForInvalidation(ctx, requests, repoID, doer, branch); invalidationErr != nil {
|
||||
log.Error("checkForInvalidation: %v", invalidationErr)
|
||||
}
|
||||
if err == nil {
|
||||
for _, pr := range prs {
|
||||
objectFormat, _ := git.GetObjectFormatOfRepo(ctx, pr.BaseRepo.RepoPath())
|
||||
if newCommitID != "" && newCommitID != objectFormat.EmptyObjectID().String() {
|
||||
changed, err := checkIfPRContentChanged(ctx, pr, oldCommitID, newCommitID)
|
||||
if err != nil {
|
||||
log.Error("checkIfPRContentChanged: %v", err)
|
||||
}
|
||||
if changed {
|
||||
// Mark old reviews as stale if diff to mergebase has changed
|
||||
if err := issues_model.MarkReviewsAsStale(ctx, pr.IssueID); err != nil {
|
||||
log.Error("MarkReviewsAsStale: %v", err)
|
||||
}
|
||||
if err := issues_model.MarkReviewsAsNotStale(ctx, pr.IssueID, newCommitID); err != nil {
|
||||
log.Error("MarkReviewsAsNotStale: %v", err)
|
||||
}
|
||||
divergence, err := GetDiverging(ctx, pr)
|
||||
|
||||
// dismiss all approval reviews if protected branch rule item enabled.
|
||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
||||
if err != nil {
|
||||
log.Error("GetDiverging: %v", err)
|
||||
} else {
|
||||
err = pr.UpdateCommitDivergence(ctx, divergence.Ahead, divergence.Behind)
|
||||
if err != nil {
|
||||
log.Error("UpdateCommitDivergence: %v", err)
|
||||
log.Error("GetFirstMatchProtectedBranchRule: %v", err)
|
||||
}
|
||||
if pb != nil && pb.DismissStaleApprovals {
|
||||
if err := DismissApprovalReviews(ctx, doer, pr); err != nil {
|
||||
log.Error("DismissApprovalReviews: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notify_service.PullRequestSynchronized(ctx, doer, pr)
|
||||
if err := issues_model.MarkReviewsAsNotStale(ctx, pr.IssueID, newCommitID); err != nil {
|
||||
log.Error("MarkReviewsAsNotStale: %v", err)
|
||||
}
|
||||
divergence, err := GetDiverging(ctx, pr)
|
||||
if err != nil {
|
||||
log.Error("GetDiverging: %v", err)
|
||||
} else {
|
||||
err = pr.UpdateCommitDivergence(ctx, divergence.Ahead, divergence.Behind)
|
||||
if err != nil {
|
||||
log.Error("UpdateCommitDivergence: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notify_service.PullRequestSynchronized(ctx, doer, pr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Trace("AddTestPullRequestTask [base_repo_id: %d, base_branch: %s]: finding pull requests", repoID, branch)
|
||||
prs, err = issues_model.GetUnmergedPullRequestsByBaseInfo(ctx, repoID, branch)
|
||||
log.Trace("TestPullRequest [base_repo_id: %d, base_branch: %s]: finding pull requests", repoID, branch)
|
||||
prs, err = issues_model.GetUnmergedPullRequestsByBaseInfo(ctx, repoID, branch)
|
||||
if err != nil {
|
||||
log.Error("Find pull requests [base_repo_id: %d, base_branch: %s]: %v", repoID, branch, err)
|
||||
return
|
||||
}
|
||||
for _, pr := range prs {
|
||||
divergence, err := GetDiverging(ctx, pr)
|
||||
if err != nil {
|
||||
log.Error("Find pull requests [base_repo_id: %d, base_branch: %s]: %v", repoID, branch, err)
|
||||
return
|
||||
}
|
||||
for _, pr := range prs {
|
||||
divergence, err := GetDiverging(ctx, pr)
|
||||
if err != nil {
|
||||
if git_model.IsErrBranchNotExist(err) && !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) {
|
||||
log.Warn("Cannot test PR %s/%d: head_branch %s no longer exists", pr.BaseRepo.Name, pr.IssueID, pr.HeadBranch)
|
||||
} else {
|
||||
log.Error("GetDiverging: %v", err)
|
||||
}
|
||||
if git_model.IsErrBranchNotExist(err) && !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) {
|
||||
log.Warn("Cannot test PR %s/%d: head_branch %s no longer exists", pr.BaseRepo.Name, pr.IssueID, pr.HeadBranch)
|
||||
} else {
|
||||
err = pr.UpdateCommitDivergence(ctx, divergence.Ahead, divergence.Behind)
|
||||
if err != nil {
|
||||
log.Error("UpdateCommitDivergence: %v", err)
|
||||
}
|
||||
log.Error("GetDiverging: %v", err)
|
||||
}
|
||||
} else {
|
||||
err = pr.UpdateCommitDivergence(ctx, divergence.Ahead, divergence.Behind)
|
||||
if err != nil {
|
||||
log.Error("UpdateCommitDivergence: %v", err)
|
||||
}
|
||||
AddToTaskQueue(ctx, pr)
|
||||
}
|
||||
})
|
||||
AddToTaskQueue(ctx, pr)
|
||||
}
|
||||
}
|
||||
|
||||
// checkIfPRContentChanged checks if diff to target branch has changed by push
|
||||
|
|
|
@ -36,7 +36,7 @@ func Update(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.
|
|||
|
||||
if rebase {
|
||||
defer func() {
|
||||
go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false, "", "")
|
||||
AddTestPullRequestTask(ctx, doer, pr.BaseRepo.ID, pr.BaseBranch, false, "", "")
|
||||
}()
|
||||
|
||||
return updateHeadByRebaseOnToBase(ctx, pr, doer, message)
|
||||
|
@ -75,7 +75,7 @@ func Update(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.
|
|||
_, err = doMergeAndPush(ctx, reversePR, doer, repo_model.MergeStyleMerge, "", message)
|
||||
|
||||
defer func() {
|
||||
go AddTestPullRequestTask(doer, reversePR.HeadRepo.ID, reversePR.HeadBranch, false, "", "")
|
||||
AddTestPullRequestTask(ctx, doer, reversePR.HeadRepo.ID, reversePR.HeadBranch, false, "", "")
|
||||
}()
|
||||
|
||||
return err
|
||||
|
|
|
@ -171,7 +171,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
|
|||
branch := opts.RefFullName.BranchName()
|
||||
if !opts.IsDelRef() {
|
||||
log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name)
|
||||
go pull_service.AddTestPullRequestTask(pusher, repo.ID, branch, true, opts.OldCommitID, opts.NewCommitID)
|
||||
pull_service.AddTestPullRequestTask(ctx, pusher, repo.ID, branch, true, opts.OldCommitID, opts.NewCommitID)
|
||||
|
||||
newCommit, err := gitRepo.GetCommit(opts.NewCommitID)
|
||||
if err != nil {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue