mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-01 20:59:45 +09:00
fix: PR merge (#39442)
* Revert the behavior introduced by #30805 * Now the PR status is still managed in Gitea's code where the operation is triggerred but not in post-receive hook * Fix #39254 and many more related bugs. * Fix #39124 ``` // MarkAsMerged sets a pull request to merged and closes the corresponding issue // To make sure the pull request is marked as merged correctly, the caller uses multiple-stage operations: // 1. Create a temp repo from base, merge the head into the temp repo, and get the merged commit ID and timestamp, // 2. The merged commit ID and related information are stored into pull request // 3. Push the merged commit to the base repo // 4. Call MarkAsMerged to mark the pull request as merged and do post-processing (notification, close issues, etc) // // If failure occurs in step 1/2/3: the pull request is still open, the base repo is not changed, the doer can start a new merge. // If failure occurs in step 4: the pull request can be marked as merged by the merged commit ID stored in it later. ```
This commit is contained in:
+85
-70
@@ -19,6 +19,7 @@ import (
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unit"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/container"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/git/gitcmd"
|
||||
"gitea.dev/modules/globallock"
|
||||
@@ -30,7 +31,6 @@ import (
|
||||
"gitea.dev/modules/timeutil"
|
||||
asymkey_service "gitea.dev/services/asymkey"
|
||||
"gitea.dev/services/automergequeue"
|
||||
notify_service "gitea.dev/services/notify"
|
||||
)
|
||||
|
||||
// prPatchCheckerQueue represents a queue to handle update pull request tests
|
||||
@@ -268,7 +268,7 @@ func checkSigningRequirements(ctx context.Context, pr *issues_model.PullRequest,
|
||||
|
||||
// markPullRequestAsMergeable checks if pull request is possible to leaving checking status,
|
||||
// and set to be either conflict or mergeable.
|
||||
func markPullRequestAsMergeable(ctx context.Context, pr *issues_model.PullRequest) {
|
||||
func markPullRequestAsMergeable(ctx context.Context, pr *issues_model.PullRequest) error {
|
||||
// If the status has not been changed to conflict by the conflict checking functions then we are mergeable
|
||||
if pr.Status == issues_model.PullRequestStatusChecking {
|
||||
pr.Status = issues_model.PullRequestStatusMergeable
|
||||
@@ -282,22 +282,22 @@ func markPullRequestAsMergeable(ctx context.Context, pr *issues_model.PullReques
|
||||
|
||||
if has {
|
||||
log.Trace("Not updating status for %-v as it is due to be rechecked", pr)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := pr.UpdateColsIfNotMerged(ctx, "merge_base", "status", "conflicted_files", "changed_protected_files"); err != nil {
|
||||
log.Error("Update[%-v]: %v", pr, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// if there is a scheduled merge for this pull request, start the auto merge check (again)
|
||||
exist, _, err := pull.GetScheduledMergeByPullID(ctx, pr.ID)
|
||||
hasScheduledMerge, _, err := pull.GetScheduledMergeByPullID(ctx, pr.ID)
|
||||
if err != nil {
|
||||
log.Error("GetScheduledMergeByPullID[%-v]: %v", pr, err)
|
||||
return
|
||||
} else if !exist {
|
||||
return
|
||||
return err
|
||||
}
|
||||
automergequeue.StartAutoMergeCheckByPullHead(ctx, pr)
|
||||
if hasScheduledMerge {
|
||||
automergequeue.StartAutoMergeCheckByPullHead(ctx, pr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getMergeCommit checks if a pull request has been merged
|
||||
@@ -384,9 +384,9 @@ func getMergerForManuallyMergedPullRequest(ctx context.Context, pr *issues_model
|
||||
return nil, fmt.Errorf("unable to find merger for manually merged pull request: %w", errors.Join(errs...))
|
||||
}
|
||||
|
||||
// manuallyMerged checks if a pull request got manually merged
|
||||
// manuallyMergedByPushedCommit checks if a pull request got manually merged
|
||||
// When a pull request got manually merged mark the pull request as merged
|
||||
func manuallyMerged(ctx context.Context, pr *issues_model.PullRequest) bool {
|
||||
func manuallyMergedByPushedCommit(ctx context.Context, pr *issues_model.PullRequest) bool {
|
||||
if err := pr.LoadBaseRepo(ctx); err != nil {
|
||||
log.Error("%-v LoadBaseRepo: %v", pr, err)
|
||||
return false
|
||||
@@ -419,31 +419,35 @@ func manuallyMerged(ctx context.Context, pr *issues_model.PullRequest) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if merged, err := SetMerged(ctx, pr, commit.ID.String(), timeutil.TimeStamp(commit.Author.When.Unix()), merger, issues_model.PullRequestStatusManuallyMerged); err != nil {
|
||||
log.Error("%-v setMerged : %v", pr, err)
|
||||
return false
|
||||
} else if !merged {
|
||||
return false
|
||||
merged, err := MarkAsMerged(ctx, pr, commit.ID.String(), timeutil.TimeStamp(commit.Author.When.Unix()), merger, issues_model.PullRequestStatusManuallyMerged)
|
||||
if err != nil {
|
||||
log.Error("%-v MarkAsMerged : %v", pr, err)
|
||||
}
|
||||
|
||||
notify_service.MergePullRequest(ctx, merger, pr)
|
||||
|
||||
log.Info("manuallyMerged[%-v]: Marked as manually merged into %s/%s by commit id: %s", pr, pr.BaseRepo.Name, pr.BaseBranch, commit.ID.String())
|
||||
return true
|
||||
return merged
|
||||
}
|
||||
|
||||
// InitializePullRequests checks and tests untested patches of pull requests.
|
||||
func InitializePullRequests(ctx context.Context) {
|
||||
// If we prefer to delay the checks, then no need to do any check during startup, there should be not much difference
|
||||
if setting.Repository.PullRequest.DelayCheckForInactiveDays >= 0 {
|
||||
return
|
||||
}
|
||||
prs, err := issues_model.GetPullRequestIDsByCheckStatus(ctx, issues_model.PullRequestStatusChecking)
|
||||
prIdSet := container.Set[int64]{}
|
||||
|
||||
prs, err := issues_model.GetInterruptedPullRequestIDs(ctx)
|
||||
if err != nil {
|
||||
log.Error("Find Checking PRs: %v", err)
|
||||
return
|
||||
log.Error("Failed to query interrupted PRs: %v", err)
|
||||
} else {
|
||||
prIdSet.AddMultiple(prs...)
|
||||
}
|
||||
for _, prID := range prs {
|
||||
|
||||
// If we prefer to delay the checks, then no need to do PR check during startup, there should be not much difference
|
||||
if setting.Repository.PullRequest.DelayCheckForInactiveDays < 0 {
|
||||
prs, err := issues_model.GetPullRequestIDsByCheckStatus(ctx, issues_model.PullRequestStatusChecking)
|
||||
if err != nil {
|
||||
log.Error("Failed to query PRs that need checking: %v", err)
|
||||
} else {
|
||||
prIdSet.AddMultiple(prs...)
|
||||
}
|
||||
}
|
||||
|
||||
for _, prID := range prIdSet.Values() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
@@ -453,51 +457,62 @@ func InitializePullRequests(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func checkPullRequestMergeable(id int64) {
|
||||
ctx := graceful.GetManager().HammerContext()
|
||||
releaser, err := globallock.Lock(ctx, getPullWorkingLockKey(id))
|
||||
func restoreInterruptedMerge(ctx context.Context, pr *issues_model.PullRequest) (bool, error) {
|
||||
hasCommitBeenMerged, err := hasPullRequestCommitBeenMerged(ctx, pr)
|
||||
if err != nil {
|
||||
log.Error("lock.Lock(): %v", err)
|
||||
return
|
||||
return false, err
|
||||
}
|
||||
defer releaser()
|
||||
|
||||
ctx, _, finished := process.GetManager().AddContext(ctx, fmt.Sprintf("Test PR[%d] from patch checking queue", id))
|
||||
defer finished()
|
||||
|
||||
pr, err := issues_model.GetPullRequestByID(ctx, id)
|
||||
if err != nil {
|
||||
log.Error("Unable to GetPullRequestByID[%d] for checkPullRequestMergeable: %v", id, err)
|
||||
return
|
||||
if !hasCommitBeenMerged {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
log.Trace("Testing %-v", pr)
|
||||
defer func() {
|
||||
log.Trace("Done testing %-v (status: %s)", pr, pr.Status)
|
||||
}()
|
||||
|
||||
if pr.HasMerged {
|
||||
log.Trace("%-v is already merged (status: %s, merge commit: %s)", pr, pr.Status, pr.MergedCommitID)
|
||||
return
|
||||
}
|
||||
|
||||
if manuallyMerged(ctx, pr) {
|
||||
log.Trace("%-v is manually merged (status: %s, merge commit: %s)", pr, pr.Status, pr.MergedCommitID)
|
||||
return
|
||||
}
|
||||
|
||||
if err := checkPullRequestBranchMergeable(ctx, pr); err != nil {
|
||||
log.Error("checkPullRequestBranchMergeable[%-v]: %v", pr, err)
|
||||
pr.Status = issues_model.PullRequestStatusError
|
||||
if err := pr.UpdateCols(ctx, "status"); err != nil {
|
||||
log.Error("update pr [%-v] status to PullRequestStatusError failed: %v", pr, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
markPullRequestAsMergeable(ctx, pr)
|
||||
return MarkAsMerged(ctx, pr, pr.MergedCommitID, pr.MergedUnix, pr.Merger, pr.Status)
|
||||
}
|
||||
|
||||
// CheckPRsForBaseBranch check all pulls with baseBrannch
|
||||
func checkPullRequestMergeable(prID int64) {
|
||||
ctx := graceful.GetManager().HammerContext()
|
||||
|
||||
ctx, _, finished := process.GetManager().AddContext(ctx, fmt.Sprintf("Test PR[%d] from patch checking queue", prID))
|
||||
defer finished()
|
||||
|
||||
err := globallock.LockAndDo(ctx, getPullWorkingLockKey(prID), func(ctx context.Context) error {
|
||||
pr, err := issues_model.GetPullRequestByID(ctx, prID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if pr.HasMerged {
|
||||
log.Trace("%-v is already merged (status: %s, merge commit: %s)", pr, pr.Status, pr.MergedCommitID)
|
||||
return nil
|
||||
}
|
||||
|
||||
if ok, err := restoreInterruptedMerge(ctx, pr); err != nil {
|
||||
return err
|
||||
} else if ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if manuallyMergedByPushedCommit(ctx, pr) {
|
||||
log.Trace("%-v is manually merged (status: %s, merge commit: %s)", pr, pr.Status, pr.MergedCommitID)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := checkPullRequestBranchMergeable(ctx, pr); err != nil {
|
||||
log.Error("checkPullRequestBranchMergeable[%-v]: %v", pr, err)
|
||||
pr.Status = issues_model.PullRequestStatusError
|
||||
if err := pr.UpdateCols(ctx, "status"); err != nil {
|
||||
log.Error("update pr [%-v] status to PullRequestStatusError failed: %v", pr, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return markPullRequestAsMergeable(ctx, pr)
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("Unable to checkPullRequestMergeable[%d]: %v", prID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// CheckPRsForBaseBranch check all pulls with base branch
|
||||
func CheckPRsForBaseBranch(ctx context.Context, baseRepo *repo_model.Repository, baseBranchName string) error {
|
||||
prs, err := issues_model.GetUnmergedPullRequestsByBaseInfo(ctx, baseRepo.ID, baseBranchName)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user