@@ -27,6 +27,7 @@ import (
27
27
28
28
"cloud.google.com/go/compute/metadata"
29
29
"github.com/google/go-github/github"
30
+ "github.com/gregjones/httpcache"
30
31
"golang.org/x/build/gerrit"
31
32
"golang.org/x/build/internal/https"
32
33
"golang.org/x/build/internal/secret"
@@ -116,9 +117,18 @@ func githubClient(sc *secret.Client) (*github.Client, error) {
116
117
if err != nil {
117
118
return nil , err
118
119
}
119
- ts := oauth2 .StaticTokenSource (& oauth2.Token {AccessToken : token })
120
- tc := oauth2 .NewClient (context .Background (), ts )
121
- return github .NewClient (tc ), nil
120
+ oauthTransport := & oauth2.Transport {
121
+ Source : oauth2 .StaticTokenSource (& oauth2.Token {AccessToken : token }),
122
+ }
123
+ cachingTransport := & httpcache.Transport {
124
+ Transport : oauthTransport ,
125
+ Cache : httpcache .NewMemoryCache (),
126
+ MarkCachedResponses : true ,
127
+ }
128
+ httpClient := & http.Client {
129
+ Transport : cachingTransport ,
130
+ }
131
+ return github .NewClient (httpClient ), nil
122
132
}
123
133
124
134
func githubToken (sc * secret.Client ) (string , error ) {
@@ -212,27 +222,16 @@ func genProjectWhitelist() map[string]bool {
212
222
return m
213
223
}
214
224
215
- type cachedPullRequest struct {
216
- pr * github.PullRequest
217
- etag string
218
- }
219
-
220
225
type bot struct {
221
226
githubClient * github.Client
222
227
gerritClient * gerrit.Client
223
228
224
229
sync.RWMutex // Protects all fields below
225
230
corpus * maintner.Corpus
226
231
227
- // importedPRs and cachedPRs should share the same method for determining keys
228
- // given the same Pull Request, as the presence of a key in the former determined
229
- // whether it should remain in the latter.
232
+ // PRs and their corresponding Gerrit CLs.
230
233
importedPRs map [string ]* maintner.GerritCL // GitHub owner/repo#n -> Gerrit CL
231
234
232
- // Pull Requests that have been cached locally since maintner doesn’t support
233
- // PRs and this is used to make conditional requests to the API.
234
- cachedPRs map [string ]* cachedPullRequest // GitHub owner/repo#n -> GitHub Pull Request
235
-
236
235
// CLs that have been created/updated on Gerrit for GitHub PRs but are not yet
237
236
// reflected in the maintner corpus yet.
238
237
pendingCLs map [string ]string // GitHub owner/repo#n -> Commit message from PR
@@ -247,7 +246,6 @@ func newBot(githubClient *github.Client, gerritClient *gerrit.Client) *bot {
247
246
gerritClient : gerritClient ,
248
247
importedPRs : map [string ]* maintner.GerritCL {},
249
248
pendingCLs : map [string ]string {},
250
- cachedPRs : map [string ]* cachedPullRequest {},
251
249
cachedGerritAccounts : map [int ]* gerrit.AccountInfo {},
252
250
}
253
251
}
@@ -309,13 +307,6 @@ func (b *bot) checkPullRequests() {
309
307
})
310
308
})
311
309
312
- // Remove any cached PRs that are no longer being checked.
313
- for k := range b .cachedPRs {
314
- if b .importedPRs [k ] == nil {
315
- delete (b .cachedPRs , k )
316
- }
317
- }
318
-
319
310
b .corpus .GitHub ().ForeachRepo (func (ghr * maintner.GitHubRepo ) error {
320
311
id := ghr .ID ()
321
312
if id .Owner != "golang" || ! gerritProjectWhitelist [id .Repo ] {
@@ -339,14 +330,22 @@ func (b *bot) checkPullRequests() {
339
330
}
340
331
return nil
341
332
}
342
- if issue .Closed || ! issue .PullRequest || ! issue . HasLabel ( "cla: yes" ) {
333
+ if issue .Closed || ! issue .PullRequest {
343
334
return nil
344
335
}
345
336
pr , err := b .getFullPR (ctx , id .Owner , id .Repo , int (issue .Number ))
346
337
if err != nil {
347
338
log .Printf ("getFullPR(ctx, %q, %q, %d): %v" , id .Owner , id .Repo , issue .Number , err )
348
339
return nil
349
340
}
341
+ approved , err := b .claApproved (ctx , id , pr )
342
+ if err != nil {
343
+ log .Printf ("checking CLA approval: %v" , err )
344
+ return nil
345
+ }
346
+ if ! approved {
347
+ return nil
348
+ }
350
349
if err := b .processPullRequest (ctx , pr ); err != nil {
351
350
log .Printf ("processPullRequest: %v" , err )
352
351
return nil
@@ -356,6 +355,31 @@ func (b *bot) checkPullRequests() {
356
355
})
357
356
}
358
357
358
+ // claApproved reports whether the latest head commit of the given PR in repo
359
+ // has been approved by the Google CLA checker.
360
+ func (b * bot ) claApproved (ctx context.Context , repo maintner.GitHubRepoID , pr * github.PullRequest ) (bool , error ) {
361
+ if pr .GetHead ().GetSHA () == "" {
362
+ // Paranoia check. This should never happen.
363
+ return false , fmt .Errorf ("no head SHA for PR %v %v" , repo , pr .GetNumber ())
364
+ }
365
+ runs , _ , err := b .githubClient .Checks .ListCheckRunsForRef (ctx , repo .Owner , repo .Repo , pr .GetHead ().GetSHA (), & github.ListCheckRunsOptions {
366
+ CheckName : github .String ("cla/google" ),
367
+ Status : github .String ("completed" ),
368
+ Filter : github .String ("latest" ),
369
+ // TODO(heschi): filter for App ID once supported by go-github
370
+ })
371
+ if err != nil {
372
+ return false , err
373
+ }
374
+ for _ , run := range runs .CheckRuns {
375
+ if run .GetApp ().GetID () != 42202 {
376
+ continue
377
+ }
378
+ return run .GetConclusion () == "success" , nil
379
+ }
380
+ return false , nil
381
+ }
382
+
359
383
// prShortLink returns text referencing an Issue or Pull Request that will be
360
384
// automatically converted into a link by GitHub.
361
385
func githubShortLink (owner , repo string , number int ) string {
@@ -798,42 +822,12 @@ func reposRoot() string {
798
822
}
799
823
800
824
// getFullPR retrieves a Pull Request via GitHub’s API.
801
- // b.RWMutex must be Lock'ed.
802
825
func (b * bot ) getFullPR (ctx context.Context , owner , repo string , number int ) (* github.PullRequest , error ) {
803
- shortLink := githubShortLink (owner , repo , number )
804
- cpr := b .cachedPRs [shortLink ]
805
- var etag string
806
- if cpr != nil {
807
- etag = cpr .etag
808
- log .Printf ("Retrieving PR %s from GitHub using Etag %q ..." , shortLink , etag )
809
- } else {
810
- log .Printf ("Retrieving PR %s from GitHub without an Etag ..." , shortLink )
811
- }
812
-
813
- u := fmt .Sprintf ("repos/%v/%v/pulls/%d" , owner , repo , number )
814
- req , err := b .githubClient .NewRequest (http .MethodGet , u , nil )
826
+ pr , resp , err := b .githubClient .PullRequests .Get (ctx , owner , repo , number )
815
827
if err != nil {
816
- return nil , fmt .Errorf ("b.githubClient.NewRequest(%q, %q, nil): %v" , http .MethodGet , u , err )
817
- }
818
- if etag != "" {
819
- req .Header .Add ("If-None-Match" , etag )
820
- }
821
-
822
- pr := new (github.PullRequest )
823
- resp , err := b .githubClient .Do (ctx , req , pr )
824
- logGitHubRateLimits (resp )
825
- if err != nil {
826
- if resp != nil && resp .StatusCode == http .StatusNotModified {
827
- log .Println ("Returning cached version of" , shortLink )
828
- return cpr .pr , nil
829
- }
830
828
return nil , fmt .Errorf ("b.githubClient.Do: %v" , err )
831
829
}
832
-
833
- b .cachedPRs [shortLink ] = & cachedPullRequest {
834
- etag : resp .Header .Get ("Etag" ),
835
- pr : pr ,
836
- }
830
+ logGitHubRateLimits (resp )
837
831
return pr , nil
838
832
}
839
833
0 commit comments