1
- using GitVersion . VersionCalculation . BaseVersionCalculators ;
1
+ using GitVersion . VersionCalculation . BaseVersionCalculators ;
2
2
using LibGit2Sharp ;
3
3
using System ;
4
4
using System . Collections . Generic ;
@@ -33,67 +33,55 @@ public SemanticVersion FindMainlineModeVersion(BaseVersion baseVersion, GitVersi
33
33
// master * *
34
34
//
35
35
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 ) )
38
42
{
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
45
48
{
46
- IncludeReachableFrom = context . CurrentBranch ,
49
+ IncludeReachableFrom = mainlineTip ,
47
50
ExcludeReachableFrom = baseVersion . BaseVersionSource ,
48
51
SortBy = CommitSortStrategies . Reverse ,
49
52
FirstParentOnly = true
50
53
} )
51
- . Where ( c => c . Sha != baseVersion . BaseVersionSource . Sha )
52
- . Except ( commitsNotOnMainline )
53
54
. ToList ( ) ;
54
-
55
- var directCommits = new List < Commit > ( ) ;
55
+ var directCommits = new List < Commit > ( mainlineCommitLog . Count ) ;
56
56
57
57
// Scans commit log in reverse, aggregating merge commits
58
- foreach ( var commit in commitLog )
58
+ foreach ( var commit in mainlineCommitLog )
59
59
{
60
60
directCommits . Add ( commit ) ;
61
61
if ( commit . Parents . Count ( ) > 1 )
62
62
{
63
- mainlineVersion = AggregateMergeCommitIncrement ( context , commit , directCommits , mainlineVersion ) ;
63
+ mainlineVersion = AggregateMergeCommitIncrement ( context , commit , directCommits , mainlineVersion , mainline ) ;
64
64
}
65
65
}
66
66
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
67
72
if ( context . CurrentBranch . FriendlyName != "master" )
68
73
{
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 ) ;
90
78
}
91
79
92
80
return mainlineVersion ;
93
81
}
94
82
}
95
83
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 )
97
85
{
98
86
// Merge commit, process all merged commits as a batch
99
87
var mergeCommit = commit ;
@@ -103,7 +91,7 @@ SemanticVersion AggregateMergeCommitIncrement(GitVersionContext context, Commit
103
91
104
92
// If this collection is not empty there has been some direct commits against master
105
93
// 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 ) ;
107
95
directCommits . Clear ( ) ;
108
96
109
97
// Finally increment for the branch
@@ -113,7 +101,7 @@ SemanticVersion AggregateMergeCommitIncrement(GitVersionContext context, Commit
113
101
return mainlineVersion ;
114
102
}
115
103
116
- static Commit GetMainlineTip ( GitVersionContext context )
104
+ static Branch GetMainline ( GitVersionContext context )
117
105
{
118
106
var mainlineBranchConfigs = context . FullConfiguration . Branches . Where ( b => b . Value . IsMainline == true ) . ToList ( ) ;
119
107
var seenMainlineTips = new List < string > ( ) ;
@@ -152,25 +140,93 @@ static Commit GetMainlineTip(GitVersionContext context)
152
140
{
153
141
var mainlineBranch = possibleMainlineBranches [ 0 ] ;
154
142
Logger . WriteInfo ( "Mainline for current branch is " + mainlineBranch . FriendlyName ) ;
155
- return mainlineBranch . Tip ;
143
+ return mainlineBranch ;
156
144
}
157
145
158
146
var chosenMainline = possibleMainlineBranches [ 0 ] ;
159
147
Logger . WriteInfo ( string . Format (
160
148
"Multiple mainlines ({0}) have the same merge base for the current branch, choosing {1} because we found that branch first..." ,
161
149
string . Join ( ", " , possibleMainlineBranches . Select ( b => b . FriendlyName ) ) ,
162
150
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 ;
164
220
}
165
221
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 )
167
223
{
168
224
foreach ( var directCommit in directCommits )
169
225
{
170
226
var directCommitIncrement = IncrementStrategyFinder . GetIncrementForCommits ( context , new [ ]
171
227
{
172
228
directCommit
173
- } ) ?? IncrementStrategyFinder . FindDefaultIncrementForBranch ( context , branch ) ;
229
+ } ) ?? IncrementStrategyFinder . FindDefaultIncrementForBranch ( context , mainline . FriendlyName ) ;
174
230
mainlineVersion = mainlineVersion . IncrementVersion ( directCommitIncrement ) ;
175
231
Logger . WriteInfo ( string . Format ( "Direct commit on master {0} incremented base versions {1}, now {2}" ,
176
232
directCommit . Sha , directCommitIncrement , mainlineVersion ) ) ;
0 commit comments