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 , baseVersion . BaseVersionSource ) ;
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,25 +101,14 @@ 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 , Commit baseVersionSource )
117
105
{
118
106
var mainlineBranchConfigs = context . FullConfiguration . Branches . Where ( b => b . Value . IsMainline == true ) . ToList ( ) ;
119
- var seenMainlineTips = new List < string > ( ) ;
120
107
var mainlineBranches = context . Repository . Branches
121
108
. Where ( b =>
122
109
{
123
110
return mainlineBranchConfigs . Any ( c => Regex . IsMatch ( b . FriendlyName , c . Value . Regex ) ) ;
124
111
} )
125
- . Where ( b =>
126
- {
127
- if ( seenMainlineTips . Contains ( b . Tip . Sha ) )
128
- {
129
- Logger . WriteInfo ( "Multiple possible mainlines pointing at the same commit, dropping " + b . FriendlyName ) ;
130
- return false ;
131
- }
132
- seenMainlineTips . Add ( b . Tip . Sha ) ;
133
- return true ;
134
- } )
135
112
. Select ( b => new
136
113
{
137
114
MergeBase = context . Repository . ObjectDatabase . FindMergeBase ( b . Tip , context . CurrentCommit ) ,
@@ -152,25 +129,125 @@ static Commit GetMainlineTip(GitVersionContext context)
152
129
{
153
130
var mainlineBranch = possibleMainlineBranches [ 0 ] ;
154
131
Logger . WriteInfo ( "Mainline for current branch is " + mainlineBranch . FriendlyName ) ;
155
- return mainlineBranch . Tip ;
132
+ return mainlineBranch ;
133
+ }
134
+
135
+ // prefer current branch, if it is a mainline branch
136
+ if ( possibleMainlineBranches . Any ( context . CurrentBranch . IsSameBranch ) )
137
+ {
138
+ Logger . WriteInfo ( string . Format ( "Choosing {0} as mainline because it is the current branch" , context . CurrentBranch . FriendlyName ) ) ;
139
+ return context . CurrentBranch ;
140
+ }
141
+
142
+ // prefer a branch on which the merge base was a direct commit, if there is such a branch
143
+ var firstMatchingCommitBranch = possibleMainlineBranches
144
+ . FirstOrDefault ( b =>
145
+ {
146
+ var filter = new CommitFilter
147
+ {
148
+ IncludeReachableFrom = b ,
149
+ ExcludeReachableFrom = baseVersionSource ,
150
+ FirstParentOnly = true ,
151
+ } ;
152
+ var query = context . Repository . Commits . QueryBy ( filter ) ;
153
+
154
+ return query . Contains ( firstMatchingCommit ) ;
155
+ } ) ;
156
+ if ( firstMatchingCommitBranch != null )
157
+ {
158
+ var message = string . Format (
159
+ "Choosing {0} as mainline because {1}'s merge base was a direct commit to {0}" ,
160
+ firstMatchingCommitBranch . FriendlyName ,
161
+ context . CurrentBranch . FriendlyName ) ;
162
+ Logger . WriteInfo ( message ) ;
163
+
164
+ return firstMatchingCommitBranch ;
156
165
}
157
166
158
167
var chosenMainline = possibleMainlineBranches [ 0 ] ;
159
168
Logger . WriteInfo ( string . Format (
160
169
"Multiple mainlines ({0}) have the same merge base for the current branch, choosing {1} because we found that branch first..." ,
161
170
string . Join ( ", " , possibleMainlineBranches . Select ( b => b . FriendlyName ) ) ,
162
171
chosenMainline . FriendlyName ) ) ;
163
- return chosenMainline . Tip ;
172
+ return chosenMainline ;
173
+ }
174
+
175
+ /// <summary>
176
+ /// Gets the commit on mainline at which <paramref name="mergeBase"/> was fully integrated.
177
+ /// </summary>
178
+ /// <param name="mainlineCommitLog">The collection of commits made directly to mainline, in reverse order.</param>
179
+ /// <param name="mergeBase">The best possible merge base between <paramref name="mainlineTip"/> and the current commit.</param>
180
+ /// <param name="mainlineTip">The tip of the mainline branch.</param>
181
+ /// <returns>The commit on mainline at which <paramref name="mergeBase"/> was merged, if such a commit exists; otherwise, <paramref name="mainlineTip"/>.</returns>
182
+ /// <remarks>
183
+ /// This method gets the most recent commit on mainline that should be considered for versioning the current branch.
184
+ /// </remarks>
185
+ private static Commit GetEffectiveMainlineTip ( IEnumerable < Commit > mainlineCommitLog , Commit mergeBase , Commit mainlineTip )
186
+ {
187
+ // find the commit that merged mergeBase into mainline
188
+ foreach ( var commit in mainlineCommitLog )
189
+ {
190
+ if ( commit == mergeBase || commit . Parents . Contains ( mergeBase ) )
191
+ {
192
+ Logger . WriteInfo ( string . Format ( "Found branch merge point; choosing {0} as effective mainline tip" , commit ) ) ;
193
+ return commit ;
194
+ }
195
+ }
196
+
197
+ return mainlineTip ;
198
+ }
199
+
200
+ /// <summary>
201
+ /// Gets the best possible merge base between the current commit and <paramref name="mainline"/> that is not the child of a forward merge.
202
+ /// </summary>
203
+ /// <param name="context">The current versioning context.</param>
204
+ /// <param name="baseVersionSource">The commit that establishes the contextual base version.</param>
205
+ /// <param name="mainline">The mainline branch.</param>
206
+ /// <param name="mainlineTip">The commit on mainline at which the returned merge base was fully integrated.</param>
207
+ /// <returns>The best possible merge base between the current commit and <paramref name="mainline"/> that is not the child of a forward merge.</returns>
208
+ private static Commit FindMergeBaseBeforeForwardMerge ( GitVersionContext context , Commit baseVersionSource , Branch mainline , out Commit mainlineTip )
209
+ {
210
+ var mergeBase = context . Repository . ObjectDatabase . FindMergeBase ( context . CurrentCommit , mainline . Tip ) ;
211
+ var mainlineCommitLog = context . Repository . Commits
212
+ . QueryBy ( new CommitFilter
213
+ {
214
+ IncludeReachableFrom = mainline . Tip ,
215
+ ExcludeReachableFrom = baseVersionSource ,
216
+ SortBy = CommitSortStrategies . Reverse ,
217
+ FirstParentOnly = true
218
+ } )
219
+ . ToList ( ) ;
220
+
221
+ // find the mainline commit effective for versioning the current branch
222
+ mainlineTip = GetEffectiveMainlineTip ( mainlineCommitLog , mergeBase , mainline . Tip ) ;
223
+
224
+ // detect forward merge and rewind mainlineTip to before it
225
+ if ( mergeBase == context . CurrentCommit && ! mainlineCommitLog . Contains ( mergeBase ) )
226
+ {
227
+ var mainlineTipPrevious = mainlineTip . Parents . First ( ) ;
228
+ var message = string . Format (
229
+ "Detected forward merge at {0}; rewinding mainline to previous commit {1}" ,
230
+ mainlineTip ,
231
+ mainlineTipPrevious ) ;
232
+
233
+ Logger . WriteInfo ( message ) ;
234
+
235
+ // re-do mergeBase detection before the forward merge
236
+ mergeBase = context . Repository . ObjectDatabase . FindMergeBase ( context . CurrentCommit , mainlineTipPrevious ) ;
237
+ mainlineTip = GetEffectiveMainlineTip ( mainlineCommitLog , mergeBase , mainlineTipPrevious ) ;
238
+ }
239
+
240
+ return mergeBase ;
164
241
}
165
242
166
- private static SemanticVersion IncrementForEachCommit ( GitVersionContext context , List < Commit > directCommits , SemanticVersion mainlineVersion , string branch = null )
243
+ private static SemanticVersion IncrementForEachCommit ( GitVersionContext context , List < Commit > directCommits , SemanticVersion mainlineVersion , Branch mainline )
167
244
{
168
245
foreach ( var directCommit in directCommits )
169
246
{
170
247
var directCommitIncrement = IncrementStrategyFinder . GetIncrementForCommits ( context , new [ ]
171
248
{
172
249
directCommit
173
- } ) ?? IncrementStrategyFinder . FindDefaultIncrementForBranch ( context , branch ) ;
250
+ } ) ?? IncrementStrategyFinder . FindDefaultIncrementForBranch ( context , mainline . FriendlyName ) ;
174
251
mainlineVersion = mainlineVersion . IncrementVersion ( directCommitIncrement ) ;
175
252
Logger . WriteInfo ( string . Format ( "Direct commit on master {0} incremented base versions {1}, now {2}" ,
176
253
directCommit . Sha , directCommitIncrement , mainlineVersion ) ) ;
0 commit comments