<commit_before>﻿using GitVersion.VersionCalculation.BaseVersionCalculators;
using LibGit2Sharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace GitVersion.VersionCalculation
{
    class MainlineVersionCalculator
    {
        IMetaDataCalculator metaDataCalculator;

        public MainlineVersionCalculator(IMetaDataCalculator metaDataCalculator)
        {
            this.metaDataCalculator = metaDataCalculator;
        }

        public SemanticVersion FindMainlineModeVersion(BaseVersion baseVersion, GitVersionContext context)
        {
            if (baseVersion.SemanticVersion.PreReleaseTag.HasTag())
            {
                throw new NotSupportedException("Mainline development mode doesn't yet support pre-release tags on master");
            }

            using (Logger.IndentLog("Using mainline development mode to calculate current version"))
            {
                var mainlineVersion = baseVersion.SemanticVersion;

                // Forward merge / PR
                //          * feature/foo
                //         / |
                // master *  *
                // 

                var mainlineTip = GetMainlineTip(context);
                var commitsNotOnMainline = context.Repository.Commits.QueryBy(new CommitFilter
                {
                    IncludeReachableFrom = context.CurrentBranch,
                    ExcludeReachableFrom = mainlineTip,
                    SortBy = CommitSortStrategies.Reverse,
                    FirstParentOnly = true
                }).Where(c => c.Sha != baseVersion.BaseVersionSource.Sha && c.Parents.Count() == 1).ToList();
                var commitLog = context.Repository.Commits.QueryBy(new CommitFilter
                {
                    IncludeReachableFrom = context.CurrentBranch,
                    ExcludeReachableFrom = baseVersion.BaseVersionSource,
                    SortBy = CommitSortStrategies.Reverse,
                    FirstParentOnly = true
                })
                .Where(c => c.Sha != baseVersion.BaseVersionSource.Sha)
                .Except(commitsNotOnMainline)
                .ToList();

                var directCommits = new List<Commit>();

                // Scans commit log in reverse, aggregating merge commits
                foreach (var commit in commitLog)
                {
                    directCommits.Add(commit);
                    if (commit.Parents.Count() > 1)
                    {
                        mainlineVersion = AggregateMergeCommitIncrement(context, commit, directCommits, mainlineVersion);
                    }
                }

                if (context.CurrentBranch.FriendlyName != "master")
                {
                    var mergedHead = context.CurrentCommit;
                    var findMergeBase = context.Repository.ObjectDatabase.FindMergeBase(context.CurrentCommit, mainlineTip);
                    Logger.WriteInfo(string.Format("Current branch ({0}) was branch from {1}", context.CurrentBranch.FriendlyName, findMergeBase));

                    var branchIncrement = FindMessageIncrement(context, null, mergedHead, findMergeBase, directCommits);
                    // This will increment for any direct commits on master
                    mainlineVersion = IncrementForEachCommit(context, directCommits, mainlineVersion, "master");
                    mainlineVersion.BuildMetaData = metaDataCalculator.Create(findMergeBase, context);
                    // Don't increment if the merge commit is a merge into mainline
                    // this ensures PR's and forward merges end up correct.
                    if (mergedHead.Parents.Count() == 1 || mergedHead.Parents.First() != mainlineTip)
                    {
                        Logger.WriteInfo(string.Format("Performing {0} increment for current branch ", branchIncrement));
                        mainlineVersion = mainlineVersion.IncrementVersion(branchIncrement);
                    }
                }
                else
                {
                    // If we are on master, make sure no commits get left behind
                    mainlineVersion = IncrementForEachCommit(context, directCommits, mainlineVersion);
                    mainlineVersion.BuildMetaData = metaDataCalculator.Create(baseVersion.BaseVersionSource, context);
                }

                return mainlineVersion;
            }
        }

        SemanticVersion AggregateMergeCommitIncrement(GitVersionContext context, Commit commit, List<Commit> directCommits, SemanticVersion mainlineVersion)
        {
            // Merge commit, process all merged commits as a batch
            var mergeCommit = commit;
            var mergedHead = GetMergedHead(mergeCommit);
            var findMergeBase = context.Repository.ObjectDatabase.FindMergeBase(mergeCommit.Parents.First(), mergedHead);
            var findMessageIncrement = FindMessageIncrement(context, mergeCommit, mergedHead, findMergeBase, directCommits);

            // If this collection is not empty there has been some direct commits against master
            // Treat each commit as it's own 'release', we need to do this before we increment the branch
            mainlineVersion = IncrementForEachCommit(context, directCommits, mainlineVersion);
            directCommits.Clear();

            // Finally increment for the branch
            mainlineVersion = mainlineVersion.IncrementVersion(findMessageIncrement);
            Logger.WriteInfo(string.Format("Merge commit {0} incremented base versions {1}, now {2}",
                mergeCommit.Sha, findMessageIncrement, mainlineVersion));
            return mainlineVersion;
        }

        static Commit GetMainlineTip(GitVersionContext context)
        {
            var mainlineBranchConfigs = context.FullConfiguration.Branches.Where(b => b.Value.IsMainline == true).ToList();
            var seenMainlineTips = new List<string>();
            var mainlineBranches = context.Repository.Branches
                .Where(b =>
                {
                    return mainlineBranchConfigs.Any(c => Regex.IsMatch(b.FriendlyName, c.Key));
                })
                .Where(b =>
                {
                    if (seenMainlineTips.Contains(b.Tip.Sha))
                    {
                        Logger.WriteInfo("Multiple possible mainlines pointing at the same commit, dropping " + b.FriendlyName);
                        return false;
                    }
                    seenMainlineTips.Add(b.Tip.Sha);
                    return true;
                })
                .Select(b => new<commit_msg>switch GetMainlineTip from using branch config key to using the regex<commit_after>﻿using GitVersion.VersionCalculation.BaseVersionCalculators;
using LibGit2Sharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace GitVersion.VersionCalculation
{
    class MainlineVersionCalculator
    {
        IMetaDataCalculator metaDataCalculator;

        public MainlineVersionCalculator(IMetaDataCalculator metaDataCalculator)
        {
            this.metaDataCalculator = metaDataCalculator;
        }

        public SemanticVersion FindMainlineModeVersion(BaseVersion baseVersion, GitVersionContext context)
        {
            if (baseVersion.SemanticVersion.PreReleaseTag.HasTag())
            {
                throw new NotSupportedException("Mainline development mode doesn't yet support pre-release tags on master");
            }

            using (Logger.IndentLog("Using mainline development mode to calculate current version"))
            {
                var mainlineVersion = baseVersion.SemanticVersion;

                // Forward merge / PR
                //          * feature/foo
                //         / |
                // master *  *
                // 

                var mainlineTip = GetMainlineTip(context);
                var commitsNotOnMainline = context.Repository.Commits.QueryBy(new CommitFilter
                {
                    IncludeReachableFrom = context.CurrentBranch,
                    ExcludeReachableFrom = mainlineTip,
                    SortBy = CommitSortStrategies.Reverse,
                    FirstParentOnly = true
                }).Where(c => c.Sha != baseVersion.BaseVersionSource.Sha && c.Parents.Count() == 1).ToList();
                var commitLog = context.Repository.Commits.QueryBy(new CommitFilter
                {
                    IncludeReachableFrom = context.CurrentBranch,
                    ExcludeReachableFrom = baseVersion.BaseVersionSource,
                    SortBy = CommitSortStrategies.Reverse,
                    FirstParentOnly = true
                })
                .Where(c => c.Sha != baseVersion.BaseVersionSource.Sha)
                .Except(commitsNotOnMainline)
                .ToList();

                var directCommits = new List<Commit>();

                // Scans commit log in reverse, aggregating merge commits
                foreach (var commit in commitLog)
                {
                    directCommits.Add(commit);
                    if (commit.Parents.Count() > 1)
                    {
                        mainlineVersion = AggregateMergeCommitIncrement(context, commit, directCommits, mainlineVersion);
                    }
                }

                if (context.CurrentBranch.FriendlyName != "master")
                {
                    var mergedHead = context.CurrentCommit;
                    var findMergeBase = context.Repository.ObjectDatabase.FindMergeBase(context.CurrentCommit, mainlineTip);
                    Logger.WriteInfo(string.Format("Current branch ({0}) was branch from {1}", context.CurrentBranch.FriendlyName, findMergeBase));

                    var branchIncrement = FindMessageIncrement(context, null, mergedHead, findMergeBase, directCommits);
                    // This will increment for any direct commits on master
                    mainlineVersion = IncrementForEachCommit(context, directCommits, mainlineVersion, "master");
                    mainlineVersion.BuildMetaData = metaDataCalculator.Create(findMergeBase, context);
                    // Don't increment if the merge commit is a merge into mainline
                    // this ensures PR's and forward merges end up correct.
                    if (mergedHead.Parents.Count() == 1 || mergedHead.Parents.First() != mainlineTip)
                    {
                        Logger.WriteInfo(string.Format("Performing {0} increment for current branch ", branchIncrement));
                        mainlineVersion = mainlineVersion.IncrementVersion(branchIncrement);
                    }
                }
                else
                {
                    // If we are on master, make sure no commits get left behind
                    mainlineVersion = IncrementForEachCommit(context, directCommits, mainlineVersion);
                    mainlineVersion.BuildMetaData = metaDataCalculator.Create(baseVersion.BaseVersionSource, context);
                }

                return mainlineVersion;
            }
        }

        SemanticVersion AggregateMergeCommitIncrement(GitVersionContext context, Commit commit, List<Commit> directCommits, SemanticVersion mainlineVersion)
        {
            // Merge commit, process all merged commits as a batch
            var mergeCommit = commit;
            var mergedHead = GetMergedHead(mergeCommit);
            var findMergeBase = context.Repository.ObjectDatabase.FindMergeBase(mergeCommit.Parents.First(), mergedHead);
            var findMessageIncrement = FindMessageIncrement(context, mergeCommit, mergedHead, findMergeBase, directCommits);

            // If this collection is not empty there has been some direct commits against master
            // Treat each commit as it's own 'release', we need to do this before we increment the branch
            mainlineVersion = IncrementForEachCommit(context, directCommits, mainlineVersion);
            directCommits.Clear();

            // Finally increment for the branch
            mainlineVersion = mainlineVersion.IncrementVersion(findMessageIncrement);
            Logger.WriteInfo(string.Format("Merge commit {0} incremented base versions {1}, now {2}",
                mergeCommit.Sha, findMessageIncrement, mainlineVersion));
            return mainlineVersion;
        }

        static Commit GetMainlineTip(GitVersionContext context)
        {
            var mainlineBranchConfigs = context.FullConfiguration.Branches.Where(b => b.Value.IsMainline == true).ToList();
            var seenMainlineTips = new List<string>();
            var mainlineBranches = context.Repository.Branches
                .Where(b =>
                {
                    return mainlineBranchConfigs.Any(c => Regex.IsMatch(b.FriendlyName, c.Value.Regex));
                })
                .Where(b =>
                {
                    if (seenMainlineTips.Contains(b.Tip.Sha))
                    {
                        Logger.WriteInfo("Multiple possible mainlines pointing at the same commit, dropping " + b.FriendlyName);
                        return false;
                    }
                    seenMainlineTips.Add(b.Tip.Sha);
                    return true;
                })
                .Select(b => new
{'fraction_non_alphanumeric': 0.07237429336506992, 'fraction_numerical': 0.001487652484379649, 'mean_word_length': 1.824579831932773, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6541279', 'n_tokens_mistral': 3234, 'n_tokens_neox': 3029, 'n_words': 719}