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}")); }
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.
/// <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; } }