Skip to content

Commit fd9a671

Browse files
committed
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
1 parent 59d50c3 commit fd9a671

File tree

1 file changed

+100
-44
lines changed

1 file changed

+100
-44
lines changed

src/GitVersionCore/VersionCalculation/MainlineVersionCalculator.cs

Lines changed: 100 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using GitVersion.VersionCalculation.BaseVersionCalculators;
1+
using GitVersion.VersionCalculation.BaseVersionCalculators;
22
using LibGit2Sharp;
33
using System;
44
using System.Collections.Generic;
@@ -33,67 +33,55 @@ public SemanticVersion FindMainlineModeVersion(BaseVersion baseVersion, GitVersi
3333
// master * *
3434
//
3535

36-
var mainlineTip = GetMainlineTip(context);
37-
var commitsNotOnMainline = context.Repository.Commits.QueryBy(new CommitFilter
36+
var mergeBase = baseVersion.BaseVersionSource;
37+
var mainline = GetMainline(context);
38+
var mainlineTip = mainline.Tip;
39+
40+
// when the current branch is not mainline, find the effective mainline tip for versioning the branch
41+
if (!context.CurrentBranch.IsSameBranch(mainline))
3842
{
39-
IncludeReachableFrom = context.CurrentBranch,
40-
ExcludeReachableFrom = mainlineTip,
41-
SortBy = CommitSortStrategies.Reverse,
42-
FirstParentOnly = true
43-
}).Where(c => c.Sha != baseVersion.BaseVersionSource.Sha && c.Parents.Count() == 1).ToList();
44-
var commitLog = context.Repository.Commits.QueryBy(new CommitFilter
43+
mergeBase = FindMergeBaseBeforeForwardMerge(context, baseVersion.BaseVersionSource, mainline, out mainlineTip);
44+
Logger.WriteInfo(string.Format("Current branch ({0}) was branch from {1}", context.CurrentBranch.FriendlyName, mergeBase));
45+
}
46+
47+
var mainlineCommitLog = context.Repository.Commits.QueryBy(new CommitFilter
4548
{
46-
IncludeReachableFrom = context.CurrentBranch,
49+
IncludeReachableFrom = mainlineTip,
4750
ExcludeReachableFrom = baseVersion.BaseVersionSource,
4851
SortBy = CommitSortStrategies.Reverse,
4952
FirstParentOnly = true
5053
})
51-
.Where(c => c.Sha != baseVersion.BaseVersionSource.Sha)
52-
.Except(commitsNotOnMainline)
5354
.ToList();
54-
55-
var directCommits = new List<Commit>();
55+
var directCommits = new List<Commit>(mainlineCommitLog.Count);
5656

5757
// Scans commit log in reverse, aggregating merge commits
58-
foreach (var commit in commitLog)
58+
foreach (var commit in mainlineCommitLog)
5959
{
6060
directCommits.Add(commit);
6161
if (commit.Parents.Count() > 1)
6262
{
63-
mainlineVersion = AggregateMergeCommitIncrement(context, commit, directCommits, mainlineVersion);
63+
mainlineVersion = AggregateMergeCommitIncrement(context, commit, directCommits, mainlineVersion, mainline);
6464
}
6565
}
6666

67+
// This will increment for any direct commits on mainline
68+
mainlineVersion = IncrementForEachCommit(context, directCommits, mainlineVersion, mainline);
69+
mainlineVersion.BuildMetaData = metaDataCalculator.Create(mergeBase, context);
70+
71+
// branches other than master always get a bump for the act of branching
6772
if (context.CurrentBranch.FriendlyName != "master")
6873
{
69-
var mergedHead = context.CurrentCommit;
70-
var findMergeBase = context.Repository.ObjectDatabase.FindMergeBase(context.CurrentCommit, mainlineTip);
71-
Logger.WriteInfo(string.Format("Current branch ({0}) was branch from {1}", context.CurrentBranch.FriendlyName, findMergeBase));
72-
73-
var branchIncrement = FindMessageIncrement(context, null, mergedHead, findMergeBase, directCommits);
74-
// This will increment for any direct commits on master
75-
mainlineVersion = IncrementForEachCommit(context, directCommits, mainlineVersion, "master");
76-
mainlineVersion.BuildMetaData = metaDataCalculator.Create(findMergeBase, context);
77-
// Don't increment if the merge commit is a merge into mainline
78-
// this ensures PR's and forward merges end up correct.
79-
if (mergedHead.Parents.Count() == 1 || mergedHead.Parents.First() != mainlineTip)
80-
{
81-
Logger.WriteInfo(string.Format("Performing {0} increment for current branch ", branchIncrement));
82-
mainlineVersion = mainlineVersion.IncrementVersion(branchIncrement);
83-
}
84-
}
85-
else
86-
{
87-
// If we are on master, make sure no commits get left behind
88-
mainlineVersion = IncrementForEachCommit(context, directCommits, mainlineVersion);
89-
mainlineVersion.BuildMetaData = metaDataCalculator.Create(baseVersion.BaseVersionSource, context);
74+
var branchIncrement = FindMessageIncrement(context, null, context.CurrentCommit, mergeBase, mainlineCommitLog);
75+
Logger.WriteInfo(string.Format("Performing {0} increment for current branch ", branchIncrement));
76+
77+
mainlineVersion = mainlineVersion.IncrementVersion(branchIncrement);
9078
}
9179

9280
return mainlineVersion;
9381
}
9482
}
9583

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

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

10997
// Finally increment for the branch
@@ -113,7 +101,7 @@ SemanticVersion AggregateMergeCommitIncrement(GitVersionContext context, Commit
113101
return mainlineVersion;
114102
}
115103

116-
static Commit GetMainlineTip(GitVersionContext context)
104+
static Branch GetMainline(GitVersionContext context)
117105
{
118106
var mainlineBranchConfigs = context.FullConfiguration.Branches.Where(b => b.Value.IsMainline == true).ToList();
119107
var seenMainlineTips = new List<string>();
@@ -152,25 +140,93 @@ static Commit GetMainlineTip(GitVersionContext context)
152140
{
153141
var mainlineBranch = possibleMainlineBranches[0];
154142
Logger.WriteInfo("Mainline for current branch is " + mainlineBranch.FriendlyName);
155-
return mainlineBranch.Tip;
143+
return mainlineBranch;
156144
}
157145

158146
var chosenMainline = possibleMainlineBranches[0];
159147
Logger.WriteInfo(string.Format(
160148
"Multiple mainlines ({0}) have the same merge base for the current branch, choosing {1} because we found that branch first...",
161149
string.Join(", ", possibleMainlineBranches.Select(b => b.FriendlyName)),
162150
chosenMainline.FriendlyName));
163-
return chosenMainline.Tip;
151+
return chosenMainline;
152+
}
153+
154+
/// <summary>
155+
/// Gets the commit on mainline at which <paramref name="mergeBase"/> was fully integrated.
156+
/// </summary>
157+
/// <param name="mainlineCommitLog">The collection of commits made directly to mainline, in reverse order.</param>
158+
/// <param name="mergeBase">The best possible merge base between <paramref name="mainlineTip"/> and the current commit.</param>
159+
/// <param name="mainlineTip">The tip of the mainline branch.</param>
160+
/// <returns>The commit on mainline at which <paramref name="mergeBase"/> was merged, if such a commit exists; otherwise, <paramref name="mainlineTip"/>.</returns>
161+
/// <remarks>
162+
/// This method gets the most recent commit on mainline that should be considered for versioning the current branch.
163+
/// </remarks>
164+
private static Commit GetEffectiveMainlineTip(IEnumerable<Commit> mainlineCommitLog, Commit mergeBase, Commit mainlineTip)
165+
{
166+
// find the commit that merged mergeBase into mainline
167+
foreach (var commit in mainlineCommitLog)
168+
{
169+
if (commit == mergeBase || commit.Parents.Contains(mergeBase))
170+
{
171+
Logger.WriteInfo(string.Format("Found branch merge point; choosing {0} as effective mainline tip", commit));
172+
return commit;
173+
}
174+
}
175+
176+
return mainlineTip;
177+
}
178+
179+
/// <summary>
180+
/// Gets the best possible merge base between the current commit and <paramref name="mainline"/> that is not the child of a forward merge.
181+
/// </summary>
182+
/// <param name="context">The current versioning context.</param>
183+
/// <param name="baseVersionSource">The commit that establishes the contextual base version.</param>
184+
/// <param name="mainline">The mainline branch.</param>
185+
/// <param name="mainlineTip">The commit on mainline at which the returned merge base was fully integrated.</param>
186+
/// <returns>The best possible merge base between the current commit and <paramref name="mainline"/> that is not the child of a forward merge.</returns>
187+
private static Commit FindMergeBaseBeforeForwardMerge(GitVersionContext context, Commit baseVersionSource, Branch mainline, out Commit mainlineTip)
188+
{
189+
var mergeBase = context.Repository.ObjectDatabase.FindMergeBase(context.CurrentCommit, mainline.Tip);
190+
var mainlineCommitLog = context.Repository.Commits
191+
.QueryBy(new CommitFilter
192+
{
193+
IncludeReachableFrom = mainline.Tip,
194+
ExcludeReachableFrom = baseVersionSource,
195+
SortBy = CommitSortStrategies.Reverse,
196+
FirstParentOnly = true
197+
})
198+
.ToList();
199+
200+
// find the mainline commit effective for versioning the current branch
201+
mainlineTip = GetEffectiveMainlineTip(mainlineCommitLog, mergeBase, mainline.Tip);
202+
203+
// detect forward merge and rewind mainlineTip to before it
204+
if (mergeBase == context.CurrentCommit && !mainlineCommitLog.Contains(mergeBase))
205+
{
206+
var mainlineTipPrevious = mainlineTip.Parents.First();
207+
var message = string.Format(
208+
"Detected forward merge at {0}; rewinding mainline to previous commit {1}",
209+
mainlineTip,
210+
mainlineTipPrevious);
211+
212+
Logger.WriteInfo(message);
213+
214+
// re-do mergeBase detection before the forward merge
215+
mergeBase = context.Repository.ObjectDatabase.FindMergeBase(context.CurrentCommit, mainlineTipPrevious);
216+
mainlineTip = GetEffectiveMainlineTip(mainlineCommitLog, mergeBase, mainlineTipPrevious);
217+
}
218+
219+
return mergeBase;
164220
}
165221

166-
private static SemanticVersion IncrementForEachCommit(GitVersionContext context, List<Commit> directCommits, SemanticVersion mainlineVersion, string branch = null)
222+
private static SemanticVersion IncrementForEachCommit(GitVersionContext context, List<Commit> directCommits, SemanticVersion mainlineVersion, Branch mainline)
167223
{
168224
foreach (var directCommit in directCommits)
169225
{
170226
var directCommitIncrement = IncrementStrategyFinder.GetIncrementForCommits(context, new[]
171227
{
172228
directCommit
173-
}) ?? IncrementStrategyFinder.FindDefaultIncrementForBranch(context, branch);
229+
}) ?? IncrementStrategyFinder.FindDefaultIncrementForBranch(context, mainline.FriendlyName);
174230
mainlineVersion = mainlineVersion.IncrementVersion(directCommitIncrement);
175231
Logger.WriteInfo(string.Format("Direct commit on master {0} incremented base versions {1}, now {2}",
176232
directCommit.Sha, directCommitIncrement, mainlineVersion));

0 commit comments

Comments
 (0)