Beispiel #1
0
        /// <inheritdoc />
        public async Task <Context> Create(string repoPath)
        {
            // get historical changes
            var historicalChanges = gitEngine.GetGitHistoricalChangesToParent(repoPath);
            var context           = DefaultContext.Value.ShallowCopy();

            // if there are no history changes into the repo then return the default context.
            if (historicalChanges.Count == 0)
            {
                return(context);
            }

            // do the quantification based on th default context to get accurate numbers
            IPullRequestQuantifier prQuantifier = new PullRequestQuantifier(context);
            var quantifierInput = new QuantifierInput();

            quantifierInput.Changes.AddRange(historicalChanges.Values.SelectMany(v => v));
            await prQuantifier.Quantify(quantifierInput);

            context.AdditionPercentile = AdditionDeletionPercentile(historicalChanges, true);
            context.DeletionPercentile = AdditionDeletionPercentile(historicalChanges, false);
            context.FormulaPercentile  = FormulaPercentile(historicalChanges);

            return(context);
        }
Beispiel #2
0
        public async Task ToMarkdownCommentAsync_Successful()
        {
            // Arrange
            gitRepoHelpers.AddUntrackedFileToRepoWithNumLines("fake.cs", 2);
            gitRepoHelpers.AddUntrackedFileToRepoWithNumLines("fake2.cs", 4);
            gitRepoHelpers.CommitFilesToRepo();
            gitRepoHelpers.AddUntrackedFileToRepoWithNumLines("fake.cs", 5);
            gitRepoHelpers.AddUntrackedFileToRepoWithNumLines("fake2.cs", 2);
            var quantifierInput = new QuantifierInput();

            quantifierInput.Changes.AddRange(gitEngine.GetGitChanges(gitRepoHelpers.RepoPath));
            var quantifyClient = new QuantifyClient(string.Empty);

            // Act
            var quantifierResult = await quantifyClient.Compute(quantifierInput);

            var comment = await quantifierResult.ToMarkdownCommentAsync(
                RepositoryLink,
                ContextFileLink,
                PullRequestLink,
                AuthorName);

            // Assert
            Assert.True(!string.IsNullOrWhiteSpace(comment));
            Assert.StartsWith("### Pull Request Quantified", comment);
        }
 private void CountTotalChanges(
     QuantifierInput quantifierInput,
     QuantifierResult quantifierResult)
 {
     quantifierResult.QuantifiedLinesAdded   = quantifierInput.Changes.Sum(c => c.QuantifiedLinesAdded);
     quantifierResult.QuantifiedLinesDeleted = quantifierInput.Changes.Sum(c => c.QuantifiedLinesDeleted);
 }
        /// <inheritdoc />
        public async Task <QuantifierResult> Quantify(QuantifierInput quantifierInput)
        {
            ArgumentCheck.ParameterIsNotNull(quantifierInput, nameof(quantifierInput));

            // execute quantifier for this context and this particular input
            return(await Compute(quantifierInput));
        }
Beispiel #5
0
        public async Task FilesWithoutExt_ShowFullPathInSummary()
        {
            // Arrange
            gitRepoHelpers.AddUntrackedFileToRepoWithNumLines("fake.cs", 2);
            gitRepoHelpers.AddUntrackedFileToRepoWithNumLines("fake2", 4);
            gitRepoHelpers.CommitFilesToRepo();
            gitRepoHelpers.AddUntrackedFileToRepoWithNumLines("fake.cs", 5);
            gitRepoHelpers.AddUntrackedFileToRepoWithNumLines("fake2", 2);
            var quantifierInput = new QuantifierInput();

            quantifierInput.Changes.AddRange(gitEngine.GetGitChanges(gitRepoHelpers.RepoPath));
            var quantifyClient = new QuantifyClient(string.Empty);

            // Act
            var quantifierResult = await quantifyClient.Compute(quantifierInput);

            var comment = await quantifierResult.ToMarkdownCommentAsync(
                RepositoryLink,
                ContextFileLink,
                PullRequestLink,
                AuthorName);

            // Assert
            Assert.True(comment.IndexOf("fake2 : +0 -2", StringComparison.Ordinal) > -1);
            Assert.True(comment.IndexOf(".cs : +3 -0", StringComparison.Ordinal) > -1);
        }
        private QuantifierInput GetChanges(string repoPath)
        {
            var quantifierInput = new QuantifierInput();

            quantifierInput.Changes.AddRange(gitEngine.GetGitChanges(repoPath));

            return(quantifierInput);
        }
        /// <inheritdoc />
        public async Task <QuantifierResult> Compute(
            string gitRepoPath,
            string commitSha1)
        {
            var quantifierInput = new QuantifierInput();

            quantifierInput.Changes.AddRange(gitEngine.GetGitChange(gitRepoPath, commitSha1));

            // quantify the changes
            return(await prQuantifier.Quantify(quantifierInput));
        }
Beispiel #8
0
        public static async Task Main(string[] args)
        {
            var commandLine = new CommandLine(args);

            contextFilePath = commandLine.ContextPath;

            if (commandLine.QuantifierInputFile != null)
            {
                // quantifier input is specified as a file
                // run once and exit
                var quantifierInput = new QuantifierInput();
                var changes         = JsonSerializer.Deserialize <List <GitFilePatch> >(
                    await File.ReadAllTextAsync(commandLine.QuantifierInputFile));
                changes?.ForEach(c => quantifierInput.Changes.Add(c));

                quantifyClient = new QuantifyClient(contextFilePath);
                PrintResult(
                    await quantifyClient.Compute(quantifierInput),
                    commandLine.Output);
            }
            else
            {
                var gitRepoPath  = commandLine.GitRepoPath ?? Environment.CurrentDirectory;
                var repoRootPath = Repository.Discover(gitRepoPath);

                // if no repo was found  to this path then exit, don't crash
                if (string.IsNullOrWhiteSpace(repoRootPath))
                {
                    Console.WriteLine("GitRepoPath couldn't be found!");
                    return;
                }

                contextFilePath ??= Path.Combine(
                    new DirectoryInfo(repoRootPath).Parent?.FullName,
                    "prquantifier.yaml");

                // run this as a service in case is configured otherwise only run once
                if (commandLine.Service)
                {
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
                    Task.Factory.StartNew(() => QuantifyLoop(repoRootPath, commandLine.Output));
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed

                    await Run(repoRootPath);
                }

                // in case service is not set or false then run once and exit
                quantifyClient = new QuantifyClient(contextFilePath);
                PrintResult(
                    await quantifyClient.Compute(repoRootPath),
                    commandLine.Output);
            }
        }
        private async Task <QuantifierInput> GetQuantifierInputFromPullRequest(PullRequestEventPayload payload, IGitHubClientAdapter gitHubClientAdapter)
        {
            // get pull request files
            var pullRequestFiles = await gitHubClientAdapter.GetPullRequestFilesAsync(
                payload.Repository.Id,
                payload.PullRequest.Number);

            // convert to quantifier input
            var quantifierInput = new QuantifierInput();

            foreach (var pullRequestFile in pullRequestFiles)
            {
                if (pullRequestFile.Patch == null)
                {
                    continue;
                }

                var changeType = GitChangeType.Modified;
                switch (pullRequestFile.Status)
                {
                case "modified":
                    break;

                case "added":
                    changeType = GitChangeType.Added;
                    break;

                case "deleted":
                    changeType = GitChangeType.Deleted;
                    break;
                }

                var fileExtension = !string.IsNullOrWhiteSpace(pullRequestFile.FileName)
                    ? new FileInfo(pullRequestFile.FileName).Extension
                    : string.Empty;
                var gitFilePatch = new GitFilePatch(
                    pullRequestFile.FileName,
                    fileExtension)
                {
                    ChangeType           = changeType,
                    AbsoluteLinesAdded   = pullRequestFile.Additions,
                    AbsoluteLinesDeleted = pullRequestFile.Deletions,
                    DiffContent          = pullRequestFile.Patch,
                };
                quantifierInput.Changes.Add(gitFilePatch);
            }

            return(quantifierInput);
        }
Beispiel #10
0
        public async Task Quantify_NoChanges_ReturnsZeroCounts()
        {
            // Arrange
            var prQuantifier    = new PullRequestQuantifier(context);
            var quantifierInput = new QuantifierInput();

            quantifierInput.Changes.AddRange(gitEngine.GetGitChanges(gitRepoHelpers.RepoPath));

            // Act
            var quantifierResult = await prQuantifier.Quantify(quantifierInput);

            // Assert
            Assert.Equal("No Changes", quantifierResult.Label);
            Assert.Equal(0, quantifierResult.QuantifiedLinesAdded);
            Assert.Equal(0, quantifierResult.QuantifiedLinesDeleted);
        }
Beispiel #11
0
        public async Task Quantify_FilesWithPlusMinusContent()
        {
            // Arrange
            gitRepoHelpers.AddUntrackedFileToRepoWithContent("fake.cs", "-text\n+add\nnormal\n-44");
            var prQuantifier    = new PullRequestQuantifier(context);
            var quantifierInput = new QuantifierInput();

            quantifierInput.Changes.AddRange(gitEngine.GetGitChanges(gitRepoHelpers.RepoPath));

            // Act
            var quantifierResult = await prQuantifier.Quantify(quantifierInput);

            // Assert
            Assert.True(!string.IsNullOrEmpty(quantifierResult.Label));
            Assert.Equal(4, quantifierResult.QuantifiedLinesAdded);
            Assert.Equal(0, quantifierResult.QuantifiedLinesDeleted);
        }
Beispiel #12
0
        public async Task Quantify_UntrackedFilesOnly()
        {
            // Arrange
            gitRepoHelpers.AddUntrackedFileToRepoWithNumLines("fake.cs", 2);
            var prQuantifier    = new PullRequestQuantifier(context);
            var quantifierInput = new QuantifierInput();

            quantifierInput.Changes.AddRange(gitEngine.GetGitChanges(gitRepoHelpers.RepoPath));

            // Act
            var quantifierResult = await prQuantifier.Quantify(quantifierInput);

            // Assert
            Assert.True(!string.IsNullOrEmpty(quantifierResult.Label));
            Assert.Equal(2, quantifierResult.QuantifiedLinesAdded);
            Assert.Equal(0, quantifierResult.QuantifiedLinesDeleted);
        }
Beispiel #13
0
        public async Task Quantify_IgnoreSpacesFalse()
        {
            // Arrange
            gitRepoHelpers.AddUntrackedFileToRepoWithContent("fake.cs", "some text\n\nmore text");
            context.LanguageOptions.IgnoreSpaces = false;
            var prQuantifier    = new PullRequestQuantifier(context);
            var quantifierInput = new QuantifierInput();

            quantifierInput.Changes.AddRange(gitEngine.GetGitChanges(gitRepoHelpers.RepoPath));

            // Act
            var quantifierResult = await prQuantifier.Quantify(quantifierInput);

            // Assert
            Assert.True(!string.IsNullOrEmpty(quantifierResult.Label));
            Assert.Equal(3, quantifierResult.QuantifiedLinesAdded);
            Assert.Equal(0, quantifierResult.QuantifiedLinesDeleted);
        }
Beispiel #14
0
        public async Task ToMarkdownCommentAsync_Options_CollapseChangesSummarySection(
            bool collapseChangesSummarySection)
        {
            // Arrange
            gitRepoHelpers.AddUntrackedFileToRepoWithNumLines("fake.cs", 2);
            gitRepoHelpers.AddUntrackedFileToRepoWithNumLines("fake2.cs", 4);
            gitRepoHelpers.CommitFilesToRepo();
            gitRepoHelpers.AddUntrackedFileToRepoWithNumLines("fake.cs", 5);
            gitRepoHelpers.AddUntrackedFileToRepoWithNumLines("fake2.cs", 2);
            var quantifierInput = new QuantifierInput();

            quantifierInput.Changes.AddRange(gitEngine.GetGitChanges(gitRepoHelpers.RepoPath));
            var quantifyClient = new QuantifyClient(string.Empty);

            // Act
            var quantifierResult = await quantifyClient.Compute(quantifierInput);

            var comment = await quantifierResult.ToMarkdownCommentAsync(
                RepositoryLink,
                ContextFileLink,
                PullRequestLink,
                AuthorName,
                new MarkdownCommentOptions
            {
                CollapseChangesSummarySection = collapseChangesSummarySection
            });

            // Assert
            Assert.True(!string.IsNullOrWhiteSpace(comment));
            Assert.StartsWith("### Pull Request Quantified", comment);
            if (collapseChangesSummarySection)
            {
                Assert.True(comment.IndexOf(
                                await File.ReadAllTextAsync(@"Data/AssertCommentSummaryCollapsed.txt"),
                                StringComparison.Ordinal) > -1);
            }
            else
            {
                Assert.True(comment.IndexOf(
                                await File.ReadAllTextAsync(@"Data/AssertCommentSummaryNotCollapsed.txt"),
                                StringComparison.Ordinal) > -1);
            }
        }
Beispiel #15
0
        public async Task ToMarkdownCommentAsync_Successful()
        {
            // Arrange
            var quantifierInput = new QuantifierInput();

            quantifierInput.Changes.AddRange(gitEngine.GetGitChanges(gitRepoHelpers.RepoPath));
            var quantifyClient = new QuantifyClient(string.Empty);

            // Act
            var quantifierResult = await quantifyClient.Compute(quantifierInput);

            var comment = await quantifierResult.ToMarkdownCommentAsync(
                RepositoryLink,
                ContextFileLink,
                PullRequestLink,
                AuthorName);

            // Assert
            Assert.True(!string.IsNullOrWhiteSpace(comment));
            Assert.StartsWith("### ![](https://img.shields.io/static/v1?label=Quantified&message=Extra%20Small&color=green)", comment);
        }
Beispiel #16
0
        public async Task ToMarkdownCommentAsync_Options_CollapseQuantificationDetailsSection(bool collapseQuantifiedDetailsSection)
        {
            // Arrange
            var quantifierInput = new QuantifierInput();

            quantifierInput.Changes.AddRange(gitEngine.GetGitChanges(gitRepoHelpers.RepoPath));
            var quantifyClient = new QuantifyClient(string.Empty);

            // Act
            var quantifierResult = await quantifyClient.Compute(quantifierInput);

            var comment = await quantifierResult.ToMarkdownCommentAsync(
                RepositoryLink,
                ContextFileLink,
                PullRequestLink,
                AuthorName,
                false,
                new MarkdownCommentOptions
            {
                CollapsePullRequestQuantifiedSection = collapseQuantifiedDetailsSection
            });

            // Assert
            Assert.True(!string.IsNullOrWhiteSpace(comment));
            Assert.StartsWith("### ![](https://img.shields.io/static/v1?label=Quantified&message=Extra%20Small&color=green)", comment);
            if (collapseQuantifiedDetailsSection)
            {
                Assert.True(
                    comment.IndexOf(
                        await File.ReadAllTextAsync(@"Data/AssertCommentSummaryCollapsed.txt"),
                        StringComparison.Ordinal) > -1);
            }
            else
            {
                Assert.True(
                    comment.IndexOf(
                        await File.ReadAllTextAsync(@"Data/AssertCommentSummaryNotCollapsed.txt"),
                        StringComparison.Ordinal) > -1);
            }
        }
        public async Task ToMarkdownCommentAsync_IdealSizeCheck(int formulaLinesChanged, bool isIdeal)
        {
            // Arrange
            var quantifierInput = new QuantifierInput();

            quantifierInput.Changes.AddRange(gitEngine.GetGitChanges(gitRepoHelpers.RepoPath));
            var quantifyClient   = new QuantifyClient(string.Empty);
            var quantifierResult = await quantifyClient.Compute(quantifierInput);

            quantifierResult.FormulaLinesChanged = formulaLinesChanged;

            var comment = await quantifierResult.ToMarkdownCommentAsync(
                RepositoryLink,
                ContextFileLink,
                PullRequestLink,
                AuthorName);

            // Assert
            var idealJobIndex = comment.IndexOf("Great job", StringComparison.Ordinal);

            Assert.True((isIdeal && idealJobIndex > -1) || (!isIdeal && idealJobIndex == -1));
        }
        private async Task <QuantifierResult> Compute(QuantifierInput quantifierInput)
        {
            QuantifierResult quantifierResult = null;

            await Task.Factory.StartNew(
                () =>
            {
                quantifierResult = new QuantifierResult
                {
                    QuantifierInput = quantifierInput
                };

                // involve context and compute
                Parallel.ForEach(quantifierInput.Changes, ApplyContext);

                CountTotalChanges(quantifierInput, quantifierResult);

                // compute the label using the context percentile information and the thresholds
                SetLabel(quantifierResult);
            });

            return(quantifierResult);
        }
Beispiel #19
0
        public async Task QuantifyClient_MissingFormulaPercentileContext()
        {
            // Arrange
            gitRepoHelpers.AddUntrackedFileToRepoWithNumLines("fake.cs", 2);
            gitRepoHelpers.AddUntrackedFileToRepoWithNumLines("fake2.cs", 4);
            gitRepoHelpers.CommitFilesToRepo();
            gitRepoHelpers.AddUntrackedFileToRepoWithNumLines("fake.cs", 5);
            gitRepoHelpers.AddUntrackedFileToRepoWithNumLines("fake2.cs", 2);
            var quantifierInput = new QuantifierInput();

            quantifierInput.Changes.AddRange(gitEngine.GetGitChanges(gitRepoHelpers.RepoPath));
            var quantifyClient = new QuantifyClient(@"Data/MissingFormulaPercentileContext.prquantifier.yaml");

            // Act
            var quantifierResult = await quantifyClient.Compute(quantifierInput);

            // Assert
            Assert.True(!string.IsNullOrEmpty(quantifierResult.Label));
            Assert.Equal(3, quantifierResult.QuantifiedLinesAdded);
            Assert.Equal(2, quantifierResult.QuantifiedLinesDeleted);
            Assert.Equal(0, quantifierResult.PercentileAddition);
            Assert.Equal(0, quantifierResult.PercentileDeletion);
            Assert.Equal(0, quantifierResult.FormulaPercentile);
        }
 /// <inheritdoc />
 public async Task <QuantifierResult> Compute(QuantifierInput quantifierInput)
 {
     // quantify the changes
     return(await prQuantifier.Quantify(quantifierInput));
 }
        private async Task <QuantifierResult> QuantifyPullRequest(PullRequestEventPayload payload)
        {
            var gitHubClientAdapter =
                await gitHubClientAdapterFactory.GetGitHubClientAdapterAsync(
                    payload.Installation.Id,
                    new Uri(payload.PullRequest.HtmlUrl).DnsSafeHost);

            // get pull request
            var pullRequest = await gitHubClientAdapter.GetPullRequestAsync(
                payload.Repository.Id,
                payload.PullRequest.Number);

            // get pull request files
            var pullRequestFiles = await gitHubClientAdapter.GetPullRequestFilesAsync(
                payload.Repository.Id,
                payload.PullRequest.Number);

            // convert to quantifier input
            var quantifierInput = new QuantifierInput();

            foreach (var pullRequestFile in pullRequestFiles)
            {
                if (pullRequestFile.Patch == null)
                {
                    continue;
                }

                var changeType = GitChangeType.Modified;
                switch (pullRequestFile.Status)
                {
                case "modified":
                    break;

                case "added":
                    changeType = GitChangeType.Added;
                    break;

                case "deleted":
                    changeType = GitChangeType.Deleted;
                    break;
                }

                var fileExtension = !string.IsNullOrWhiteSpace(pullRequestFile.FileName)
                    ? new FileInfo(pullRequestFile.FileName).Extension
                    : string.Empty;
                var gitFilePatch = new GitFilePatch(
                    pullRequestFile.FileName,
                    fileExtension)
                {
                    ChangeType           = changeType,
                    AbsoluteLinesAdded   = pullRequestFile.Additions,
                    AbsoluteLinesDeleted = pullRequestFile.Deletions,
                    DiffContent          = pullRequestFile.Patch,
                };
                quantifierInput.Changes.Add(gitFilePatch);
            }

            // get context if present
            string context = null;

            try
            {
                var rawContext = await gitHubClientAdapter.GetRawFileAsync(
                    payload.Repository.Owner.Login,
                    payload.Repository.Name,
                    "/prquantifier.yaml");

                context = Encoding.UTF8.GetString(rawContext);
            }
            catch (NotFoundException)
            {
            }
            catch
            {
                // ignored
            }

            var quantifyClient         = new QuantifyClient(context);
            var quantifierClientResult = await quantifyClient.Compute(quantifierInput);

            // create a new label in the repository if doesn't exist
            try
            {
                var existingLabel = await gitHubClientAdapter.GetLabelAsync(
                    payload.Repository.Id,
                    quantifierClientResult.Label);
            }
            catch (NotFoundException)
            {
                // create new label
                var color = Color.FromName(quantifierClientResult.Color);
                await gitHubClientAdapter.CreateLabelAsync(
                    payload.Repository.Id,
                    new NewLabel(quantifierClientResult.Label, ConvertToHex(color)));
            }

            // apply label to pull request
            await gitHubClientAdapter.ApplyLabelAsync(
                payload.Repository.Id,
                payload.PullRequest.Number,
                new[] { quantifierClientResult.Label });

            // create a comment on the issue
            var defaultBranch         = payload.Repository.DefaultBranch;
            var quantifierContextLink = $"{payload.Repository.HtmlUrl}/blob/{defaultBranch}/prquantifier.yaml";
            var comment = await quantifierClientResult.ToMarkdownCommentAsync(
                payload.Repository.HtmlUrl,
                quantifierContextLink,
                payload.PullRequest.HtmlUrl,
                payload.PullRequest.User.Login);

            await gitHubClientAdapter.CreateIssueCommentAsync(
                payload.Repository.Id,
                payload.PullRequest.Number,
                comment);

            return(quantifierClientResult);
        }
        private static async Task RunQuantifier(
            QuantifyClient quantifyClient,
            string resultFile,
            string repoPath)
        {
            var repoRoot = LibGit2Sharp.Repository.Discover(repoPath);

            if (repoRoot == null)
            {
                Console.WriteLine($"No repo found at {repoPath}");
                return;
            }

            using var repo = new LibGit2Sharp.Repository(repoRoot);
            var commits = repo.Commits.QueryBy(
                new CommitFilter
            {
                FirstParentOnly = true
            });

            Console.WriteLine($"Total commits to evaluate : {commits.Count()}. Repository path {repoPath}.");
            var sw = new Stopwatch();

            sw.Reset();
            sw.Start();
            var batchSize         = 100;
            var quantifierResults = new ConcurrentDictionary <string, QuantifierResult>();

            for (int page = 0; page < (commits.Count() / batchSize) + 1; page++)
            {
                var commitBatch   = commits.Skip(batchSize * page).Take(batchSize);
                var quantifyTasks = commitBatch.Select(
                    async commit =>
                {
                    try
                    {
                        var quantifierInput = new QuantifierInput();
                        var firstParent     = commit.Parents.FirstOrDefault();
                        if (firstParent != null)
                        {
                            var patch = repo.Diff.Compare <Patch>(firstParent.Tree, commit.Tree);

                            foreach (var gitFilePatch in patch.GetGitFilePatch())
                            {
                                quantifierInput.Changes.Add(gitFilePatch);
                            }
                        }

                        var quantifierResult = await quantifyClient.Compute(quantifierInput);
                        quantifierResults.TryAdd(commit.Sha, quantifierResult);
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(e);
                    }
                });
                await Task.WhenAll(quantifyTasks);
                await AddResultsToFile(quantifierResults, resultFile);

                quantifierResults = new ConcurrentDictionary <string, QuantifierResult>();
                Console.WriteLine($"{page * batchSize}/{commits.Count()} {sw.Elapsed}");
            }

            Console.WriteLine("Completed!");
        }