/// <summary> /// Checks if a given <paramref name="pullRequest"/> is considered mergeable and does so if need be and sets it's commit status /// </summary> /// <param name="pullRequest">The <see cref="PullRequest"/> to check</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param> /// <returns>A <see cref="Task"/> representing the running operation</returns> async Task CheckMergePullRequest(PullRequest pullRequest, CancellationToken cancellationToken) { using (logger.BeginScope("Checking mergability of pull request #{0}.", pullRequest.Number)) { bool merge = true; string mergerToken = null; int rescheduleIn = 0; Task pendingStatusTask = null; try { pendingStatusTask = gitHubManager.SetCommitStatus(pullRequest, CommitState.Pending, stringLocalizer["CommitStatusPending"]); for (var I = 0; I < 4 && !pullRequest.Mergeable.HasValue; ++I) { await Task.Delay(I * 1000, cancellationToken).ConfigureAwait(false); logger.LogTrace("Rechecking git mergeablility."); pullRequest = await gitHubManager.GetPullRequest(pullRequest.Number).ConfigureAwait(false); } if (!pullRequest.Mergeable.HasValue || !pullRequest.Mergeable.Value) { logger.LogDebug("Aborted due to lack of mergeablility: {0}", pullRequest.Mergeable); return; } var tasks = new List <Task <AutoMergeStatus> >(); foreach (var I in componentProvider.MergeRequirements) { tasks.Add(I.EvaluateFor(pullRequest, cancellationToken)); } await Task.WhenAll(tasks).ConfigureAwait(false); bool goodStatus = true; var failReasons = new List <string>(); foreach (var I in tasks.Select(x => x.Result)) { if (I.Progress < I.RequiredProgress && merge) { logger.LogDebug("Aborting merge due to status failure: {0}/{1}", I.Progress, I.RequiredProgress); merge = false; if (I.FailStatusReport) { goodStatus = false; failReasons.AddRange(I.Notes); } } if (I.ReevaluateIn > 0) { if (rescheduleIn == 0) { rescheduleIn = I.ReevaluateIn; } else { rescheduleIn = Math.Min(rescheduleIn, I.ReevaluateIn); } } if (I.MergerAccessToken != null) { if (mergerToken != null) { throw new InvalidOperationException("Multiple AutoMergeResults with MergerAccessTokens!"); } mergerToken = I.MergerAccessToken; } } var failReasonMessage = String.Empty; foreach (var I in failReasons) { failReasonMessage = String.Format(CultureInfo.InvariantCulture, "{0}{1} - {2}", failReasonMessage, Environment.NewLine, I); } await pendingStatusTask.ConfigureAwait(false); await gitHubManager.SetCommitStatus(pullRequest, goodStatus?CommitState.Success : CommitState.Failure, goodStatus?stringLocalizer["CommitStatusSuccess"] : stringLocalizer["CommitStatusFail", failReasonMessage]).ConfigureAwait(false); } catch (Exception e) { if (pendingStatusTask.Exception != null && pendingStatusTask.Exception != e) { logger.LogError(e, "Error setting pending status!"); } logger.LogDebug(e, "Error occurred. Setting commit state to errored."); try { await gitHubManager.SetCommitStatus(pullRequest, CommitState.Error, stringLocalizer["CommitStatusError", e]).ConfigureAwait(false); } catch (Exception e2) { logger.LogError(e2, "Unable to create error status!"); } throw; } if (merge) { if (mergerToken == null) { logger.LogWarning("Not merging due to lack of provided merger token!"); } else { await MergePullRequest(pullRequest, mergerToken, cancellationToken).ConfigureAwait(false); return; } } if (rescheduleIn > 0) { var targetTime = DateTimeOffset.UtcNow.AddSeconds(rescheduleIn); BackgroundJob.Schedule(() => RecheckPullRequest(pullRequest.Number, JobCancellationToken.Null), targetTime); logger.LogDebug("Pull request recheck scheduled for {0}.", targetTime); } else { logger.LogTrace("Not rescheduling pull request check."); } } }