Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Always compute mainlineVersion w/ mainline commits
Query the mainline commit log starting from mainline tip regardless of
the current branch. Stop considering mainline commits after the merge
root for the current branch (or, if the current branch has been merged
to mainline but has new commmits, the last commit that merged the
current branch into mainline).

fixes #1035
  • Loading branch information
wjrogers committed Nov 26, 2018
commit fd9a671a03e72daaa3a58d799b6c977cb41e28cd
144 changes: 100 additions & 44 deletions src/GitVersionCore/VersionCalculation/MainlineVersionCalculator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using GitVersion.VersionCalculation.BaseVersionCalculators;
using GitVersion.VersionCalculation.BaseVersionCalculators;
using LibGit2Sharp;
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -33,67 +33,55 @@ public SemanticVersion FindMainlineModeVersion(BaseVersion baseVersion, GitVersi
// master * *
//

var mainlineTip = GetMainlineTip(context);
var commitsNotOnMainline = context.Repository.Commits.QueryBy(new CommitFilter
var mergeBase = baseVersion.BaseVersionSource;
var mainline = GetMainline(context);
var mainlineTip = mainline.Tip;

// when the current branch is not mainline, find the effective mainline tip for versioning the branch
if (!context.CurrentBranch.IsSameBranch(mainline))
{
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
mergeBase = FindMergeBaseBeforeForwardMerge(context, baseVersion.BaseVersionSource, mainline, out mainlineTip);
Logger.WriteInfo(string.Format("Current branch ({0}) was branch from {1}", context.CurrentBranch.FriendlyName, mergeBase));
}

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

var directCommits = new List<Commit>();
var directCommits = new List<Commit>(mainlineCommitLog.Count);

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

// This will increment for any direct commits on mainline
mainlineVersion = IncrementForEachCommit(context, directCommits, mainlineVersion, mainline);
mainlineVersion.BuildMetaData = metaDataCalculator.Create(mergeBase, context);

// branches other than master always get a bump for the act of branching
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);
var branchIncrement = FindMessageIncrement(context, null, context.CurrentCommit, mergeBase, mainlineCommitLog);
Logger.WriteInfo(string.Format("Performing {0} increment for current branch ", branchIncrement));

mainlineVersion = mainlineVersion.IncrementVersion(branchIncrement);
}

return mainlineVersion;
}
}

SemanticVersion AggregateMergeCommitIncrement(GitVersionContext context, Commit commit, List<Commit> directCommits, SemanticVersion mainlineVersion)
SemanticVersion AggregateMergeCommitIncrement(GitVersionContext context, Commit commit, List<Commit> directCommits, SemanticVersion mainlineVersion, Branch mainline)
{
// Merge commit, process all merged commits as a batch
var mergeCommit = commit;
Expand All @@ -103,7 +91,7 @@ SemanticVersion AggregateMergeCommitIncrement(GitVersionContext context, Commit

// 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);
mainlineVersion = IncrementForEachCommit(context, directCommits, mainlineVersion, mainline);
directCommits.Clear();

// Finally increment for the branch
Expand All @@ -113,7 +101,7 @@ SemanticVersion AggregateMergeCommitIncrement(GitVersionContext context, Commit
return mainlineVersion;
}

static Commit GetMainlineTip(GitVersionContext context)
static Branch GetMainline(GitVersionContext context)
{
var mainlineBranchConfigs = context.FullConfiguration.Branches.Where(b => b.Value.IsMainline == true).ToList();
var seenMainlineTips = new List<string>();
Expand Down Expand Up @@ -152,25 +140,93 @@ static Commit GetMainlineTip(GitVersionContext context)
{
var mainlineBranch = possibleMainlineBranches[0];
Logger.WriteInfo("Mainline for current branch is " + mainlineBranch.FriendlyName);
return mainlineBranch.Tip;
return mainlineBranch;
}

var chosenMainline = possibleMainlineBranches[0];
Logger.WriteInfo(string.Format(
"Multiple mainlines ({0}) have the same merge base for the current branch, choosing {1} because we found that branch first...",
string.Join(", ", possibleMainlineBranches.Select(b => b.FriendlyName)),
chosenMainline.FriendlyName));
return chosenMainline.Tip;
return chosenMainline;
}

/// <summary>
/// Gets the commit on mainline at which <paramref name="mergeBase"/> was fully integrated.
/// </summary>
/// <param name="mainlineCommitLog">The collection of commits made directly to mainline, in reverse order.</param>
/// <param name="mergeBase">The best possible merge base between <paramref name="mainlineTip"/> and the current commit.</param>
/// <param name="mainlineTip">The tip of the mainline branch.</param>
/// <returns>The commit on mainline at which <paramref name="mergeBase"/> was merged, if such a commit exists; otherwise, <paramref name="mainlineTip"/>.</returns>
/// <remarks>
/// This method gets the most recent commit on mainline that should be considered for versioning the current branch.
/// </remarks>
private static Commit GetEffectiveMainlineTip(IEnumerable<Commit> mainlineCommitLog, Commit mergeBase, Commit mainlineTip)
{
// find the commit that merged mergeBase into mainline
foreach (var commit in mainlineCommitLog)
{
if (commit == mergeBase || commit.Parents.Contains(mergeBase))
{
Logger.WriteInfo(string.Format("Found branch merge point; choosing {0} as effective mainline tip", commit));
return commit;
}
}

return mainlineTip;
}

/// <summary>
/// Gets the best possible merge base between the current commit and <paramref name="mainline"/> that is not the child of a forward merge.
/// </summary>
/// <param name="context">The current versioning context.</param>
/// <param name="baseVersionSource">The commit that establishes the contextual base version.</param>
/// <param name="mainline">The mainline branch.</param>
/// <param name="mainlineTip">The commit on mainline at which the returned merge base was fully integrated.</param>
/// <returns>The best possible merge base between the current commit and <paramref name="mainline"/> that is not the child of a forward merge.</returns>
private static Commit FindMergeBaseBeforeForwardMerge(GitVersionContext context, Commit baseVersionSource, Branch mainline, out Commit mainlineTip)
{
var mergeBase = context.Repository.ObjectDatabase.FindMergeBase(context.CurrentCommit, mainline.Tip);
var mainlineCommitLog = context.Repository.Commits
.QueryBy(new CommitFilter
{
IncludeReachableFrom = mainline.Tip,
ExcludeReachableFrom = baseVersionSource,
SortBy = CommitSortStrategies.Reverse,
FirstParentOnly = true
})
.ToList();

// find the mainline commit effective for versioning the current branch
mainlineTip = GetEffectiveMainlineTip(mainlineCommitLog, mergeBase, mainline.Tip);

// detect forward merge and rewind mainlineTip to before it
if (mergeBase == context.CurrentCommit && !mainlineCommitLog.Contains(mergeBase))
{
var mainlineTipPrevious = mainlineTip.Parents.First();
var message = string.Format(
"Detected forward merge at {0}; rewinding mainline to previous commit {1}",
mainlineTip,
mainlineTipPrevious);

Logger.WriteInfo(message);

// re-do mergeBase detection before the forward merge
mergeBase = context.Repository.ObjectDatabase.FindMergeBase(context.CurrentCommit, mainlineTipPrevious);
mainlineTip = GetEffectiveMainlineTip(mainlineCommitLog, mergeBase, mainlineTipPrevious);
}

return mergeBase;
}

private static SemanticVersion IncrementForEachCommit(GitVersionContext context, List<Commit> directCommits, SemanticVersion mainlineVersion, string branch = null)
private static SemanticVersion IncrementForEachCommit(GitVersionContext context, List<Commit> directCommits, SemanticVersion mainlineVersion, Branch mainline)
{
foreach (var directCommit in directCommits)
{
var directCommitIncrement = IncrementStrategyFinder.GetIncrementForCommits(context, new[]
{
directCommit
}) ?? IncrementStrategyFinder.FindDefaultIncrementForBranch(context, branch);
}) ?? IncrementStrategyFinder.FindDefaultIncrementForBranch(context, mainline.FriendlyName);
mainlineVersion = mainlineVersion.IncrementVersion(directCommitIncrement);
Logger.WriteInfo(string.Format("Direct commit on master {0} incremented base versions {1}, now {2}",
directCommit.Sha, directCommitIncrement, mainlineVersion));
Expand Down