private bool ShouldAddReviewer(PullRequestContext pullRequestContext, PullRequestReviewerSelectionStrategy[] strategies) { var result = false; if (strategies.Length == 0) { result = _pullRequestReviewerSelectionDefaultStrategy.Action == "add"; } else { result = strategies.Any(q => q.Action == "add"); } if (result) { if (!_addOnlyToUnsafePullrequests.HasValue || !_addOnlyToUnsafePullrequests.Value) { return(true); } if (_addOnlyToUnsafePullrequests.Value && !pullRequestContext.PullRequestFilesAreSafe) { return(true); } } return(false); }
private long GetLoadScore(PullRequestContext pullRequestContext, DeveloperKnowledge reviewer) { var reviwes = new List <long>(); var overitems = pullRequestContext.Overlap; if (overitems.Count == 0) { return(0); } foreach (var pullreq in pullRequestContext.KnowledgeMap.ReviewBasedKnowledgeMap.GetDeveloperReviews(reviewer.DeveloperName)) { reviwes.Add(pullreq.Number); } var count = overitems.Intersect(reviwes); var commits = pullRequestContext.KnowledgeMap.CommitBasedKnowledgeMap.GetDeveloperCommits(reviewer.DeveloperName); var start = pullRequestContext.PullRequest.CreatedAtDateTime; var end = pullRequestContext.PullRequest.ClosedAtDateTime; var conflict_commits = commits.Where(a => a.AuthorDateTime >= start && a.AuthorDateTime <= end); if (conflict_commits.Count() > 1) { var i = 9; } return(count.Count() + conflict_commits.Count()); }
public (CommitState state, string description) GetStatus(PullRequestContext context) { var pullRequest = context.PullRequestInfo; var containsWip = ContainsWip(context.RepositorySettings); if (containsWip(pullRequest.Title)) { context.Logger.LogInformation("Pull request title matches WIP regex"); return(CommitState.Pending, "Work in progress"); } if (pullRequest.CommitMessages.Any(containsWip)) { context.Logger.LogInformation("Commit message matches WIP regex"); return(CommitState.Pending, "Work in progress"); } var wipLabels = context.RepositorySettings.WipLabels ?? Array.Empty <string>(); if (pullRequest.Labels.Intersect(wipLabels, StringComparer.OrdinalIgnoreCase).Any()) { context.Logger.LogInformation("Pull request has a WIP label"); return(CommitState.Pending, "Work in progress"); } if (pullRequest.CommitMessages.Any(ShouldBeSquashed)) { context.Logger.LogInformation("Commit message has squash or fixup prefix"); return(CommitState.Pending, "Needs to be squashed before merging"); } return(CommitState.Success, "Ready to merge"); }
public PullRequestKnowledgeDistribution( IEnumerable <DeveloperKnowledge> reviewers, PullRequestContext pullRequestContext, Func <PullRequestContext, PullRequestKnowledgeDistributionFactors, double> scoreComputerFunc) { PullRequestKnowledgeDistributionFactors = new PullRequestKnowledgeDistributionFactors(reviewers, pullRequestContext, scoreComputerFunc); }
internal override sealed DeveloperKnowledge[] AvailablePRKnowledgeables(PullRequestContext pullRequestContext) { var availableDevs = pullRequestContext.PullRequestKnowledgeables.Where(q => q.DeveloperName != pullRequestContext.PRSubmitterNormalizedName && IsDeveloperAvailable(pullRequestContext, q.DeveloperName)).ToArray(); ComputeAllReviewerScores(pullRequestContext, availableDevs); var depthToScanForReviewers = 0; while (availableDevs.All(q => q.Score == 0) && depthToScanForReviewers < 5) { depthToScanForReviewers++; var folderLevelOwners = GetFolderLevelOweners(depthToScanForReviewers, pullRequestContext) .Where(q => availableDevs.All(ad => q.DeveloperName != ad.DeveloperName)); availableDevs = availableDevs.Concat(folderLevelOwners .Where(q => q.DeveloperName != pullRequestContext.PRSubmitterNormalizedName && IsDeveloperAvailable(pullRequestContext, q.DeveloperName))).ToArray(); ComputeAllReviewerScores(pullRequestContext, availableDevs); } return(availableDevs .OrderByDescending(q => q.Score) .ToArray()); }
public async Task <RepositorySettings> GetRepositorySettingsAsync(PullRequestContext context) { try { var baseRef = context.Payload.PullRequest.Base.Ref; var repositoryId = context.Payload.Repository.Id; var client = new GitHubClient(context.GithubConnection); var contents = await client.Repository.Content.GetAllContentsByRef(repositoryId, "dontmergemeyet.yml", baseRef); var yaml = contents.FirstOrDefault()?.Content; var settings = !string.IsNullOrEmpty(yaml) ? YamlDeserializer.Deserialize <RepositorySettings>(yaml) : DefaultSettings; return(settings); } catch (NotFoundException) { context.Logger.LogInformation("Configuration file not found, using defaults"); return(DefaultSettings); } catch (Exception ex) { context.Logger.LogError(ex, "Failed to get configuration file from repo, using defaults"); return(DefaultSettings); } }
internal override double ComputeReviewerScore(PullRequestContext pullRequestContext, DeveloperKnowledge reviewer) { double?commit_score = 0.0; double?review_score = 0.0; double?neighber_commit_score = 0.0; double?neighber_review_score = 0.0; foreach (var pullRequestFile in pullRequestContext.PullRequestFiles) { var canonicalPath = pullRequestContext.CanononicalPathMapper.GetValueOrDefault(pullRequestFile.FileName); if (canonicalPath == null) { continue; } commit_score += ComputeCommitScore(pullRequestContext, canonicalPath, reviewer); review_score += ComputeReviewScore(pullRequestContext, canonicalPath, reviewer); var blameSnapshot = pullRequestContext.KnowledgeMap.BlameBasedKnowledgeMap.GetSnapshopOfPeriod(pullRequestContext.PullRequestPeriod.Id); var neighbors = blameSnapshot.Trie.GetFileNeighbors(1, canonicalPath); for (int j = 0; j < neighbors.Length; j++) { var neighber_file = neighbors[j]; neighber_commit_score += ComputeCommitScore(pullRequestContext, neighber_file, reviewer); neighber_review_score += ComputeReviewScore(pullRequestContext, neighber_file, reviewer); } } double loadscore = GetLoadScore(pullRequestContext, reviewer); var load = Math.Pow(Math.E, (0.5 * loadscore)); double final_score = Convert.ToDouble(review_score + commit_score + neighber_commit_score + neighber_review_score) / load; double final_score_noload = Convert.ToDouble(review_score + commit_score + neighber_commit_score + neighber_review_score); return(final_score); }
internal override double ComputeReviewerScore(PullRequestContext pullRequestContext, DeveloperKnowledge reviewer) { var probabilityOfStay = pullRequestContext.GetProbabilityOfStay(reviewer.DeveloperName, _numberOfPeriodsForCalculatingProbabilityOfStay.Value); var effort = pullRequestContext.GetEffort(reviewer.DeveloperName, _numberOfPeriodsForCalculatingProbabilityOfStay.Value); return(effort * probabilityOfStay); }
protected override PullRequestRecommendationResult RecommendReviewers(PullRequestContext pullRequestContext) { var availableDevs = AvailablePRKnowledgeables(pullRequestContext); if (availableDevs.Length == 0) { return(Actual(pullRequestContext)); } var simulationResults = new List <PullRequestKnowledgeDistribution>(); var simulator = new PullRequestReviewSimulator(pullRequestContext, availableDevs, ComputeScore); foreach (var candidateSet in GetPossibleCandidateSets(pullRequestContext, availableDevs)) { var simulationResult = simulator.Simulate(candidateSet.Reviewers, candidateSet.SelectedCandidateKnowledge); simulationResults.Add(simulationResult); } if (simulationResults.Count == 0) { return(Actual(pullRequestContext)); } var bestPullRequestKnowledgeDistribution = GetBestDistribution(simulationResults); return(Recommendation(pullRequestContext, availableDevs, bestPullRequestKnowledgeDistribution)); }
private double ComputeReviewScore(PullRequestContext pullRequestContext, string filePath, DeveloperKnowledge reviewer) { double score = 0.0; DateTime nowTime = pullRequestContext.PullRequest.CreatedAtDateTime ?? DateTime.Now; var data = pullRequestContext.KnowledgeMap.ReviewBasedKnowledgeMap.GetReviowersOnFile(reviewer.DeveloperName, filePath); var reviewNumber = data.Keys.FirstOrDefault(); var reviewerExpertise = pullRequestContext.KnowledgeMap.PullRequestEffortKnowledgeMap.GetReviewerExpertise(filePath, reviewer.DeveloperName); if (reviewNumber + reviewerExpertise.TotalComments == 0) { return(score); } var recency = data.Values.FirstOrDefault(); if (recency < reviewerExpertise.RecentWorkDay) { recency = reviewerExpertise.RecentWorkDay ?? recency; } if (recency != DateTime.MinValue) { var diff_review = (nowTime - recency).TotalDays == 0 ? 1 : (nowTime - recency).TotalDays; score = reviewNumber + reviewerExpertise.TotalComments / diff_review; } return(score); }
private void ComputeAllReviewerScores(PullRequestContext pullRequestContext, DeveloperKnowledge[] availableDevs) { foreach (var candidate in availableDevs) { var score = ComputeReviewerScore(pullRequestContext, candidate); candidate.Score = score; } }
public PullRequestReviewSimulator( PullRequestContext pullRequestContext, DeveloperKnowledge[] availableDevs, Func <PullRequestContext, PullRequestKnowledgeDistributionFactors, double> scoreComputerFunc) { _pullRequestContext = pullRequestContext; _scoreComputerFunc = scoreComputerFunc; }
public PullRequestKnowledgeDistributionFactors( IEnumerable <DeveloperKnowledge> reviewers, PullRequestContext pullRequestContext, Func <PullRequestContext, PullRequestKnowledgeDistributionFactors, double> scoreComputerFunc) { Reviewers = reviewers; PullRequestContext = pullRequestContext; _scoreComputerFunc = scoreComputerFunc; }
internal override double ComputeReviewerScore(PullRequestContext pullRequestContext, DeveloperKnowledge reviewer) { var totalReviews = pullRequestContext.PullRequestKnowledgeables.Sum(q => q.NumberOfReviews); if (totalReviews == 0) { return(0); } return(reviewer.NumberOfReviews / (double)totalReviews); }
public async Task WriteCommitStatusAsync(PullRequestContext context, CommitState state, string description) { var client = new CommitStatusClient(new ApiConnection(context.GithubConnection)); var status = new NewCommitStatus { State = state, Description = description, Context = _settingsProvider.Settings.StatusContext }; await client.Create(context.Payload.Repository.Id, context.Payload.PullRequest.Head.Sha, status); }
internal override double ComputeReviewerScore(PullRequestContext pullRequestContext, DeveloperKnowledge reviewer) { var prFiles = pullRequestContext.PullRequestFiles.Select(q => pullRequestContext.CanononicalPathMapper[q.FileName]) .Where(q => q != null).ToArray(); var reviewedFiles = reviewer.GetTouchedFiles().Where(q => prFiles.Contains(q)); var specializedKnowledge = reviewedFiles.Count() / (double)pullRequestContext.PullRequestFiles.Length; return(1 - specializedKnowledge); }
internal override double ComputeReviewerScore(PullRequestContext pullRequestContext, DeveloperKnowledge reviewer) { double spreadingScore = GetPersistSpreadingScore(pullRequestContext, reviewer); var expertiseScore = ComputeBirdReviewerScore(pullRequestContext, reviewer); var alpha = pullRequestContext.GetRiskyFiles(_riskOwenershipThreshold).Length > 0 ? 1 : 0; var score = (1 - alpha) * expertiseScore + alpha * spreadingScore; return(score); }
internal override sealed double ComputeScore(PullRequestContext pullRequestContext, PullRequestKnowledgeDistributionFactors pullRequestKnowledgeDistributionFactors) { var scores = new List <double>(); foreach (var reviewer in pullRequestKnowledgeDistributionFactors.Reviewers) { double score = reviewer.Score == 0 ? ComputeReviewerScore(pullRequestContext, reviewer) : reviewer.Score; scores.Add(score); } return(scores.Aggregate((a, b) => a + b)); }
public async Task HandleWebhookEventAsync(PullRequestContext context) { context.Logger.LogDebug("Getting details for pull request #{PullRequestNumber}...", context.Payload.Number); context.PullRequestInfo = await _prInfoProvider.GetPullRequestInfoAsync(context); context.Logger.LogDebug("Getting repository settings for pull request #{PullRequestNumber}", context.Payload.Number); context.RepositorySettings = await _repositorySettingsProvider.GetRepositorySettingsAsync(context); context.Logger.LogDebug("Evaluating status for pull request #{PullRequestNumber}...", context.Payload.Number); var(state, description) = _pullRequestPolicy.GetStatus(context); context.Logger.LogInformation("Status for pull request #{PullRequestNumber} is '{PullRequestState}' ({PullRequestDescription})", context.Payload.Number, state, description); context.Logger.LogDebug("Writing commit status for pull request #{PullRequestNumber}...", context.Payload.Number); await _statusWriter.WriteCommitStatusAsync(context, state, description); }
public async Task HandleWebhookEventAsync(PullRequestContext context) { context.Log.Verbose($"Getting details for pull request #{context.Payload.Number}..."); context.PullRequestInfo = await _prInfoProvider.GetPullRequestInfoAsync(context); context.Log.Verbose($"Getting repository settings for pull request #{context.Payload.Number}"); context.RepositorySettings = await _repositorySettingsProvider.GetRepositorySettingsAsync(context); context.Log.Verbose($"Evaluating status for pull request #{context.Payload.Number}..."); var(state, description) = _pullRequestPolicy.GetStatus(context); context.Log.Info($"Status for pull request #{context.Payload.Number} is '{state}' ({description})"); context.Log.Verbose($"Writing commit status for pull request #{context.Payload.Number}..."); await _statusWriter.WriteCommitStatusAsync(context, state, description); }
private int GetSelectedCandidatesLength(PullRequestContext pullRequestContext, PullRequestReviewerSelectionStrategy[] strategies, string policy) { var length = _pullRequestReviewerSelectionDefaultStrategy.ActionArgument; if (strategies.Length > 0) { length = strategies.Single(q => q.Action.StartsWith(policy)).ActionArgument; } if (length == "all") { return(pullRequestContext.ActualReviewers.Length); } return(int.Parse(length)); }
public async Task <PullRequestInfo> GetPullRequestInfoAsync(PullRequestContext context) { context.Log.Verbose($"Getting commits for pull request #{context.Payload.Number}"); var commits = await GetCommitsAsync(context); context.Log.Verbose($"Getting labels for pull request #{context.Payload.Number}"); var labels = await GetLabelsAsync(context); return(new PullRequestInfo { Title = context.Payload.PullRequest.Title, Labels = labels.Select(l => l.Name), SourceRepositoryFullName = context.Payload.Repository.FullName, Head = context.Payload.PullRequest.Head.Sha, CommitMessages = commits.Select(c => c.Commit.Message) }); }
private double GetPersistSpreadingScore(PullRequestContext pullRequestContext, DeveloperKnowledge reviewer) { var reviewerImportance = pullRequestContext.IsHoarder(reviewer.DeveloperName) ? _hoarderRatio : 1; var probabilityOfStay = pullRequestContext.GetProbabilityOfStay(reviewer.DeveloperName, _numberOfPeriodsForCalculatingProbabilityOfStay.Value); var effort = pullRequestContext.GetEffort(reviewer.DeveloperName, _numberOfPeriodsForCalculatingProbabilityOfStay.Value); var prFiles = pullRequestContext.PullRequestFiles.Select(q => pullRequestContext.CanononicalPathMapper[q.FileName]) .Where(q => q != null).ToArray(); var reviewedFiles = reviewer.GetTouchedFiles().Where(q => prFiles.Contains(q)); var specializedKnowledge = reviewedFiles.Count() / (double)pullRequestContext.PullRequestFiles.Length; var spreadingScore = 0.0; spreadingScore = reviewerImportance * Math.Pow(probabilityOfStay * effort, _alpha) * Math.Pow(1 - specializedKnowledge, _beta); return(spreadingScore); }
public async Task <PullRequestInfo> GetPullRequestInfoAsync(PullRequestContext context) { context.Logger.LogDebug("Getting commits for pull request {RepoName}#{PullRequestNumber}", context.RepositoryName, context.Payload.Number); var commits = await GetCommitsAsync(context); context.Logger.LogDebug("Getting labels for pull request {RepoName}#{PullRequestNumber}", context.RepositoryName, context.Payload.Number); var labels = await GetLabelsAsync(context); return(new PullRequestInfo { Title = context.Payload.PullRequest.Title, Labels = labels.Select(l => l.Name), IsDraft = context.Payload.PullRequest.Draft, SourceRepositoryFullName = context.RepositoryName, Head = context.Payload.PullRequest.Head.Sha, CommitMessages = commits.Select(c => c.Commit.Message) }); }
private long GetLoadScore(PullRequestContext pullRequestContext, DeveloperKnowledge reviewer) { var reviwes = new List <long>(); var overitems = pullRequestContext.Overlap; if (overitems.Count == 0) { return(0); } foreach (var pullreq in pullRequestContext.KnowledgeMap.ReviewBasedKnowledgeMap.GetDeveloperReviews(reviewer.DeveloperName)) { reviwes.Add(pullreq.Number); } var count = overitems.Intersect(reviwes); return(count.Count()); }
internal override double ComputeReviewerScore(PullRequestContext pullRequestContext, DeveloperKnowledge reviewer) { var alpha = pullRequestContext.GetRiskyFiles(_riskOwenershipThreshold).Length > 0 ? 1 : 0; if (alpha == 1) { double spreadingScore = GetPersistSpreadingScore(pullRequestContext, reviewer); return(spreadingScore); } else { double loadscore = GetLoadScore(pullRequestContext, reviewer); var load = Math.Pow(Math.E, (0.5 * loadscore)); var expertiseScore = ComputeBirdReviewerScore(pullRequestContext, reviewer); return(expertiseScore / load); } }
private bool IsRandomReplacement(PullRequestContext pullRequestContext, PullRequestReviewerSelectionStrategy[] strategies) { if (pullRequestContext.ActualReviewers.Length == 0) { return(false); } var result = false; if (strategies.Length == 0) { result = _pullRequestReviewerSelectionDefaultStrategy.Action == "replacerandom"; } else { result = strategies.Any(q => q.Action == "replacerandom"); } return(result); }
private bool ShouldReplaceReviewer(PullRequestContext pullRequestContext, PullRequestReviewerSelectionStrategy[] strategies) { if (pullRequestContext.ActualReviewers.Length == 0) { return(false); } var result = false; if (strategies.Length == 0) { result = _pullRequestReviewerSelectionDefaultStrategy.Action.StartsWith("replace"); } else { result = strategies.Any(q => q.Action.StartsWith("replace")); } return(result); }
private PullRequestReviewerSelectionStrategy[] GetModificationStrategies(PullRequestContext pullRequestContext) { var strategies = _pullRequestReviewerSelectionStrategies .Where(q => q.ActualReviewerCount == pullRequestContext.ActualReviewers.Length.ToString()) .ToArray(); if (strategies.Length > 2) { throw new InvalidOperationException($"There are more than two modification strategies for : {pullRequestContext.ActualReviewers.Length}"); } if (strategies.Length == 2) { if (strategies[0].Action == strategies[1].Action) { throw new InvalidOperationException($"There are duplicated modification strategies for : {pullRequestContext.ActualReviewers.Length}"); } } return(strategies); }
private double ComputeCommitScore(PullRequestContext pullRequestContext, string filePath, DeveloperKnowledge reviewer) { double score = 0.0; DateTime reviewer_recency = DateTime.MinValue; DateTime nowTime = pullRequestContext.PullRequest.CreatedAtDateTime ?? DateTime.Now; var reviewerCommits = pullRequestContext.KnowledgeMap.CommitBasedKnowledgeMap.GetDeveloperCommitsOnFile(reviewer.DeveloperName, filePath, nowTime, reviewer_recency); if (reviewerCommits == 0) { return(score); } if (reviewer_recency != DateTime.MinValue) { var diff_review = (nowTime - reviewer_recency).TotalDays == 0 ? 1 : (nowTime - reviewer_recency).TotalDays; score = reviewerCommits / diff_review; } return(score); }