9
9
"regexp"
10
10
"strconv"
11
11
"strings"
12
+ "time"
12
13
13
14
"github.com/github/hub/github"
14
15
"github.com/github/hub/ui"
@@ -73,7 +74,10 @@ var cmdApi = &Command{
73
74
resource as indicated in the "Link" response header. For GraphQL queries,
74
75
this utilizes 'pageInfo' that must be present in the query; see EXAMPLES.
75
76
76
- Note that multiple JSON documents will be output as a result.
77
+ Note that multiple JSON documents will be output as a result. If the API
78
+ rate limit has been reached, the final document that is output will be the
79
+ HTTP 403 notice, and the process will exit with a non-zero status. One way
80
+ this can be avoided is by enabling '--obey-ratelimit'.
77
81
78
82
--color[=<WHEN>]
79
83
Enable colored output even if stdout is not a terminal. <WHEN> can be one
@@ -86,6 +90,11 @@ var cmdApi = &Command{
86
90
requests as well. Just make sure to not use '--cache' for any GraphQL
87
91
mutations.
88
92
93
+ --obey-ratelimit
94
+ After exceeding the API rate limit, pause the process until the reset time
95
+ of the current rate limit window and retry the request. Note that this may
96
+ cause the process to hang for a long time (maximum of 1 hour).
97
+
89
98
<ENDPOINT>
90
99
The GitHub API endpoint to send the HTTP request to (default: "/").
91
100
@@ -136,7 +145,7 @@ func init() {
136
145
CmdRunner .Use (cmdApi )
137
146
}
138
147
139
- func apiCommand (cmd * Command , args * Args ) {
148
+ func apiCommand (_ * Command , args * Args ) {
140
149
path := ""
141
150
if ! args .IsParamsEmpty () {
142
151
path = args .GetParam (0 )
@@ -235,15 +244,20 @@ func apiCommand(cmd *Command, args *Args) {
235
244
parseJSON := args .Flag .Bool ("--flat" )
236
245
includeHeaders := args .Flag .Bool ("--include" )
237
246
paginate := args .Flag .Bool ("--paginate" )
247
+ rateLimitWait := args .Flag .Bool ("--obey-ratelimit" )
238
248
239
249
args .NoForward ()
240
250
241
- requestLoop := true
242
- for requestLoop {
251
+ for {
243
252
response , err := gh .GenericAPIRequest (method , path , body , headers , cacheTTL )
244
253
utils .Check (err )
245
- success := response .StatusCode < 300
246
254
255
+ if rateLimitWait && response .StatusCode == 403 && response .RateLimitRemaining () == 0 {
256
+ pauseUntil (response .RateLimitReset ())
257
+ continue
258
+ }
259
+
260
+ success := response .StatusCode < 300
247
261
jsonType := true
248
262
if ! success {
249
263
jsonType , _ = regexp .MatchString (`[/+]json(?:;|$)` , response .Header .Get ("Content-Type" ))
@@ -273,7 +287,6 @@ func apiCommand(cmd *Command, args *Args) {
273
287
os .Exit (22 )
274
288
}
275
289
276
- requestLoop = false
277
290
if paginate {
278
291
if isGraphQL && hasNextPage && endCursor != "" {
279
292
if v , ok := params ["variables" ]; ok {
@@ -283,15 +296,31 @@ func apiCommand(cmd *Command, args *Args) {
283
296
variables := map [string ]interface {}{"endCursor" : endCursor }
284
297
params ["variables" ] = variables
285
298
}
286
- requestLoop = true
299
+ goto next
287
300
} else if nextLink := response .Link ("next" ); nextLink != "" {
288
301
path = nextLink
289
- requestLoop = true
302
+ goto next
290
303
}
291
304
}
292
- if requestLoop && ! parseJSON {
305
+
306
+ break
307
+ next:
308
+ if ! parseJSON {
293
309
fmt .Fprintf (out , "\n " )
294
310
}
311
+
312
+ if rateLimitWait && response .RateLimitRemaining () == 0 {
313
+ pauseUntil (response .RateLimitReset ())
314
+ }
315
+ }
316
+ }
317
+
318
+ func pauseUntil (timestamp int ) {
319
+ rollover := time .Unix (int64 (timestamp )+ 1 , 0 )
320
+ duration := time .Until (rollover )
321
+ if duration > 0 {
322
+ ui .Errorf ("API rate limit exceeded; pausing until %v ...\n " , rollover )
323
+ time .Sleep (duration )
295
324
}
296
325
}
297
326
0 commit comments