@@ -384,6 +384,7 @@ var tasks = []struct {
384
384
// Gerrit tasks are applied to all projects by default.
385
385
{"abandon scratch reviews" , (* gopherbot ).abandonScratchReviews },
386
386
{"assign reviewers to CLs" , (* gopherbot ).assignReviewersToCLs },
387
+ {"auto-submit CLs" , (* gopherbot ).autoSubmitCLs },
387
388
388
389
// Tasks that are specific to the golang/vscode-go repo.
389
390
{"set vscode-go milestones" , (* gopherbot ).setVSCodeGoMilestones },
@@ -2255,6 +2256,122 @@ func (b *gopherbot) humanReviewersOnChange(ctx context.Context, change gerritCha
2255
2256
return ids , count >= minHumans
2256
2257
}
2257
2258
2259
+ // autoSubmitCLs submits CLs which are labelled "Auto-Submit", are submittable according to Gerrit,
2260
+ // have a positive TryBot-Result label, and have no unresolved comments.
2261
+ //
2262
+ // See golang.org/issue/48021.
2263
+ func (b * gopherbot ) autoSubmitCLs (ctx context.Context ) error {
2264
+ // We only run this task if it was explicitly requested via
2265
+ // the --only-run flag.
2266
+ if * onlyRun == "" {
2267
+ return nil
2268
+ }
2269
+
2270
+ return b .corpus .Gerrit ().ForeachProjectUnsorted (func (gp * maintner.GerritProject ) error {
2271
+ if gp .Server () != "go.googlesource.com" {
2272
+ return nil
2273
+ }
2274
+ return gp .ForeachOpenCL (func (cl * maintner.GerritCL ) error {
2275
+ gc := gerritChange {gp .Project (), cl .Number }
2276
+ if b .deletedChanges [gc ] {
2277
+ return nil
2278
+ }
2279
+
2280
+ // Break out early (before making Gerrit API calls) if the Auto-Submit label
2281
+ // hasn't been used at all in this CL.
2282
+ var autosubmitPresent bool
2283
+ for _ , meta := range cl .Metas {
2284
+ if strings .Contains (meta .Commit .Msg , "\n Label: Auto-Submit" ) {
2285
+ autosubmitPresent = true
2286
+ break
2287
+ }
2288
+ }
2289
+ if ! autosubmitPresent {
2290
+ return nil
2291
+ }
2292
+
2293
+ // Skip this CL if there aren't Auto-Submit+1 and TryBot-Result+1 labels.
2294
+ changeInfo , err := b .gerrit .GetChange (ctx , fmt .Sprint (cl .Number ), gerrit.QueryChangesOpt {Fields : []string {"LABELS" , "SUBMITTABLE" }})
2295
+ if err != nil {
2296
+ if httpErr , ok := err .(* gerrit.HTTPError ); ok && httpErr .Res .StatusCode == http .StatusNotFound {
2297
+ b .deletedChanges [gc ] = true
2298
+ }
2299
+ log .Printf ("Could not retrieve change %q: %v" , gc .ID (), err )
2300
+ return nil
2301
+ }
2302
+ if ! (changeInfo .Labels ["Auto-Submit" ].Approved != nil && changeInfo .Labels ["TryBot-Result" ].Approved != nil ) {
2303
+ return nil
2304
+ }
2305
+ // NOTE: we might be able to skip this as well, since the revision action
2306
+ // check will also cover this...
2307
+ if ! changeInfo .Submittable {
2308
+ return nil
2309
+ }
2310
+
2311
+ // Skip this CL if there are any unresolved comment threads.
2312
+ comments , err := b .gerrit .ListChangeComments (ctx , fmt .Sprint (cl .Number ))
2313
+ if err != nil {
2314
+ return err
2315
+ }
2316
+ for _ , commentSet := range comments {
2317
+ sort .Slice (commentSet , func (i , j int ) bool {
2318
+ return commentSet [i ].Updated .Time ().Before (commentSet [j ].Updated .Time ())
2319
+ })
2320
+ threads := make (map [string ]bool )
2321
+ for _ , c := range commentSet {
2322
+ id := c .ID
2323
+ if c .InReplyTo != "" {
2324
+ id = c .InReplyTo
2325
+ }
2326
+ threads [id ] = * c .Unresolved
2327
+ }
2328
+ for _ , unresolved := range threads {
2329
+ if unresolved {
2330
+ return nil
2331
+ }
2332
+ }
2333
+ }
2334
+
2335
+ // We need to check the mergeability, as well as the submitability,
2336
+ // as the latter doesn't take into account merge conflicts, just
2337
+ // if the change satisfies the project submit rules.
2338
+ //
2339
+ // NOTE: this may now be redundant, since the revision action check
2340
+ // below will also inherently checks mergeability, since the change
2341
+ // cannot actually be submitted if there is a merge conflict. We
2342
+ // may be able to just skip this entirely.
2343
+ mi , err := b .gerrit .GetMergeable (ctx , fmt .Sprint (cl .Number ), "current" )
2344
+ if err != nil {
2345
+ return err
2346
+ }
2347
+ if ! mi .Mergeable || mi .CommitMerged {
2348
+ return nil
2349
+ }
2350
+
2351
+ ra , err := b .gerrit .GetRevisionActions (ctx , fmt .Sprint (cl .Number ), "current" )
2352
+ if err != nil {
2353
+ return err
2354
+ }
2355
+ if ra ["submit" ] == nil || ! ra ["submit" ].Enabled {
2356
+ return nil
2357
+ }
2358
+
2359
+ if * dryRun {
2360
+ log .Printf ("[dry-run] would've submitted CL https://golang.org/cl/%d ..." , cl .Number )
2361
+ return nil
2362
+ }
2363
+ log .Printf ("submitting CL https://golang.org/cl/%d ..." , cl .Number )
2364
+
2365
+ // TODO: if maintner isn't fast enough (or is too fast) and it re-runs this
2366
+ // before the submission is noticed, we may run this more than once. This
2367
+ // could be handled with a local cache of "recently submitted" changes to
2368
+ // be ignored.
2369
+ _ , err = b .gerrit .SubmitChange (ctx , fmt .Sprint (cl .Number ))
2370
+ return err
2371
+ })
2372
+ })
2373
+ }
2374
+
2258
2375
// reviewerRe extracts the reviewer's Gerrit ID from a line that looks like:
2259
2376
//
2260
2377
// Reviewer: Rebecca Stambler <16140@62eb7196-b449-3ce5-99f1-c037f21e1705>
0 commit comments