@@ -31,6 +31,8 @@ type CreateFixPullRequestsCmd struct {
31
31
projectWorkingDir string
32
32
// The git client the command performs git operations with
33
33
gitManager * utils.GitManager
34
+ // Determines whether to open a pull request for each vulnerability fix or to aggregate all fixes into one pull request.
35
+ aggregateFixes bool
34
36
}
35
37
36
38
func (cfp * CreateFixPullRequestsCmd ) Run (configAggregator utils.FrogbotConfigAggregator , client vcsclient.VcsClient ) error {
@@ -63,6 +65,7 @@ func (cfp *CreateFixPullRequestsCmd) scanAndFixRepository(repository *utils.Frog
63
65
}
64
66
for _ , project := range repository .Projects {
65
67
cfp .details .Project = project
68
+ cfp .aggregateFixes = repository .Git .AggregateFixes
66
69
projectFullPathWorkingDirs := getFullPathWorkingDirs (project .WorkingDirs , baseWd )
67
70
for _ , fullPathWd := range projectFullPathWorkingDirs {
68
71
scanResults , isMultipleRoots , err := cfp .scan (cfp .details , fullPathWd )
@@ -134,9 +137,17 @@ func (cfp *CreateFixPullRequestsCmd) fixVulnerablePackages(fixVersionsMap map[st
134
137
}
135
138
}()
136
139
137
- // Fix all impacted packages
140
+ if cfp .aggregateFixes {
141
+ err = cfp .fixIssuesSinglePR (fixVersionsMap )
142
+ } else {
143
+ err = cfp .fixIssuesSeparatePRs (fixVersionsMap )
144
+ }
145
+ return
146
+ }
147
+
148
+ func (cfp * CreateFixPullRequestsCmd ) fixIssuesSeparatePRs (fixVersionsMap map [string ]* utils.FixVersionInfo ) (err error ) {
138
149
for impactedPackage , fixVersionInfo := range fixVersionsMap {
139
- if err = cfp .fixSinglePackage (impactedPackage , fixVersionInfo ); err != nil {
150
+ if err = cfp .fixSinglePackageAndCreatePR (impactedPackage , fixVersionInfo ); err != nil {
140
151
log .Warn (err )
141
152
}
142
153
// After finishing to work on the current vulnerability, we go back to the base branch to start the next vulnerability fix
@@ -148,10 +159,41 @@ func (cfp *CreateFixPullRequestsCmd) fixVulnerablePackages(fixVersionsMap map[st
148
159
return
149
160
}
150
161
151
- func (cfp * CreateFixPullRequestsCmd ) fixSinglePackage (impactedPackage string , fixVersionInfo * utils.FixVersionInfo ) (err error ) {
162
+ func (cfp * CreateFixPullRequestsCmd ) fixIssuesSinglePR (fixVersionsMap map [string ]* utils.FixVersionInfo ) (err error ) {
163
+ successfullyFixedPackages := make (map [string ]* utils.FixVersionInfo )
164
+ log .Info ("-----------------------------------------------------------------" )
165
+ log .Info ("Start aggregated packages fix" )
166
+ aggregatedFixBranchName , err := cfp .gitManager .GenerateAggregatedFixBranchName (fixVersionsMap )
167
+ if err != nil {
168
+ return
169
+ }
170
+ if err = cfp .createFixBranch (aggregatedFixBranchName , false ); err != nil {
171
+ return
172
+ }
173
+ // Fix all packages in the same branch
174
+ for impactedPackage , fixVersionInfo := range fixVersionsMap {
175
+ if err = cfp .updatePackageToFixedVersion (impactedPackage , fixVersionInfo ); err != nil {
176
+ log .Error ("Could not fix impacted package" , impactedPackage , "as part of the PR. Skipping it. Cause:" , err .Error ())
177
+ } else {
178
+ log .Info ("Successfully fixed" , impactedPackage )
179
+ successfullyFixedPackages [impactedPackage ] = fixVersionInfo
180
+ }
181
+ }
182
+
183
+ if err = cfp .openAggregatedPullRequest (aggregatedFixBranchName , successfullyFixedPackages ); err != nil {
184
+ return fmt .Errorf ("failed while creating aggreagted pull request. Error: \n %s" , err .Error ())
185
+ }
186
+ return
187
+ }
188
+
189
+ func (cfp * CreateFixPullRequestsCmd ) fixSinglePackageAndCreatePR (impactedPackage string , fixVersionInfo * utils.FixVersionInfo ) (err error ) {
152
190
log .Info ("-----------------------------------------------------------------" )
153
191
log .Info ("Start fixing" , impactedPackage , "with" , fixVersionInfo .FixVersion )
154
- fixBranchName , err := cfp .createFixingBranch (impactedPackage , fixVersionInfo )
192
+ fixBranchName , err := cfp .gitManager .GenerateFixBranchName (cfp .details .Branch , impactedPackage , fixVersionInfo .FixVersion )
193
+ if err != nil {
194
+ return
195
+ }
196
+ err = cfp .createFixBranch (fixBranchName , true )
155
197
if err != nil {
156
198
return fmt .Errorf ("failed while creating new branch: \n %s" , err .Error ())
157
199
}
@@ -179,14 +221,12 @@ func (cfp *CreateFixPullRequestsCmd) openFixingPullRequest(impactedPackage, fixB
179
221
180
222
commitMessage := cfp .gitManager .GenerateCommitMessage (impactedPackage , fixVersionInfo .FixVersion )
181
223
log .Info ("Running git add all and commit..." )
182
- err = cfp .gitManager .AddAllAndCommit (commitMessage )
183
- if err != nil {
224
+ if err = cfp .gitManager .AddAllAndCommit (commitMessage ); err != nil {
184
225
return
185
226
}
186
227
187
- log .Info ("Pushing fix branch:" , fixBranchName , "..." )
188
- err = cfp .gitManager .Push ()
189
- if err != nil {
228
+ log .Info ("Pushing branch:" , fixBranchName , "..." )
229
+ if err = cfp .gitManager .Push (false ); err != nil {
190
230
return
191
231
}
192
232
@@ -196,22 +236,49 @@ func (cfp *CreateFixPullRequestsCmd) openFixingPullRequest(impactedPackage, fixB
196
236
return cfp .details .Client .CreatePullRequest (context .Background (), cfp .details .RepoOwner , cfp .details .RepoName , fixBranchName , cfp .details .Branch , pullRequestTitle , prBody )
197
237
}
198
238
199
- func (cfp * CreateFixPullRequestsCmd ) createFixingBranch (impactedPackage string , fixVersionInfo * utils.FixVersionInfo ) (fixBranchName string , err error ) {
200
- fixBranchName , err = cfp .gitManager .GenerateFixBranchName (cfp .details .Branch , impactedPackage , fixVersionInfo .FixVersion )
239
+ // When aggregate mode is active, there can be only one updated pull request to contain all the available fixes.
240
+ // In case of an already opened pull request, Frogbot will only update the branch.
241
+ func (cfp * CreateFixPullRequestsCmd ) openAggregatedPullRequest (fixBranchName string , versionsMap map [string ]* utils.FixVersionInfo ) (err error ) {
242
+ log .Info ("Checking if there are changes to commit" )
243
+ isClean , err := cfp .gitManager .IsClean ()
201
244
if err != nil {
202
245
return
203
246
}
247
+ if isClean {
248
+ return fmt .Errorf ("there were no changes in the fix branch '%s'" , fixBranchName )
249
+ }
250
+ commitMessage := cfp .gitManager .GenerateAggregatedCommitMessage ()
251
+ log .Info ("Running git add all and commit..." )
252
+ if err = cfp .gitManager .AddAllAndCommit (commitMessage ); err != nil {
253
+ return
254
+ }
255
+ exists , err := cfp .gitManager .BranchExistsInRemote (fixBranchName )
256
+ if err != nil {
257
+ return
258
+ }
259
+ log .Info ("Pushing branch:" , fixBranchName , "..." )
260
+ if err = cfp .gitManager .Push (true ); err != nil {
261
+ return
262
+ }
263
+ if ! exists {
264
+ log .Info ("Creating Pull Request form:" , fixBranchName , " to:" , cfp .details .Branch )
265
+ prBody := commitMessage + "\n \n " + utils .WhatIsFrogbotMd
266
+ return cfp .details .Client .CreatePullRequest (context .Background (), cfp .details .RepoOwner , cfp .details .RepoName , fixBranchName , cfp .details .Branch , utils .AggregatedPullRequestTitleTemplate , prBody )
267
+ }
268
+ log .Info ("Pull Request branch:" , fixBranchName , "has been updated" )
269
+ return
270
+ }
204
271
272
+ func (cfp * CreateFixPullRequestsCmd ) createFixBranch (fixBranchName string , failOnExists bool ) (err error ) {
205
273
exists , err := cfp .gitManager .BranchExistsInRemote (fixBranchName )
206
274
if err != nil {
207
275
return
208
276
}
209
277
log .Info ("Creating branch" , fixBranchName , "..." )
210
- if exists {
211
- return "" , fmt .Errorf ("branch %s already exists in the remote git repository" , fixBranchName )
278
+ if failOnExists && exists {
279
+ return fmt .Errorf ("branch %s already exists in the remote git repository" , fixBranchName )
212
280
}
213
-
214
- return fixBranchName , cfp .gitManager .CreateBranchAndCheckout (fixBranchName )
281
+ return cfp .gitManager .CreateBranchAndCheckout (fixBranchName )
215
282
}
216
283
217
284
func (cfp * CreateFixPullRequestsCmd ) cloneRepository () (tempWd string , restoreDir func () error , err error ) {
@@ -275,21 +342,6 @@ func (cfp *CreateFixPullRequestsCmd) addVulnerabilityToFixVersionsMap(vulnerabil
275
342
return nil
276
343
}
277
344
278
- // getMinimalFixVersion that fixes the current impactedPackage
279
- // fixVersions array is sorted, so we take the first index, unless it's version is older than what we have now.
280
- func getMinimalFixVersion (impactedPackageVersion string , fixVersions []string ) string {
281
- // Trim 'v' prefix in case of Go package
282
- currVersionStr := strings .TrimPrefix (impactedPackageVersion , "v" )
283
- currVersion := version .NewVersion (currVersionStr )
284
- for _ , fixVersion := range fixVersions {
285
- fixVersionCandidate := parseVersionChangeString (fixVersion )
286
- if currVersion .Compare (fixVersionCandidate ) > 0 {
287
- return fixVersionCandidate
288
- }
289
- }
290
- return ""
291
- }
292
-
293
345
func (cfp * CreateFixPullRequestsCmd ) updatePackageToFixedVersion (impactedPackage string , fixVersionInfo * utils.FixVersionInfo ) (err error ) {
294
346
// 'CD' into the relevant working directory
295
347
if cfp .projectWorkingDir != "" {
@@ -317,6 +369,21 @@ func (cfp *CreateFixPullRequestsCmd) updatePackageToFixedVersion(impactedPackage
317
369
return packageHandler .UpdateImpactedPackage (impactedPackage , fixVersionInfo )
318
370
}
319
371
372
+ // getMinimalFixVersion find the minimal version that fixes the current impactedPackage;
373
+ // fixVersions is a sorted array. The function returns the first version in the array, that is larger than impactedPackageVersion.
374
+ func getMinimalFixVersion (impactedPackageVersion string , fixVersions []string ) string {
375
+ // Trim 'v' prefix in case of Go package
376
+ currVersionStr := strings .TrimPrefix (impactedPackageVersion , "v" )
377
+ currVersion := version .NewVersion (currVersionStr )
378
+ for _ , fixVersion := range fixVersions {
379
+ fixVersionCandidate := parseVersionChangeString (fixVersion )
380
+ if currVersion .Compare (fixVersionCandidate ) > 0 {
381
+ return fixVersionCandidate
382
+ }
383
+ }
384
+ return ""
385
+ }
386
+
320
387
// 1.0 --> 1.0 ≤ x
321
388
// (,1.0] --> x ≤ 1.0
322
389
// (,1.0) --> x < 1.0
0 commit comments