Пример #1
0
        private async Task <ActionResult <bool?> > SynchronizePullRequestAsync(string prUrl)
        {
            ConditionalValue <InProgressPullRequest> maybePr =
                await StateManager.TryGetStateAsync <InProgressPullRequest>(PullRequest);

            if (!maybePr.HasValue || maybePr.Value.Url != prUrl)
            {
                return(ActionResult.Create(
                           (bool?)null,
                           $"Not Applicable: Pull Request '{prUrl}' is not tracked by maestro anymore."));
            }

            (string targetRepository, string targetBranch) = await GetTargetAsync();

            IRemote darc = await DarcRemoteFactory.GetRemoteAsync(targetRepository, Logger);

            InProgressPullRequest pr = maybePr.Value;
            PrStatus status          = await darc.GetPullRequestStatusAsync(prUrl);

            ActionResult <bool?> checkPolicyResult = null;

            switch (status)
            {
            case PrStatus.Open:
                checkPolicyResult = await CheckMergePolicyAsync(prUrl, darc);

                if (checkPolicyResult.Result == true)
                {
                    goto case PrStatus.Merged;
                }

                if (checkPolicyResult.Result == false)
                {
                    return(ActionResult.Create((bool?)true, checkPolicyResult.Message));
                }

                return(ActionResult.Create((bool?)false, checkPolicyResult.Message));

            case PrStatus.Merged:
                await UpdateSubscriptionsForMergedPRAsync(pr.ContainedSubscriptions);

                goto case PrStatus.Closed;

            case PrStatus.Closed:
                await StateManager.RemoveStateAsync(PullRequest);

                break;

            default:
                Logger.LogError("Unknown pr status '{status}'", status);
                break;
            }

            if (checkPolicyResult != null)
            {
                return(ActionResult.Create((bool?)null, checkPolicyResult.Message));
            }

            return(ActionResult.Create((bool?)null, $"PR Has been manually {status}"));
        }
Пример #2
0
        /// <summary>
        ///     Synchronizes an in progress pull request.
        ///     This will update current state if the pull request has been manually closed or merged.
        ///     This will evaluate merge policies on an in progress pull request and merge the pull request if policies allow.
        /// </summary>
        /// <returns>
        ///     A <see cref="ValueTuple{InProgressPullRequest, bool}" /> containing:
        ///     The current open pull request if one exists, and
        ///     <see langword="true" /> if the open pull request can be updated; <see langword="false" /> otherwise.
        /// </returns>
        public virtual async Task <(InProgressPullRequest pr, bool canUpdate)> SynchronizeInProgressPullRequestAsync()
        {
            ConditionalValue <InProgressPullRequest> maybePr =
                await StateManager.TryGetStateAsync <InProgressPullRequest>(PullRequest);

            if (maybePr.HasValue)
            {
                InProgressPullRequest pr = maybePr.Value;
                if (string.IsNullOrEmpty(pr.Url))
                {
                    // somehow a bad PR got in the collection, remove it
                    await StateManager.RemoveStateAsync(PullRequest);

                    return(null, false);
                }

                bool?result = await ActionRunner.ExecuteAction(() => SynchronizePullRequestAsync(pr.Url));

                if (result == true)
                {
                    return(pr, true);
                }

                if (result == false)
                {
                    return(pr, false);
                }
            }

            await Reminders.TryUnregisterReminderAsync(PullRequestCheck);

            return(null, false);
        }
Пример #3
0
        public async Task SubscriptionDeletedAsync(string user)
        {
            ConditionalValue <InProgressPullRequest> maybePr =
                await StateManager.TryGetStateAsync <InProgressPullRequest>(PullRequest);

            if (maybePr.HasValue)
            {
                InProgressPullRequest pr = maybePr.Value;
                if (string.IsNullOrEmpty(pr.Url))
                {
                    // somehow a bad PR got in the collection, remove it
                    await StateManager.RemoveStateAsync(PullRequest);

                    return;
                }

                long installationId = 0;
                if (pr.Url.Contains("github.com"))
                {
                    (string owner, string repo, int id) = GitHubClient.ParsePullRequestUri(pr.Url);
                    installationId = await Context.GetInstallationId($"https://github.com/{owner}/{repo}");
                }

                IRemote darc = await DarcFactory.CreateAsync(pr.Url, installationId);

                await darc.CreatePullRequestCommentAsync(
                    pr.Url,
                    $@"The subscription that generated this pull request has been deleted by @{user}.
This pull request will no longer be tracked by maestro.");

                await StateManager.RemoveStateAsync(PullRequest);
            }
        }
Пример #4
0
        private async Task UpdateStatusCommentAsync(IRemote darc, InProgressPullRequest pr, string message)
        {
            if (pr.StatusCommentId == null)
            {
                pr.StatusCommentId = await darc.CreatePullRequestCommentAsync(pr.Url, message);

                await StateManager.SetStateAsync(PullRequest, pr);
            }
            else
            {
                await darc.UpdatePullRequestCommentAsync(pr.Url, pr.StatusCommentId, message);
            }
        }
Пример #5
0
        public async Task UpdateAsync(int buildId)
        {
            await SynchronizeInProgressPRAsync();

            Subscription subscription = await Context.Subscriptions.FindAsync(SubscriptionId);

            Build build = await Context.Builds.Include(b => b.Assets)
                          .ThenInclude(a => a.Locations)
                          .FirstAsync(b => b.Id == buildId);

            string targetRepository = subscription.TargetRepository;
            string targetBranch     = subscription.TargetBranch;
            long   installationId   = await Context.GetInstallationId(subscription.TargetRepository);

            IRemote darc = await DarcFactory.CreateAsync(targetRepository, installationId);

            List <AssetData> assets = build.Assets.Select(a => new AssetData {
                Name = a.Name, Version = a.Version
            })
                                      .ToList();

            ConditionalValue <InProgressPullRequest> maybePr =
                await StateManager.TryGetStateAsync <InProgressPullRequest>(PullRequest);

            string prUrl;

            if (maybePr.HasValue)
            {
                InProgressPullRequest pr = maybePr.Value;
                await darc.UpdatePullRequestAsync(pr.Url, build.Commit, targetBranch, assets);

                prUrl = pr.Url;
            }
            else
            {
                prUrl = await darc.CreatePullRequestAsync(targetRepository, targetBranch, build.Commit, assets);
            }

            var newPr = new InProgressPullRequest {
                Url = prUrl, BuildId = build.Id
            };
            await StateManager.SetStateAsync(PullRequest, newPr);

            await Reminders.TryRegisterReminderAsync(
                PullRequestCheck,
                Array.Empty <byte>(),
                new TimeSpan(0, 5, 0),
                new TimeSpan(0, 5, 0));

            await StateManager.SaveStateAsync();
        }
Пример #6
0
        private async Task <string> CheckMergePolicyAsyncImpl(string prUrl)
        {
            Subscription subscription = await Context.Subscriptions.FindAsync(SubscriptionId);

            if (subscription == null)
            {
                await Reminders.TryUnregisterReminderAsync(PullRequestCheck);

                await StateManager.TryRemoveStateAsync(PullRequest);

                return("Action Ignored: Subscription does not exist.");
            }

            ConditionalValue <InProgressPullRequest> maybePr =
                await StateManager.TryGetStateAsync <InProgressPullRequest>(PullRequest);

            if (!maybePr.HasValue)
            {
                return("Action Ignored: Pull Request not found.");
            }

            InProgressPullRequest pr = maybePr.Value;
            long installationId      = await Context.GetInstallationId(subscription.TargetRepository);

            IRemote darc = await DarcFactory.CreateAsync(pr.Url, installationId);

            SubscriptionPolicy policy = subscription.PolicyObject;
            PrStatus           status = await darc.GetPullRequestStatusAsync(pr.Url);

            switch (status)
            {
            case PrStatus.Open:
                string result = await CheckMergePolicyInternalAsync(darc, policy.MergePolicies, pr);

                if (result.StartsWith("Merged:"))
                {
                    subscription.LastAppliedBuildId = pr.BuildId;
                    await Context.SaveChangesAsync();

                    await StateManager.RemoveStateAsync(PullRequest);

                    return(result);
                }

                return(result);

            default:
                return("Action Ignored: Pull Request is not Open.");
            }
        }
Пример #7
0
        /// <summary>
        ///     Compute the title for a pull request.
        /// </summary>
        /// <param name="inProgressPr">Current in progress pull request information</param>
        /// <returns>Pull request title</returns>
        private async Task <string> ComputePullRequestTitleAsync(InProgressPullRequest inProgressPr, string targetBranch)
        {
            // Get the unique subscription IDs. It may be possible for a coherency update
            // to not have any contained subscription.  In this case
            // we return a different title.
            var uniqueSubscriptionIds = inProgressPr.ContainedSubscriptions.Select(
                subscription => subscription.SubscriptionId).ToHashSet();

            if (uniqueSubscriptionIds.Count > 0)
            {
                // We'll either list out the repos involved (in a shortened form)
                // or we'll list out the number of repos that are involved.
                // Start building up the list. If we reach a max length, then backtrack and
                // just note the number of input subscriptions.
                string        baseTitle      = $"[{targetBranch}] Update dependencies from ";
                StringBuilder titleBuilder   = new StringBuilder(baseTitle);
                bool          prefixComma    = false;
                const int     maxTitleLength = 80;
                foreach (Guid subscriptionId in uniqueSubscriptionIds)
                {
                    string repoName = await GetSourceRepositoryAsync(subscriptionId);

                    // Strip down repo name.
                    repoName = repoName?.Replace("https://github.com/", "");
                    repoName = repoName?.Replace("https://dev.azure.com/", "");
                    repoName = repoName?.Replace("_git/", "");
                    string repoNameForTitle = prefixComma ? $", {repoName}" : repoName;

                    if (titleBuilder.Length + repoNameForTitle?.Length > maxTitleLength)
                    {
                        return($"{baseTitle} {uniqueSubscriptionIds.Count} repositories");
                    }
                    else
                    {
                        titleBuilder.Append(repoNameForTitle);
                    }
                }

                return(titleBuilder.ToString());
            }
            else
            {
                return($"[{targetBranch}] Update dependencies to ensure coherency");
            }
        }
        public async Task TagSourceRepositoryGitHubContactsAsync(InProgressPullRequest pr)
        {
            // We'll try to notify the source repo if the subscription provided a list of aliases to tag.
            // The API checks when creating / updating subscriptions that any resolve-able logins are in the
            // "Microsoft" Github org, so we can safely use them in any comment.
            if (pr.SourceRepoNotified == true)
            {
                Logger.LogInformation($"Skipped notifying source repository for {pr.Url}'s failed policies, as it has already been tagged");
                return;
            }

            var subscriptionFromPr = pr.ContainedSubscriptions.FirstOrDefault();

            if (subscriptionFromPr == null)
            {
                Logger.LogWarning("Unable to get any contained subscriptions from this PR for notification; skipping attempts to notify.");
                pr.SourceRepoNotified = true;
                return;
            }

            // In practice these all contain the same subscription id, the property is more like "containedBuildsAndTheirSubscriptions"
            Logger.LogInformation($"PR contains {pr.ContainedSubscriptions.Count} builds.  Using first ({subscriptionFromPr.SubscriptionId}) for notification tagging.");

            (string owner, string repo, int prIssueId) = GitHubClient.ParsePullRequestUri(pr.Url);
            if (owner == null || repo == null || prIssueId == 0)
            {
                Logger.LogInformation($"Unable to parse pull request URI '{pr.Url}' (typically due to Azure DevOps pull requests), will not notify on this PR.");
                pr.SourceRepoNotified = true;
                return;
            }

            var darcRemote = await DarcRemoteFactory.GetRemoteAsync($"https://github.com/{owner}/{repo}", Logger);

            var darcSubscriptionObject = await darcRemote.GetSubscriptionAsync(subscriptionFromPr.SubscriptionId.ToString());

            string sourceRepository = darcSubscriptionObject.SourceRepository;
            string targetRepository = darcSubscriptionObject.TargetRepository;

            // If we're here, there are failing checks, but if the only checks that failed were Maestro Merge Policy checks, we'll skip informing until something else fails too.
            var prChecks = await darcRemote.GetPullRequestChecksAsync(pr.Url);

            var failedPrChecks = prChecks.Where(p => !p.IsMaestroMergePolicy && (p.Status == CheckState.Failure || p.Status == CheckState.Error)).AsEnumerable();

            if (failedPrChecks.Count() == 0)
            {
                Logger.LogInformation($"All failing or error state checks are 'Maestro Merge Policy'-type checks, not notifying subscribed users.");
                return;
            }

            List <string> tagsToNotify = new List <string>();

            if (!string.IsNullOrEmpty(darcSubscriptionObject.PullRequestFailureNotificationTags))
            {
                tagsToNotify.AddRange(darcSubscriptionObject.PullRequestFailureNotificationTags.Split(';', StringSplitOptions.RemoveEmptyEntries));
            }

            if (tagsToNotify.Count == 0)
            {
                Logger.LogInformation("Found no matching tags for source '{sourceRepo}' to target '{targetRepo}' on channel '{channel}'. ", sourceRepository, targetRepository, darcSubscriptionObject.Channel);
                return;
            }

            // At this point we definitely have notifications to make, so do it.
            Logger.LogInformation("Found {count} matching tags for source '{sourceRepo}' to target '{targetRepo}' on channel '{channel}'. ", tagsToNotify.Count, sourceRepository, targetRepository, darcSubscriptionObject.Channel);

            // To ensure GitHub notifies the people / teams on the list, forcibly check they are inserted with a preceding '@'
            for (int i = 0; i < tagsToNotify.Count; i++)
            {
                if (!tagsToNotify[i].StartsWith('@'))
                {
                    tagsToNotify[i] = $"@{tagsToNotify[i]}";
                }
            }

            string githubToken = await GitHubTokenProvider.GetTokenForRepository(targetRepository);

            var gitHubClient = GitHubClientFactory.CreateGitHubClient(githubToken);

            string sourceRepoNotificationComment = @$ "
#### Notification for subscribed users from {sourceRepository}:

{string.Join($", { Environment.NewLine } ", tagsToNotify)}

#### Action requested: Please take a look at this failing automated dependency-flow pull request's checks; failures may be related to changes which originated in your repo.

- This pull request contains changes from your source repo ({sourceRepository}) and seems to have failed checks in this PR.  Please take a peek at the failures and comment if they seem relevant to your changes.
Пример #9
0
        /// <summary>
        ///     Creates a pull request from the given updates.
        /// </summary>
        /// <param name="updates"></param>
        /// <returns>The pull request url when a pr was created; <see langref="null" /> if no PR is necessary</returns>
        private async Task <string> CreatePullRequestAsync(List <UpdateAssetsParameters> updates)
        {
            (string targetRepository, string targetBranch) = await GetTargetAsync();

            IRemote darc = await GetDarc();


            List <(UpdateAssetsParameters update, List <DependencyDetail> deps)> requiredUpdates =
                await GetRequiredUpdates(updates, darc, targetRepository, targetBranch);

            if (requiredUpdates.Count < 1)
            {
                return(null);
            }

            string newBranchName = $"darc-{targetBranch}-{Guid.NewGuid()}";

            await darc.CreateNewBranchAsync(targetRepository, targetBranch, newBranchName);

            using (var description = new StringWriter())
            {
                description.WriteLine("This pull request updates the following dependencies");
                description.WriteLine();

                await CommitUpdatesAsync(requiredUpdates, description, darc, targetRepository, newBranchName);

                string prUrl = await darc.CreatePullRequestAsync(
                    targetRepository,
                    new PullRequest
                {
                    Title       = "Update dependency files",
                    Description = description.ToString(),
                    BaseBranch  = targetBranch,
                    HeadBranch  = newBranchName
                });

                var inProgressPr = new InProgressPullRequest
                {
                    Url = prUrl,
                    ContainedSubscriptions = requiredUpdates.Select(
                        u => new SubscriptionPullRequestUpdate
                    {
                        SubscriptionId = u.update.SubscriptionId,
                        BuildId        = u.update.BuildId
                    })
                                             .ToList()
                };

                await StateManager.SetStateAsync(PullRequest, inProgressPr);

                await StateManager.SaveStateAsync();

                await Reminders.TryRegisterReminderAsync(
                    PullRequestCheck,
                    null,
                    TimeSpan.FromMinutes(5),
                    TimeSpan.FromMinutes(5));

                return(prUrl);
            }
        }
Пример #10
0
        /// <summary>
        ///     Creates a pull request from the given updates.
        /// </summary>
        /// <param name="updates"></param>
        /// <returns>The pull request url when a pr was created; <see langref="null" /> if no PR is necessary</returns>
        private async Task <string> CreatePullRequestAsync(List <UpdateAssetsParameters> updates)
        {
            (string targetRepository, string targetBranch) = await GetTargetAsync();

            IRemote darcRemote = await DarcRemoteFactory.GetRemoteAsync(targetRepository, Logger);

            List <(UpdateAssetsParameters update, List <DependencyDetail> deps)> requiredUpdates =
                await GetRequiredUpdates(updates, DarcRemoteFactory, targetRepository, targetBranch);

            if (requiredUpdates.Count < 1)
            {
                return(null);
            }

            string newBranchName = $"darc-{targetBranch}-{Guid.NewGuid()}";
            await darcRemote.CreateNewBranchAsync(targetRepository, targetBranch, newBranchName);

            try
            {
                using (var description = new StringWriter())
                {
                    description.WriteLine("This pull request updates the following dependencies");
                    description.WriteLine();

                    await CommitUpdatesAsync(requiredUpdates, description, darcRemote, targetRepository, newBranchName);

                    var inProgressPr = new InProgressPullRequest
                    {
                        // Calculate the subscriptions contained within the
                        // update. Coherency updates do not have subscription info.
                        ContainedSubscriptions = requiredUpdates
                                                 .Where(u => !u.update.IsCoherencyUpdate)
                                                 .Select(
                            u => new SubscriptionPullRequestUpdate
                        {
                            SubscriptionId = u.update.SubscriptionId,
                            BuildId        = u.update.BuildId
                        })
                                                 .ToList()
                    };

                    string prUrl = await darcRemote.CreatePullRequestAsync(
                        targetRepository,
                        new PullRequest
                    {
                        Title       = await ComputePullRequestTitleAsync(inProgressPr, targetBranch),
                        Description = description.ToString(),
                        BaseBranch  = targetBranch,
                        HeadBranch  = newBranchName
                    });

                    if (!string.IsNullOrEmpty(prUrl))
                    {
                        inProgressPr.Url = prUrl;

                        await StateManager.SetStateAsync(PullRequest, inProgressPr);

                        await StateManager.SaveStateAsync();

                        await Reminders.TryRegisterReminderAsync(
                            PullRequestCheck,
                            null,
                            TimeSpan.FromMinutes(5),
                            TimeSpan.FromMinutes(5));

                        return(prUrl);
                    }

                    // Something wrong happened when trying to create the PR but didn't throw an exception (probably there was no diff).
                    // We need to delete the branch also in this case.
                    await darcRemote.DeleteBranchAsync(targetRepository, newBranchName);

                    return(null);
                }
            }
            catch
            {
                await darcRemote.DeleteBranchAsync(targetRepository, newBranchName);

                throw;
            }
        }
Пример #11
0
        private async Task <string> CheckMergePolicyInternalAsync(
            IRemote darc,
            IList <MergePolicyDefinition> policyDefinitions,
            InProgressPullRequest pr)
        {
            var results = new List <(MergePolicy policy, MergePolicyEvaluationResult result)>();

            if (policyDefinitions != null)
            {
                foreach (MergePolicyDefinition policyDefinition in policyDefinitions)
                {
                    var context = new MergePolicyEvaluationContext(pr.Url, darc, Logger, policyDefinition.Properties);
                    if (PolicyEvaluators.TryGetValue(policyDefinition.Name, out MergePolicy policyEvaluator))
                    {
                        MergePolicyEvaluationResult result = await policyEvaluator.EvaluateAsync(context);

                        results.Add((policyEvaluator, result));
                    }
                    else
                    {
                        results.Add((null, context.Fail($"Unknown Merge Policy '{policyDefinition.Name}'")));
                    }
                }
            }

            if (results.Count == 0)
            {
                await UpdateStatusCommentAsync(
                    darc,
                    pr,
                    $@"## Auto-Merge Status
This pull request will not be merged automatically because the subscription with id '{SubscriptionId}' does not have any merge policies.");

                return(NotMergedNoPolicies());
            }

            string DisplayPolicy((MergePolicy policy, MergePolicyEvaluationResult result) p)
            {
                (MergePolicy policy, MergePolicyEvaluationResult result) = p;
                if (policy == null)
                {
                    return($"- ❌ **{result.Message}**");
                }

                if (result.Succeeded)
                {
                    return($"- ✔️ **{policy.DisplayName}** Succeeded");
                }

                return($"- ❌ **{policy.DisplayName}** {result.Message}");
            }

            if (results.Any(r => !r.result.Succeeded))
            {
                await UpdateStatusCommentAsync(
                    darc,
                    pr,
                    $@"## Auto-Merge Status
This pull request has not been merged because the subscription with id '{SubscriptionId}' is waiting on the following merge policies.

{string.Join("\n", results.OrderBy(r => r.policy == null ? " " : r.policy.Name).Select(DisplayPolicy))}");

                return(NotMergedFailedPolicies(results.Where(r => !r.result.Succeeded), pr.Url));
            }

            await UpdateStatusCommentAsync(
                darc,
                pr,
                $@"## Auto-Merge Status
This pull request has been merged because the following merge policies have succeeded.

{string.Join("\n", results.OrderBy(r => r.policy == null ? " " : r.policy.Name).Select(DisplayPolicy))}");

            await darc.MergePullRequestAsync(pr.Url, null);

            return(Merged(policyDefinitions, pr.Url));
        }
Пример #12
0
        private async Task <string> UpdateAsyncImpl(int buildId)
        {
            await SynchronizeInProgressPRAsync();

            Subscription subscription = await Context.Subscriptions.FindAsync(SubscriptionId);

            Build build = await Context.Builds.Include(b => b.Assets)
                          .ThenInclude(a => a.Locations)
                          .FirstAsync(b => b.Id == buildId);

            string targetRepository = subscription.TargetRepository;
            string targetBranch     = subscription.TargetBranch;
            long   installationId   = await Context.GetInstallationId(subscription.TargetRepository);

            IRemote darc = await DarcFactory.CreateAsync(targetRepository, installationId);

            List <AssetData> assets = build.Assets.Select(a => new AssetData {
                Name = a.Name, Version = a.Version
            })
                                      .ToList();
            string title       = GetTitle(subscription, build);
            string description = GetDescription(subscription, build, assets);

            ConditionalValue <InProgressPullRequest> maybePr =
                await StateManager.TryGetStateAsync <InProgressPullRequest>(PullRequest);

            InProgressPullRequest pr;

            if (maybePr.HasValue)
            {
                pr = maybePr.Value;
                await darc.UpdatePullRequestAsync(pr.Url, build.Commit, targetBranch, assets, title, description);
            }
            else
            {
                string prUrl = await darc.CreatePullRequestAsync(
                    targetRepository,
                    targetBranch,
                    build.Commit,
                    assets,
                    pullRequestTitle : title,
                    pullRequestDescription : description);

                if (string.IsNullOrEmpty(prUrl))
                {
                    return($"No Pull request created. Darc Reports no dependencies need to be updated.");
                }

                pr = new InProgressPullRequest {
                    Url = prUrl
                };
            }

            pr.BuildId = build.Id;
            await StateManager.SetStateAsync(PullRequest, pr);

            await Reminders.TryRegisterReminderAsync(
                PullRequestCheck,
                Array.Empty <byte>(),
                new TimeSpan(0, 5, 0),
                new TimeSpan(0, 5, 0));

            await StateManager.SaveStateAsync();

            return($"Pull request '{pr.Url}' updated.");
        }
Пример #13
0
        public async Task SynchronizeInProgressPRAsync()
        {
            Subscription subscription = await Context.Subscriptions.FindAsync(SubscriptionId);

            if (subscription == null)
            {
                await Reminders.TryUnregisterReminderAsync(PullRequestCheck);

                await StateManager.TryRemoveStateAsync(PullRequest);

                return;
            }

            ConditionalValue <InProgressPullRequest> maybePr =
                await StateManager.TryGetStateAsync <InProgressPullRequest>(PullRequest);

            if (maybePr.HasValue)
            {
                InProgressPullRequest pr = maybePr.Value;
                long installationId      = await Context.GetInstallationId(subscription.TargetRepository);

                IRemote darc = await DarcFactory.CreateAsync(pr.Url, installationId);

                MergePolicy policy = subscription.PolicyObject.MergePolicy;
                PrStatus    status = await darc.GetPullRequestStatusAsync(pr.Url);

                switch (status)
                {
                case PrStatus.Open:
                    switch (policy)
                    {
                    case MergePolicy.Never:
                        return;

                    case MergePolicy.BuildSucceeded:
                    case MergePolicy.UnitTestPassed:         // for now both of these cases are the same
                        if (await ShouldMergePrAsync(darc, pr.Url, policy))
                        {
                            await darc.MergePullRequestAsync(pr.Url);

                            goto merged;
                        }

                        return;

                    default:
                        Logger.LogError("Unknown merge policy '{policy}'", policy);
                        return;
                    }

                case PrStatus.Merged:
merged:
                    subscription.LastAppliedBuildId = pr.BuildId;
                    await Context.SaveChangesAsync();

                    goto case PrStatus.Closed;

                case PrStatus.Closed:
                    await StateManager.RemoveStateAsync(PullRequest);

                    break;

                default:
                    Logger.LogError("Unknown pr status '{status}'", status);
                    return;
                }
            }

            await Reminders.TryUnregisterReminderAsync(PullRequestCheck);
        }