-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
-
106
-
107
-
108
-
109
-
110
-
111
-
112
-
113
-
114
-
115
-
116
-
117
-
118
-
119
-
120
-
121
-
122
-
123
-
124
-
125
-
126
-
127
-
128
-
129
-
130
-
131
-
132
-
133
-
134
-
135
-
136
-
137
-
138
-
139
-
140
-
141
-
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
-
153
-
154
-
155
-
156
-
157
-
158
-
159
-
160
-
161
-
162
-
163
-
164
-
165
-
166
-
167
-
168
-
169
-
170
-
171
-
172
-
173
-
174
-
175
-
176
-
177
-
178
-
179
-
180
-
181
-
182
-
183
-
184
-
185
-
186
-
187
-
188
-
189
-
190
-
191
-
192
-
193
-
194
-
195
-
196
-
197
-
198
-
199
-
200
-
201
-
202
-
203
-
204
-
205
-
206
-
207
-
208
-
209
-
210
-
211
-
212
-
213
-
214
-
215
-
216
-
217
-
218
-
219
-
220
-
221
-
222
-
223
-
224
-
225
-
226
-
227
-
228
-
229
-
230
-
231
-
232
-
233
-
234
-
235
-
236
-
237
-
238
-
239
-
240
-
241
-
242
-
243
-
244
-
245
-
246
-
247
-
248
-
249
-
250
-
251
-
252
-
253
-
254
-
255
-
256
-
257
-
258
-
259
-
260
-
261
-
262
-
263
-
264
-
265
-
266
-
267
-
268
-
269
-
270
-
271
-
272
-
273
-
274
-
275
-
276
-
277
-
278
-
279
-
280
-
281
-
282
-
283
-
284
-
285
-
286
-
287
-
288
-
289
-
290
-
291
-
292
-
293
-
294
-
295
-
296
-
297
-
298
-
299
-
300
-
301
-
302
-
303
-
304
-
305
-
306
-
307
-
308
-
309
-
310
-
311
-
312
-
313
-
314
-
315
-
316
-
317
-
318
-
319
-
320
-
321
-
322
-
323
-
324
-
325
-
326
-
327
-
328
-
329
-
330
-
331
-
332
-
333
-
334
-
335
-
336
-
337
-
338
-
339
-
340
-
341
-
342
-
343
-
344
-
345
-
346
-
347
-
348
-
349
-
350
-
351
-
352
-
353
-
354
-
355
-
356
-
357
-
358
-
359
-
360
-
361
-
362
-
363
-
364
-
365
-
366
-
367
-
368
-
369
-
370
-
371
-
372
-
373
-
374
-
375
-
376
-
377
-
378
-
379
-
380
-
381
-
382
-
383
-
384
-
385
-
386
-
387
-
388
-
389
-
390
-
391
-
392
-
393
-
394
-
395
-
396
-
397
-
398
-
399
-
400
-
401
-
402
-
403
-
404
-
405
-
406
-
407
-
408
-
409
-
410
-
411
-
412
-
413
-
414
-
415
-
416
-
417
-
418
-
419
-
420
-
421
-
422
-
423
-
424
-
425
-
426
-
427
-
428
-
429
-
430
-
431
-
432
-
433
-
434
-
435
-
436
-
437
-
438
-
439
-
440
-
441
-
442
-
443
-
444
-
445
-
446
-
447
-
448
-
449
-
450
-
451
-
452
-
453
-
454
-
455
-
456
-
457
-
458
-
459
-
460
-
461
-
462
-
463
-
464
-
465
-
466
-
467
-
468
-
469
-
470
-
471
-
472
-
473
-
474
-
475
-
476
-
477
-
478
-
479
-
480
-
481
-
482
-
483
-
484
-
485
-
486
-
487
-
488
-
489
-
490
-
491
-
492
-
493
-
494
-
495
-
496
-
497
-
498
-
499
-
500
-
501
-
502
-
503
-
504
-
505
-
506
-
507
-
508
-
509
-
510
-
511
-
512
-
513
-
514
-
515
-
516
-
517
-
518
-
519
-
520
-
521
-
522
-
523
-
524
-
525
-
526
-
527
-
528
-
529
-
530
-
531
-
532
-
533
-
534
-
535
-
536
-
537
-
538
-
539
-
540
-
541
-
542
-
543
-
544
-
545
-
546
-
547
-
548
-
549
-
550
-
551
-
552
-
553
-
554
-
555
-
556
-
557
-
558
-
559
-
560
-
561
-
562
-
563
-
564
-
565
-
566
-
567
-
568
-
569
-
570
-
571
-
572
-
573
-
574
-
575
-
576
-
577
-
578
-
579
-
580
-
581
-
582
-
583
-
584
-
585
-
586
-
587
-
588
-
589
-
590
-
591
-
592
-
593
-
594
-
595
-
596
-
597
-
598
-
599
-
600
-
601
-
602
-
603
-
604
-
605
-
606
-
607
-
608
-
609
-
610
-
611
-
612
-
613
-
614
-
615
-
616
-
617
-
618
-
619
-
620
-
621
-
622
-
623
-
624
-
625
-
626
-
627
-
628
-
629
-
630
-
631
-
632
-
633
-
634
-
635
-
636
-
637
-
638
-
639
-
640
-
641
-
642
-
643
-
644
-
645
-
646
-
647
-
648
-
649
-
650
-
651
-
652
-
653
-
654
-
655
-
656
-
657
-
658
-
659
-
660
-
661
-
662
-
663
-
664
-
665
-
666
-
667
-
668
-
669
-
670
-
671
-
672
-
673
-
674
-
675
-
676
-
677
-
678
-
679
-
680
-
681
-
682
-
683
-
684
-
685
-
686
-
687
-
688
-
689
-
690
-
691
-
692
-
693
-
694
-
695
-
696
-
697
-
698
-
699
-
700
-
701
-
702
-
703
-
704
-
705
-
706
-
707
-
708
-
709
-
710
-
711
-
712
-
713
-
714
-
715
-
716
-
717
-
718
-
719
-
720
-
721
-
722
-
723
-
724
-
725
-
726
-
727
-
728
-
729
-
730
-
731
-
732
-
733
-
734
-
735
-
736
-
737
-
738
-
739
-
740
-
741
-
742
-
743
-
744
-
745
-
746
-
747
-
748
-
749
-
750
-
751
-
752
-
753
-
754
-
755
-
756
-
757
-
758
-
759
-
760
-
761
-
762
-
763
-
764
-
765
-
766
-
767
-
768
-
769
-
770
-
771
-
772
-
773
-
774
-
775
-
776
-
777
-
778
-
779
-
780
-
781
-
782
-
783
-
784
-
785
-
786
-
787
-
788
-
789
-
790
-
791
-
792
-
793
-
794
-
795
-
796
-
797
-
798
-
799
-
800
-
801
-
802
-
803
-
804
-
805
-
806
-
807
-
808
-
809
-
810
-
811
-
812
-
813
-
814
-
815
-
816
-
817
-
818
-
819
-
820
-
821
-
822
-
823
-
824
-
825
-
826
-
827
-
828
-
829
-
830
-
831
-
832
-
833
-
834
-
835
-
836
-
837
-
838
-
839
-
840
-
841
-
842
-
843
-
844
-
845
-
846
-
847
-
848
-
849
-
850
-
851
-
852
-
853
-
854
-
855
-
856
-
857
-
858
-
859
-
860
-
861
-
862
-
863
-
864
-
865
-
866
-
867
-
868
-
869
-
870
-
871
-
872
-
873
-
874
-
875
-
876
-
877
-
878
-
879
-
880
-
881
-
882
-
883
-
884
-
885
-
886
-
887
-
888
-
889
-
890
-
891
-
892
-
893
-
894
-
895
-
896
-
897
-
898
-
899
-
900
-
901
-
902
-
903
-
904
-
905
-
906
-
907
-
908
-
909
-
910
-
911
-
912
-
913
-
914
-
915
-
916
-
917
-
918
-
919
-
920
-
921
-
922
-
923
-
924
-
925
-
926
-
927
-
928
-
929
-
930
-
931
-
932
-
933
-
934
-
935
-
936
-
937
-
938
-
939
-
940
-
941
-
942
-
943
-
944
-
945
-
946
-
947
-
948
-
949
-
950
-
951
-
952
-
953
-
954
-
955
-
956
-
957
-
958
-
959
-
960
-
961
-
962
-
963
-
964
-
965
-
966
-
967
-
968
-
969
-
970
-
971
-
972
-
973
-
974
-
975
-
976
-
977
-
978
-
979
-
980
-
981
-
982
-
983
-
984
-
985
-
986
-
987
-
988
-
989
-
990
-
991
-
992
-
993
-
994
-
995
-
996
-
997
-
998
-
999
-
1000
-
1001
-
1002
-
1003
-
1004
-
1005
-
1006
-
1007
-
1008
-
1009
-
1010
-
1011
-
1012
-
1013
-
1014
-
1015
-
1016
-
1017
-
1018
-
1019
-
1020
-
1021
-
1022
-
1023
-
1024
-
1025
-
1026
-
1027
-
1028
-
1029
-
1030
-
1031
-
1032
-
1033
-
1034
-
1035
-
1036
-
1037
-
1038
-
1039
-
1040
-
1041
-
1042
-
1043
-
1044
-
1045
-
1046
-
1047
-
1048
-
1049
-
1050
-
1051
-
1052
-
1053
-
1054
-
1055
-
1056
-
1057
-
1058
-
1059
-
1060
-
1061
-
1062
-
1063
-
1064
-
1065
-
1066
-
1067
-
1068
-
1069
-
1070
-
1071
-
1072
-
1073
-
1074
-
1075
-
1076
-
1077
-
1078
-
1079
-
1080
-
1081
-
1082
-
1083
-
1084
-
1085
-
1086
-
1087
-
1088
-
1089
-
1090
-
1091
-
1092
-
1093
-
1094
-
1095
-
1096
-
1097
-
1098
-
1099
-
1100
-
1101
-
1102
-
1103
-
1104
-
1105
-
1106
-
1107
-
1108
-
1109
-
1110
-
1111
-
1112
-
1113
-
1114
-
1115
-
1116
-
1117
-
1118
-
1119
-
1120
-
1121
-
1122
-
1123
-
1124
-
1125
-
1126
-
1127
-
1128
-
1129
-
1130
-
1131
-
1132
-
1133
-
1134
-
1135
-
1136
-
1137
-
1138
-
1139
-
1140
-
1141
-
1142
-
1143
-
1144
-
1145
-
1146
-
1147
-
1148
-
1149
-
1150
-
1151
-
1152
-
1153
-
1154
-
1155
-
1156
-
1157
-
1158
-
1159
-
1160
-
1161
-
1162
-
1163
-
1164
-
1165
-
1166
-
1167
-
1168
-
1169
-
1170
-
1171
-
1172
-
1173
-
1174
-
1175
-
1176
-
1177
-
1178
-
1179
-
1180
-
1181
-
1182
-
1183
-
1184
-
1185
-
1186
-
1187
-
1188
-
1189
-
1190
-
1191
-
1192
-
1193
-
1194
-
1195
-
1196
-
1197
-
1198
-
1199
-
1200
-
1201
-
1202
-
1203
-
1204
-
1205
-
1206
-
1207
-
1208
-
1209
-
1210
-
1211
-
1212
-
1213
-
1214
-
1215
-
1216
-
1217
-
1218
-
1219
-
1220
-
1221
-
1222
-
1223
-
1224
-
1225
-
1226
-
1227
-
1228
-
1229
-
1230
-
1231
-
1232
-
1233
-
1234
-
1235
-
1236
-
1237
-
1238
-
1239
-
1240
-
1241
-
1242
-
1243
-
1244
-
1245
-
1246
-
1247
-
1248
-
1249
-
1250
-
1251
-
1252
-
1253
-
1254
// Copyright 2013 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:generate go run gen-accessors.go
//go:generate go run gen-stringify-test.go
package github
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"reflect"
"strconv"
"strings"
"sync"
"time"
"github.com/google/go-querystring/query"
)
const (
defaultBaseURL = "https://api.github.com/"
uploadBaseURL = "https://uploads.github.com/"
userAgent = "go-github"
headerRateLimit = "X-RateLimit-Limit"
headerRateRemaining = "X-RateLimit-Remaining"
headerRateReset = "X-RateLimit-Reset"
headerOTP = "X-GitHub-OTP"
headerTokenExpiration = "GitHub-Authentication-Token-Expiration"
mediaTypeV3 = "application/vnd.github.v3+json"
defaultMediaType = "application/octet-stream"
mediaTypeV3SHA = "application/vnd.github.v3.sha"
mediaTypeV3Diff = "application/vnd.github.v3.diff"
mediaTypeV3Patch = "application/vnd.github.v3.patch"
mediaTypeOrgPermissionRepo = "application/vnd.github.v3.repository+json"
mediaTypeIssueImportAPI = "application/vnd.github.golden-comet-preview+json"
// Media Type values to access preview APIs
// https://developer.github.com/changes/2014-12-09-new-attributes-for-stars-api/
mediaTypeStarringPreview = "application/vnd.github.v3.star+json"
// https://help.github.com/enterprise/2.4/admin/guides/migrations/exporting-the-github-com-organization-s-repositories/
mediaTypeMigrationsPreview = "application/vnd.github.wyandotte-preview+json"
// https://developer.github.com/changes/2016-04-06-deployment-and-deployment-status-enhancements/
mediaTypeDeploymentStatusPreview = "application/vnd.github.ant-man-preview+json"
// https://developer.github.com/changes/2018-10-16-deployments-environments-states-and-auto-inactive-updates/
mediaTypeExpandDeploymentStatusPreview = "application/vnd.github.flash-preview+json"
// https://developer.github.com/changes/2016-05-12-reactions-api-preview/
mediaTypeReactionsPreview = "application/vnd.github.squirrel-girl-preview"
// https://developer.github.com/changes/2016-05-23-timeline-preview-api/
mediaTypeTimelinePreview = "application/vnd.github.mockingbird-preview+json"
// https://developer.github.com/changes/2016-09-14-projects-api/
mediaTypeProjectsPreview = "application/vnd.github.inertia-preview+json"
// https://developer.github.com/changes/2017-01-05-commit-search-api/
mediaTypeCommitSearchPreview = "application/vnd.github.cloak-preview+json"
// https://developer.github.com/changes/2017-02-28-user-blocking-apis-and-webhook/
mediaTypeBlockUsersPreview = "application/vnd.github.giant-sentry-fist-preview+json"
// https://developer.github.com/changes/2017-05-23-coc-api/
mediaTypeCodesOfConductPreview = "application/vnd.github.scarlet-witch-preview+json"
// https://developer.github.com/changes/2017-07-17-update-topics-on-repositories/
mediaTypeTopicsPreview = "application/vnd.github.mercy-preview+json"
// https://developer.github.com/changes/2018-03-16-protected-branches-required-approving-reviews/
mediaTypeRequiredApprovingReviewsPreview = "application/vnd.github.luke-cage-preview+json"
// https://developer.github.com/enterprise/2.13/v3/repos/pre_receive_hooks/
mediaTypePreReceiveHooksPreview = "application/vnd.github.eye-scream-preview"
// https://developer.github.com/changes/2018-02-22-protected-branches-required-signatures/
mediaTypeSignaturePreview = "application/vnd.github.zzzax-preview+json"
// https://developer.github.com/changes/2018-09-05-project-card-events/
mediaTypeProjectCardDetailsPreview = "application/vnd.github.starfox-preview+json"
// https://developer.github.com/changes/2018-12-18-interactions-preview/
mediaTypeInteractionRestrictionsPreview = "application/vnd.github.sombra-preview+json"
// https://developer.github.com/changes/2019-03-14-enabling-disabling-pages/
mediaTypeEnablePagesAPIPreview = "application/vnd.github.switcheroo-preview+json"
// https://developer.github.com/changes/2019-04-24-vulnerability-alerts/
mediaTypeRequiredVulnerabilityAlertsPreview = "application/vnd.github.dorian-preview+json"
// https://developer.github.com/changes/2019-06-04-automated-security-fixes/
mediaTypeRequiredAutomatedSecurityFixesPreview = "application/vnd.github.london-preview+json"
// https://developer.github.com/changes/2019-05-29-update-branch-api/
mediaTypeUpdatePullRequestBranchPreview = "application/vnd.github.lydian-preview+json"
// https://developer.github.com/changes/2019-04-11-pulls-branches-for-commit/
mediaTypeListPullsOrBranchesForCommitPreview = "application/vnd.github.groot-preview+json"
// https://docs.github.com/en/free-pro-team@latest/rest/reference/previews/#repository-creation-permissions
mediaTypeMemberAllowedRepoCreationTypePreview = "application/vnd.github.surtur-preview+json"
// https://docs.github.com/en/free-pro-team@latest/rest/reference/previews/#create-and-use-repository-templates
mediaTypeRepositoryTemplatePreview = "application/vnd.github.baptiste-preview+json"
// https://developer.github.com/changes/2019-10-03-multi-line-comments/
mediaTypeMultiLineCommentsPreview = "application/vnd.github.comfort-fade-preview+json"
// https://developer.github.com/changes/2019-11-05-deprecated-passwords-and-authorizations-api/
mediaTypeOAuthAppPreview = "application/vnd.github.doctor-strange-preview+json"
// https://developer.github.com/changes/2019-12-03-internal-visibility-changes/
mediaTypeRepositoryVisibilityPreview = "application/vnd.github.nebula-preview+json"
// https://developer.github.com/changes/2018-12-10-content-attachments-api/
mediaTypeContentAttachmentsPreview = "application/vnd.github.corsair-preview+json"
)
var errNonNilContext = errors.New("context must be non-nil")
// A Client manages communication with the GitHub API.
type Client struct {
clientMu sync.Mutex // clientMu protects the client during calls that modify the CheckRedirect func.
client *http.Client // HTTP client used to communicate with the API.
// Base URL for API requests. Defaults to the public GitHub API, but can be
// set to a domain endpoint to use with GitHub Enterprise. BaseURL should
// always be specified with a trailing slash.
BaseURL *url.URL
// Base URL for uploading files.
UploadURL *url.URL
// User agent used when communicating with the GitHub API.
UserAgent string
rateMu sync.Mutex
rateLimits [categories]Rate // Rate limits for the client as determined by the most recent API calls.
common service // Reuse a single struct instead of allocating one for each service on the heap.
// Services used for talking to different parts of the GitHub API.
Actions *ActionsService
Activity *ActivityService
Admin *AdminService
Apps *AppsService
Authorizations *AuthorizationsService
Billing *BillingService
Checks *ChecksService
CodeScanning *CodeScanningService
Enterprise *EnterpriseService
Gists *GistsService
Git *GitService
Gitignores *GitignoresService
Interactions *InteractionsService
IssueImport *IssueImportService
Issues *IssuesService
Licenses *LicensesService
Marketplace *MarketplaceService
Migrations *MigrationService
Organizations *OrganizationsService
Projects *ProjectsService
PullRequests *PullRequestsService
Reactions *ReactionsService
Repositories *RepositoriesService
SCIM *SCIMService
Search *SearchService
Teams *TeamsService
Users *UsersService
}
type service struct {
client *Client
}
// Client returns the http.Client used by this GitHub client.
func (c *Client) Client() *http.Client {
c.clientMu.Lock()
defer c.clientMu.Unlock()
clientCopy := *c.client
return &clientCopy
}
// ListOptions specifies the optional parameters to various List methods that
// support offset pagination.
type ListOptions struct {
// For paginated result sets, page of results to retrieve.
Page int `url:"page,omitempty"`
// For paginated result sets, the number of results to include per page.
PerPage int `url:"per_page,omitempty"`
}
// ListCursorOptions specifies the optional parameters to various List methods that
// support cursor pagination.
type ListCursorOptions struct {
// For paginated result sets, page of results to retrieve.
Page string `url:"page,omitempty"`
// For paginated result sets, the number of results to include per page.
PerPage int `url:"per_page,omitempty"`
// A cursor, as given in the Link header. If specified, the query only searches for events after this cursor.
After string `url:"after,omitempty"`
// A cursor, as given in the Link header. If specified, the query only searches for events before this cursor.
Before string `url:"before,omitempty"`
// A cursor, as given in the Link header. If specified, the query continues the search using this cursor.
Cursor string `url:"cursor,omitempty"`
}
// UploadOptions specifies the parameters to methods that support uploads.
type UploadOptions struct {
Name string `url:"name,omitempty"`
Label string `url:"label,omitempty"`
MediaType string `url:"-"`
}
// RawType represents type of raw format of a request instead of JSON.
type RawType uint8
const (
// Diff format.
Diff RawType = 1 + iota
// Patch format.
Patch
)
// RawOptions specifies parameters when user wants to get raw format of
// a response instead of JSON.
type RawOptions struct {
Type RawType
}
// addOptions adds the parameters in opts as URL query parameters to s. opts
// must be a struct whose fields may contain "url" tags.
func addOptions(s string, opts interface{}) (string, error) {
v := reflect.ValueOf(opts)
if v.Kind() == reflect.Ptr && v.IsNil() {
return s, nil
}
u, err := url.Parse(s)
if err != nil {
return s, err
}
qs, err := query.Values(opts)
if err != nil {
return s, err
}
u.RawQuery = qs.Encode()
return u.String(), nil
}
// NewClient returns a new GitHub API client. If a nil httpClient is
// provided, a new http.Client will be used. To use API methods which require
// authentication, provide an http.Client that will perform the authentication
// for you (such as that provided by the golang.org/x/oauth2 library).
func NewClient(httpClient *http.Client) *Client {
if httpClient == nil {
httpClient = &http.Client{}
}
baseURL, _ := url.Parse(defaultBaseURL)
uploadURL, _ := url.Parse(uploadBaseURL)
c := &Client{client: httpClient, BaseURL: baseURL, UserAgent: userAgent, UploadURL: uploadURL}
c.common.client = c
c.Actions = (*ActionsService)(&c.common)
c.Activity = (*ActivityService)(&c.common)
c.Admin = (*AdminService)(&c.common)
c.Apps = (*AppsService)(&c.common)
c.Authorizations = (*AuthorizationsService)(&c.common)
c.Billing = (*BillingService)(&c.common)
c.Checks = (*ChecksService)(&c.common)
c.CodeScanning = (*CodeScanningService)(&c.common)
c.Enterprise = (*EnterpriseService)(&c.common)
c.Gists = (*GistsService)(&c.common)
c.Git = (*GitService)(&c.common)
c.Gitignores = (*GitignoresService)(&c.common)
c.Interactions = (*InteractionsService)(&c.common)
c.IssueImport = (*IssueImportService)(&c.common)
c.Issues = (*IssuesService)(&c.common)
c.Licenses = (*LicensesService)(&c.common)
c.Marketplace = &MarketplaceService{client: c}
c.Migrations = (*MigrationService)(&c.common)
c.Organizations = (*OrganizationsService)(&c.common)
c.Projects = (*ProjectsService)(&c.common)
c.PullRequests = (*PullRequestsService)(&c.common)
c.Reactions = (*ReactionsService)(&c.common)
c.Repositories = (*RepositoriesService)(&c.common)
c.SCIM = (*SCIMService)(&c.common)
c.Search = (*SearchService)(&c.common)
c.Teams = (*TeamsService)(&c.common)
c.Users = (*UsersService)(&c.common)
return c
}
// NewEnterpriseClient returns a new GitHub API client with provided
// base URL and upload URL (often is your GitHub Enterprise hostname).
// If the base URL does not have the suffix "/api/v3/", it will be added automatically.
// If the upload URL does not have the suffix "/api/uploads", it will be added automatically.
// If a nil httpClient is provided, a new http.Client will be used.
//
// Note that NewEnterpriseClient is a convenience helper only;
// its behavior is equivalent to using NewClient, followed by setting
// the BaseURL and UploadURL fields.
//
// Another important thing is that by default, the GitHub Enterprise URL format
// should be http(s)://[hostname]/api/v3/ or you will always receive the 406 status code.
// The upload URL format should be http(s)://[hostname]/api/uploads/.
func NewEnterpriseClient(baseURL, uploadURL string, httpClient *http.Client) (*Client, error) {
baseEndpoint, err := url.Parse(baseURL)
if err != nil {
return nil, err
}
if !strings.HasSuffix(baseEndpoint.Path, "/") {
baseEndpoint.Path += "/"
}
if !strings.HasSuffix(baseEndpoint.Path, "/api/v3/") &&
!strings.HasPrefix(baseEndpoint.Host, "api.") &&
!strings.Contains(baseEndpoint.Host, ".api.") {
baseEndpoint.Path += "api/v3/"
}
uploadEndpoint, err := url.Parse(uploadURL)
if err != nil {
return nil, err
}
if !strings.HasSuffix(uploadEndpoint.Path, "/") {
uploadEndpoint.Path += "/"
}
if !strings.HasSuffix(uploadEndpoint.Path, "/api/uploads/") &&
!strings.HasPrefix(uploadEndpoint.Host, "api.") &&
!strings.Contains(uploadEndpoint.Host, ".api.") {
uploadEndpoint.Path += "api/uploads/"
}
c := NewClient(httpClient)
c.BaseURL = baseEndpoint
c.UploadURL = uploadEndpoint
return c, nil
}
// NewRequest creates an API request. A relative URL can be provided in urlStr,
// in which case it is resolved relative to the BaseURL of the Client.
// Relative URLs should always be specified without a preceding slash. If
// specified, the value pointed to by body is JSON encoded and included as the
// request body.
func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
if !strings.HasSuffix(c.BaseURL.Path, "/") {
return nil, fmt.Errorf("BaseURL must have a trailing slash, but %q does not", c.BaseURL)
}
u, err := c.BaseURL.Parse(urlStr)
if err != nil {
return nil, err
}
var buf io.ReadWriter
if body != nil {
buf = &bytes.Buffer{}
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false)
err := enc.Encode(body)
if err != nil {
return nil, err
}
}
req, err := http.NewRequest(method, u.String(), buf)
if err != nil {
return nil, err
}
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
req.Header.Set("Accept", mediaTypeV3)
if c.UserAgent != "" {
req.Header.Set("User-Agent", c.UserAgent)
}
return req, nil
}
// NewUploadRequest creates an upload request. A relative URL can be provided in
// urlStr, in which case it is resolved relative to the UploadURL of the Client.
// Relative URLs should always be specified without a preceding slash.
func (c *Client) NewUploadRequest(urlStr string, reader io.Reader, size int64, mediaType string) (*http.Request, error) {
if !strings.HasSuffix(c.UploadURL.Path, "/") {
return nil, fmt.Errorf("UploadURL must have a trailing slash, but %q does not", c.UploadURL)
}
u, err := c.UploadURL.Parse(urlStr)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", u.String(), reader)
if err != nil {
return nil, err
}
req.ContentLength = size
if mediaType == "" {
mediaType = defaultMediaType
}
req.Header.Set("Content-Type", mediaType)
req.Header.Set("Accept", mediaTypeV3)
req.Header.Set("User-Agent", c.UserAgent)
return req, nil
}
// Response is a GitHub API response. This wraps the standard http.Response
// returned from GitHub and provides convenient access to things like
// pagination links.
type Response struct {
*http.Response
// These fields provide the page values for paginating through a set of
// results. Any or all of these may be set to the zero value for
// responses that are not part of a paginated set, or for which there
// are no additional pages.
//
// These fields support what is called "offset pagination" and should
// be used with the ListOptions struct.
NextPage int
PrevPage int
FirstPage int
LastPage int
// Additionally, some APIs support "cursor pagination" instead of offset.
// This means that a token points directly to the next record which
// can lead to O(1) performance compared to O(n) performance provided
// by offset pagination.
//
// For APIs that support cursor pagination (such as
// TeamsService.ListIDPGroupsInOrganization), the following field
// will be populated to point to the next page.
//
// To use this token, set ListCursorOptions.Page to this value before
// calling the endpoint again.
NextPageToken string
// For APIs that support cursor pagination, such as RepositoriesService.ListHookDeliveries,
// the following field will be populated to point to the next page.
// Set ListCursorOptions.Cursor to this value when calling the endpoint again.
Cursor string
// Explicitly specify the Rate type so Rate's String() receiver doesn't
// propagate to Response.
Rate Rate
// token's expiration date
TokenExpiration Timestamp
}
// newResponse creates a new Response for the provided http.Response.
// r must not be nil.
func newResponse(r *http.Response) *Response {
response := &Response{Response: r}
response.populatePageValues()
response.Rate = parseRate(r)
response.TokenExpiration = parseTokenExpiration(r)
return response
}
// populatePageValues parses the HTTP Link response headers and populates the
// various pagination link values in the Response.
func (r *Response) populatePageValues() {
if links, ok := r.Response.Header["Link"]; ok && len(links) > 0 {
for _, link := range strings.Split(links[0], ",") {
segments := strings.Split(strings.TrimSpace(link), ";")
// link must at least have href and rel
if len(segments) < 2 {
continue
}
// ensure href is properly formatted
if !strings.HasPrefix(segments[0], "<") || !strings.HasSuffix(segments[0], ">") {
continue
}
// try to pull out page parameter
url, err := url.Parse(segments[0][1 : len(segments[0])-1])
if err != nil {
continue
}
q := url.Query()
if cursor := q.Get("cursor"); cursor != "" {
for _, segment := range segments[1:] {
switch strings.TrimSpace(segment) {
case `rel="next"`:
r.Cursor = cursor
}
}
continue
}
page := q.Get("page")
if page == "" {
continue
}
for _, segment := range segments[1:] {
switch strings.TrimSpace(segment) {
case `rel="next"`:
if r.NextPage, err = strconv.Atoi(page); err != nil {
r.NextPageToken = page
}
case `rel="prev"`:
r.PrevPage, _ = strconv.Atoi(page)
case `rel="first"`:
r.FirstPage, _ = strconv.Atoi(page)
case `rel="last"`:
r.LastPage, _ = strconv.Atoi(page)
}
}
}
}
}
// parseRate parses the rate related headers.
func parseRate(r *http.Response) Rate {
var rate Rate
if limit := r.Header.Get(headerRateLimit); limit != "" {
rate.Limit, _ = strconv.Atoi(limit)
}
if remaining := r.Header.Get(headerRateRemaining); remaining != "" {
rate.Remaining, _ = strconv.Atoi(remaining)
}
if reset := r.Header.Get(headerRateReset); reset != "" {
if v, _ := strconv.ParseInt(reset, 10, 64); v != 0 {
rate.Reset = Timestamp{time.Unix(v, 0)}
}
}
return rate
}
// parseTokenExpiration parses the TokenExpiration related headers.
func parseTokenExpiration(r *http.Response) Timestamp {
var exp Timestamp
if v := r.Header.Get(headerTokenExpiration); v != "" {
if t, err := time.Parse("2006-01-02 03:04:05 MST", v); err == nil {
exp = Timestamp{t.Local()}
}
}
return exp
}
type requestContext uint8
const (
bypassRateLimitCheck requestContext = iota
)
// BareDo sends an API request and lets you handle the api response. If an error
// or API Error occurs, the error will contain more information. Otherwise you
// are supposed to read and close the response's Body. If rate limit is exceeded
// and reset time is in the future, BareDo returns *RateLimitError immediately
// without making a network API call.
//
// The provided ctx must be non-nil, if it is nil an error is returned. If it is
// canceled or times out, ctx.Err() will be returned.
func (c *Client) BareDo(ctx context.Context, req *http.Request) (*Response, error) {
if ctx == nil {
return nil, errNonNilContext
}
req = withContext(ctx, req)
rateLimitCategory := category(req.URL.Path)
if bypass := ctx.Value(bypassRateLimitCheck); bypass == nil {
// If we've hit rate limit, don't make further requests before Reset time.
if err := c.checkRateLimitBeforeDo(req, rateLimitCategory); err != nil {
return &Response{
Response: err.Response,
Rate: err.Rate,
}, err
}
}
resp, err := c.client.Do(req)
if err != nil {
// If we got an error, and the context has been canceled,
// the context's error is probably more useful.
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
// If the error type is *url.Error, sanitize its URL before returning.
if e, ok := err.(*url.Error); ok {
if url, err := url.Parse(e.URL); err == nil {
e.URL = sanitizeURL(url).String()
return nil, e
}
}
return nil, err
}
response := newResponse(resp)
c.rateMu.Lock()
c.rateLimits[rateLimitCategory] = response.Rate
c.rateMu.Unlock()
err = CheckResponse(resp)
if err != nil {
defer resp.Body.Close()
// Special case for AcceptedErrors. If an AcceptedError
// has been encountered, the response's payload will be
// added to the AcceptedError and returned.
//
// Issue #1022
aerr, ok := err.(*AcceptedError)
if ok {
b, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil {
return response, readErr
}
aerr.Raw = b
err = aerr
}
}
return response, err
}
// Do sends an API request and returns the API response. The API response is
// JSON decoded and stored in the value pointed to by v, or returned as an
// error if an API error has occurred. If v implements the io.Writer interface,
// the raw response body will be written to v, without attempting to first
// decode it. If v is nil, and no error hapens, the response is returned as is.
// If rate limit is exceeded and reset time is in the future, Do returns
// *RateLimitError immediately without making a network API call.
//
// The provided ctx must be non-nil, if it is nil an error is returned. If it
// is canceled or times out, ctx.Err() will be returned.
func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) {
resp, err := c.BareDo(ctx, req)
if err != nil {
return resp, err
}
defer resp.Body.Close()
switch v := v.(type) {
case nil:
case io.Writer:
_, err = io.Copy(v, resp.Body)
default:
decErr := json.NewDecoder(resp.Body).Decode(v)
if decErr == io.EOF {
decErr = nil // ignore EOF errors caused by empty response body
}
if decErr != nil {
err = decErr
}
}
return resp, err
}
// checkRateLimitBeforeDo does not make any network calls, but uses existing knowledge from
// current client state in order to quickly check if *RateLimitError can be immediately returned
// from Client.Do, and if so, returns it so that Client.Do can skip making a network API call unnecessarily.
// Otherwise it returns nil, and Client.Do should proceed normally.
func (c *Client) checkRateLimitBeforeDo(req *http.Request, rateLimitCategory rateLimitCategory) *RateLimitError {
c.rateMu.Lock()
rate := c.rateLimits[rateLimitCategory]
c.rateMu.Unlock()
if !rate.Reset.Time.IsZero() && rate.Remaining == 0 && time.Now().Before(rate.Reset.Time) {
// Create a fake response.
resp := &http.Response{
Status: http.StatusText(http.StatusForbidden),
StatusCode: http.StatusForbidden,
Request: req,
Header: make(http.Header),
Body: ioutil.NopCloser(strings.NewReader("")),
}
return &RateLimitError{
Rate: rate,
Response: resp,
Message: fmt.Sprintf("API rate limit of %v still exceeded until %v, not making remote request.", rate.Limit, rate.Reset.Time),
}
}
return nil
}
// compareHTTPResponse returns whether two http.Response objects are equal or not.
// Currently, only StatusCode is checked. This function is used when implementing the
// Is(error) bool interface for the custom error types in this package.
func compareHTTPResponse(r1, r2 *http.Response) bool {
if r1 == nil && r2 == nil {
return true
}
if r1 != nil && r2 != nil {
return r1.StatusCode == r2.StatusCode
}
return false
}
/*
An ErrorResponse reports one or more errors caused by an API request.
GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/#client-errors
*/
type ErrorResponse struct {
Response *http.Response // HTTP response that caused this error
Message string `json:"message"` // error message
Errors []Error `json:"errors"` // more detail on individual errors
// Block is only populated on certain types of errors such as code 451.
Block *ErrorBlock `json:"block,omitempty"`
// Most errors will also include a documentation_url field pointing
// to some content that might help you resolve the error, see
// https://docs.github.com/en/free-pro-team@latest/rest/reference/#client-errors
DocumentationURL string `json:"documentation_url,omitempty"`
}
// ErrorBlock contains a further explanation for the reason of an error.
// See https://developer.github.com/changes/2016-03-17-the-451-status-code-is-now-supported/
// for more information.
type ErrorBlock struct {
Reason string `json:"reason,omitempty"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
}
func (r *ErrorResponse) Error() string {
return fmt.Sprintf("%v %v: %d %v %+v",
r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
r.Response.StatusCode, r.Message, r.Errors)
}
// Is returns whether the provided error equals this error.
func (r *ErrorResponse) Is(target error) bool {
v, ok := target.(*ErrorResponse)
if !ok {
return false
}
if r.Message != v.Message || (r.DocumentationURL != v.DocumentationURL) ||
!compareHTTPResponse(r.Response, v.Response) {
return false
}
// Compare Errors.
if len(r.Errors) != len(v.Errors) {
return false
}
for idx := range r.Errors {
if r.Errors[idx] != v.Errors[idx] {
return false
}
}
// Compare Block.
if (r.Block != nil && v.Block == nil) || (r.Block == nil && v.Block != nil) {
return false
}
if r.Block != nil && v.Block != nil {
if r.Block.Reason != v.Block.Reason {
return false
}
if (r.Block.CreatedAt != nil && v.Block.CreatedAt == nil) || (r.Block.CreatedAt ==
nil && v.Block.CreatedAt != nil) {
return false
}
if r.Block.CreatedAt != nil && v.Block.CreatedAt != nil {
if *(r.Block.CreatedAt) != *(v.Block.CreatedAt) {
return false
}
}
}
return true
}
// TwoFactorAuthError occurs when using HTTP Basic Authentication for a user
// that has two-factor authentication enabled. The request can be reattempted
// by providing a one-time password in the request.
type TwoFactorAuthError ErrorResponse
func (r *TwoFactorAuthError) Error() string { return (*ErrorResponse)(r).Error() }
// RateLimitError occurs when GitHub returns 403 Forbidden response with a rate limit
// remaining value of 0.
type RateLimitError struct {
Rate Rate // Rate specifies last known rate limit for the client
Response *http.Response // HTTP response that caused this error
Message string `json:"message"` // error message
}
func (r *RateLimitError) Error() string {
return fmt.Sprintf("%v %v: %d %v %v",
r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
r.Response.StatusCode, r.Message, formatRateReset(time.Until(r.Rate.Reset.Time)))
}
// Is returns whether the provided error equals this error.
func (r *RateLimitError) Is(target error) bool {
v, ok := target.(*RateLimitError)
if !ok {
return false
}
return r.Rate == v.Rate &&
r.Message == v.Message &&
compareHTTPResponse(r.Response, v.Response)
}
// AcceptedError occurs when GitHub returns 202 Accepted response with an
// empty body, which means a job was scheduled on the GitHub side to process
// the information needed and cache it.
// Technically, 202 Accepted is not a real error, it's just used to
// indicate that results are not ready yet, but should be available soon.
// The request can be repeated after some time.
type AcceptedError struct {
// Raw contains the response body.
Raw []byte
}
func (*AcceptedError) Error() string {
return "job scheduled on GitHub side; try again later"
}
// Is returns whether the provided error equals this error.
func (ae *AcceptedError) Is(target error) bool {
v, ok := target.(*AcceptedError)
if !ok {
return false
}
return bytes.Compare(ae.Raw, v.Raw) == 0
}
// AbuseRateLimitError occurs when GitHub returns 403 Forbidden response with the
// "documentation_url" field value equal to "https://docs.github.com/en/free-pro-team@latest/rest/reference/#abuse-rate-limits".
type AbuseRateLimitError struct {
Response *http.Response // HTTP response that caused this error
Message string `json:"message"` // error message
// RetryAfter is provided with some abuse rate limit errors. If present,
// it is the amount of time that the client should wait before retrying.
// Otherwise, the client should try again later (after an unspecified amount of time).
RetryAfter *time.Duration
}
func (r *AbuseRateLimitError) Error() string {
return fmt.Sprintf("%v %v: %d %v",
r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
r.Response.StatusCode, r.Message)
}
// Is returns whether the provided error equals this error.
func (r *AbuseRateLimitError) Is(target error) bool {
v, ok := target.(*AbuseRateLimitError)
if !ok {
return false
}
return r.Message == v.Message &&
r.RetryAfter == v.RetryAfter &&
compareHTTPResponse(r.Response, v.Response)
}
// sanitizeURL redacts the client_secret parameter from the URL which may be
// exposed to the user.
func sanitizeURL(uri *url.URL) *url.URL {
if uri == nil {
return nil
}
params := uri.Query()
if len(params.Get("client_secret")) > 0 {
params.Set("client_secret", "REDACTED")
uri.RawQuery = params.Encode()
}
return uri
}
/*
An Error reports more details on an individual error in an ErrorResponse.
These are the possible validation error codes:
missing:
resource does not exist
missing_field:
a required field on a resource has not been set
invalid:
the formatting of a field is invalid
already_exists:
another resource has the same valid as this field
custom:
some resources return this (e.g. github.User.CreateKey()), additional
information is set in the Message field of the Error
GitHub error responses structure are often undocumented and inconsistent.
Sometimes error is just a simple string (Issue #540).
In such cases, Message represents an error message as a workaround.
GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/#client-errors
*/
type Error struct {
Resource string `json:"resource"` // resource on which the error occurred
Field string `json:"field"` // field on which the error occurred
Code string `json:"code"` // validation error code
Message string `json:"message"` // Message describing the error. Errors with Code == "custom" will always have this set.
}
func (e *Error) Error() string {
return fmt.Sprintf("%v error caused by %v field on %v resource",
e.Code, e.Field, e.Resource)
}
func (e *Error) UnmarshalJSON(data []byte) error {
type aliasError Error // avoid infinite recursion by using type alias.
if err := json.Unmarshal(data, (*aliasError)(e)); err != nil {
return json.Unmarshal(data, &e.Message) // data can be json string.
}
return nil
}
// CheckResponse checks the API response for errors, and returns them if
// present. A response is considered an error if it has a status code outside
// the 200 range or equal to 202 Accepted.
// API error responses are expected to have response
// body, and a JSON response body that maps to ErrorResponse.
//
// The error type will be *RateLimitError for rate limit exceeded errors,
// *AcceptedError for 202 Accepted status codes,
// and *TwoFactorAuthError for two-factor authentication errors.
func CheckResponse(r *http.Response) error {
if r.StatusCode == http.StatusAccepted {
return &AcceptedError{}
}
if c := r.StatusCode; 200 <= c && c <= 299 {
return nil
}
errorResponse := &ErrorResponse{Response: r}
data, err := ioutil.ReadAll(r.Body)
if err == nil && data != nil {
json.Unmarshal(data, errorResponse)
}
// Re-populate error response body because GitHub error responses are often
// undocumented and inconsistent.
// Issue #1136, #540.
r.Body = ioutil.NopCloser(bytes.NewBuffer(data))
switch {
case r.StatusCode == http.StatusUnauthorized && strings.HasPrefix(r.Header.Get(headerOTP), "required"):
return (*TwoFactorAuthError)(errorResponse)
case r.StatusCode == http.StatusForbidden && r.Header.Get(headerRateRemaining) == "0":
return &RateLimitError{
Rate: parseRate(r),
Response: errorResponse.Response,
Message: errorResponse.Message,
}
case r.StatusCode == http.StatusForbidden && strings.HasSuffix(errorResponse.DocumentationURL, "#abuse-rate-limits"):
abuseRateLimitError := &AbuseRateLimitError{
Response: errorResponse.Response,
Message: errorResponse.Message,
}
if v := r.Header["Retry-After"]; len(v) > 0 {
// According to GitHub support, the "Retry-After" header value will be
// an integer which represents the number of seconds that one should
// wait before resuming making requests.
retryAfterSeconds, _ := strconv.ParseInt(v[0], 10, 64) // Error handling is noop.
retryAfter := time.Duration(retryAfterSeconds) * time.Second
abuseRateLimitError.RetryAfter = &retryAfter
}
return abuseRateLimitError
default:
return errorResponse
}
}
// parseBoolResponse determines the boolean result from a GitHub API response.
// Several GitHub API methods return boolean responses indicated by the HTTP
// status code in the response (true indicated by a 204, false indicated by a
// 404). This helper function will determine that result and hide the 404
// error if present. Any other error will be returned through as-is.
func parseBoolResponse(err error) (bool, error) {
if err == nil {
return true, nil
}
if err, ok := err.(*ErrorResponse); ok && err.Response.StatusCode == http.StatusNotFound {
// Simply false. In this one case, we do not pass the error through.
return false, nil
}
// some other real error occurred
return false, err
}
// Rate represents the rate limit for the current client.
type Rate struct {
// The number of requests per hour the client is currently limited to.
Limit int `json:"limit"`
// The number of remaining requests the client can make this hour.
Remaining int `json:"remaining"`
// The time at which the current rate limit will reset.
Reset Timestamp `json:"reset"`
}
func (r Rate) String() string {
return Stringify(r)
}
// RateLimits represents the rate limits for the current client.
type RateLimits struct {
// The rate limit for non-search API requests. Unauthenticated
// requests are limited to 60 per hour. Authenticated requests are
// limited to 5,000 per hour.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/#rate-limiting
Core *Rate `json:"core"`
// The rate limit for search API requests. Unauthenticated requests
// are limited to 10 requests per minutes. Authenticated requests are
// limited to 30 per minute.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/search/#rate-limit
Search *Rate `json:"search"`
}
func (r RateLimits) String() string {
return Stringify(r)
}
type rateLimitCategory uint8
const (
coreCategory rateLimitCategory = iota
searchCategory
categories // An array of this length will be able to contain all rate limit categories.
)
// category returns the rate limit category of the endpoint, determined by Request.URL.Path.
func category(path string) rateLimitCategory {
switch {
default:
return coreCategory
case strings.HasPrefix(path, "/search/"):
return searchCategory
}
}
// RateLimits returns the rate limits for the current client.
func (c *Client) RateLimits(ctx context.Context) (*RateLimits, *Response, error) {
req, err := c.NewRequest("GET", "rate_limit", nil)
if err != nil {
return nil, nil, err
}
response := new(struct {
Resources *RateLimits `json:"resources"`
})
// This resource is not subject to rate limits.
ctx = context.WithValue(ctx, bypassRateLimitCheck, true)
resp, err := c.Do(ctx, req, response)
if err != nil {
return nil, resp, err
}
if response.Resources != nil {
c.rateMu.Lock()
if response.Resources.Core != nil {
c.rateLimits[coreCategory] = *response.Resources.Core
}
if response.Resources.Search != nil {
c.rateLimits[searchCategory] = *response.Resources.Search
}
c.rateMu.Unlock()
}
return response.Resources, resp, nil
}
func setCredentialsAsHeaders(req *http.Request, id, secret string) *http.Request {
// To set extra headers, we must make a copy of the Request so
// that we don't modify the Request we were given. This is required by the
// specification of http.RoundTripper.
//
// Since we are going to modify only req.Header here, we only need a deep copy
// of req.Header.
convertedRequest := new(http.Request)
*convertedRequest = *req
convertedRequest.Header = make(http.Header, len(req.Header))
for k, s := range req.Header {
convertedRequest.Header[k] = append([]string(nil), s...)
}
convertedRequest.SetBasicAuth(id, secret)
return convertedRequest
}
/*
UnauthenticatedRateLimitedTransport allows you to make unauthenticated calls
that need to use a higher rate limit associated with your OAuth application.
t := &github.UnauthenticatedRateLimitedTransport{
ClientID: "your app's client ID",
ClientSecret: "your app's client secret",
}
client := github.NewClient(t.Client())
This will add the client id and secret as a base64-encoded string in the format
ClientID:ClientSecret and apply it as an "Authorization": "Basic" header.
See https://docs.github.com/en/free-pro-team@latest/rest/reference/#unauthenticated-rate-limited-requests for
more information.
*/
type UnauthenticatedRateLimitedTransport struct {
// ClientID is the GitHub OAuth client ID of the current application, which
// can be found by selecting its entry in the list at
// https://github.com/settings/applications.
ClientID string
// ClientSecret is the GitHub OAuth client secret of the current
// application.
ClientSecret string
// Transport is the underlying HTTP transport to use when making requests.
// It will default to http.DefaultTransport if nil.
Transport http.RoundTripper
}
// RoundTrip implements the RoundTripper interface.
func (t *UnauthenticatedRateLimitedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if t.ClientID == "" {
return nil, errors.New("t.ClientID is empty")
}
if t.ClientSecret == "" {
return nil, errors.New("t.ClientSecret is empty")
}
req2 := setCredentialsAsHeaders(req, t.ClientID, t.ClientSecret)
// Make the HTTP request.
return t.transport().RoundTrip(req2)
}
// Client returns an *http.Client that makes requests which are subject to the
// rate limit of your OAuth application.
func (t *UnauthenticatedRateLimitedTransport) Client() *http.Client {
return &http.Client{Transport: t}
}
func (t *UnauthenticatedRateLimitedTransport) transport() http.RoundTripper {
if t.Transport != nil {
return t.Transport
}
return http.DefaultTransport
}
// BasicAuthTransport is an http.RoundTripper that authenticates all requests
// using HTTP Basic Authentication with the provided username and password. It
// additionally supports users who have two-factor authentication enabled on
// their GitHub account.
type BasicAuthTransport struct {
Username string // GitHub username
Password string // GitHub password
OTP string // one-time password for users with two-factor auth enabled
// Transport is the underlying HTTP transport to use when making requests.
// It will default to http.DefaultTransport if nil.
Transport http.RoundTripper
}
// RoundTrip implements the RoundTripper interface.
func (t *BasicAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req2 := setCredentialsAsHeaders(req, t.Username, t.Password)
if t.OTP != "" {
req2.Header.Set(headerOTP, t.OTP)
}
return t.transport().RoundTrip(req2)
}
// Client returns an *http.Client that makes requests that are authenticated
// using HTTP Basic Authentication.
func (t *BasicAuthTransport) Client() *http.Client {
return &http.Client{Transport: t}
}
func (t *BasicAuthTransport) transport() http.RoundTripper {
if t.Transport != nil {
return t.Transport
}
return http.DefaultTransport
}
// formatRateReset formats d to look like "[rate reset in 2s]" or
// "[rate reset in 87m02s]" for the positive durations. And like "[rate limit was reset 87m02s ago]"
// for the negative cases.
func formatRateReset(d time.Duration) string {
isNegative := d < 0
if isNegative {
d *= -1
}
secondsTotal := int(0.5 + d.Seconds())
minutes := secondsTotal / 60
seconds := secondsTotal - minutes*60
var timeString string
if minutes > 0 {
timeString = fmt.Sprintf("%dm%02ds", minutes, seconds)
} else {
timeString = fmt.Sprintf("%ds", seconds)
}
if isNegative {
return fmt.Sprintf("[rate limit was reset %v ago]", timeString)
}
return fmt.Sprintf("[rate reset in %v]", timeString)
}
// Bool is a helper routine that allocates a new bool value
// to store v and returns a pointer to it.
func Bool(v bool) *bool { return &v }
// Int is a helper routine that allocates a new int value
// to store v and returns a pointer to it.
func Int(v int) *int { return &v }
// Int64 is a helper routine that allocates a new int64 value
// to store v and returns a pointer to it.
func Int64(v int64) *int64 { return &v }
// String is a helper routine that allocates a new string value
// to store v and returns a pointer to it.
func String(v string) *string { return &v }