1
1
#!/usr/bin/env python
2
2
3
- # total_count: number
4
- # incomplete_results: boolean
5
- # items: array
3
+ # The service provided by this script is not critical, but it shares a GitHub
4
+ # API request quota with critical services. For this reason, all requests to
5
+ # the GitHub API are preceded by a "guard" which verifies that the subsequent
6
+ # request will not deplete the shared quota.
7
+ #
8
+ # In effect, this script will fail rather than interfere with the operations of
9
+ # critical services.
6
10
7
11
import argparse
8
12
import contextlib
@@ -44,16 +48,43 @@ def request(method_name, url, body=None):
44
48
45
49
return resp .json ()
46
50
51
+ def guard (resource ):
52
+ '''Decorate a `Project` instance method which interacts with the GitHub
53
+ API, ensuring that the subsequent request will not deplete the relevant
54
+ allowance. This verification does not itself influence rate limiting:
55
+
56
+ > Accessing this endpoint does not count against your REST API rate limit.
57
+
58
+ https://developer.github.com/v3/rate_limit/
59
+ '''
60
+ def guard_decorator (func ):
61
+ def wrapped (self , * args , ** kwargs ):
62
+ limits = request ('GET' , '{}/rate_limit' .format (self ._host ))
63
+
64
+ values = limits ['resources' ].get (resource )
65
+
66
+ remaining = values ['remaining' ]
67
+ limit = values ['limit' ]
68
+
69
+ logger .info ('Limit for "{}" resource: {}/{}' .format (resource , remaining , limit ))
70
+
71
+ if limit and float (remaining ) / limit < API_RATE_LIMIT_THRESHOLD :
72
+ raise Exception ('Exiting to avoid GitHub.com API request throttling.' )
73
+
74
+ return func (self , * args , ** kwargs )
75
+ return wrapped
76
+ return guard_decorator
77
+
47
78
class Project (object ):
48
- def __init__ (self , host , org , repo ):
79
+ def __init__ (self , host , github_project ):
49
80
self ._host = host
50
- self ._org = org
51
- self ._repo = repo
81
+ self ._github_project = github_project
52
82
83
+ @guard ('search' )
53
84
def get_pull_requests (self , updated_since ):
54
85
window_start = time .strftime ('%Y-%m-%dT%H:%M:%SZ' , updated_since )
55
- url = '{}/search/issues?q=repo:{}/{} +is:pr+updated:>{}' .format (
56
- self ._host , self ._org , self . _repo , window_start
86
+ url = '{}/search/issues?q=repo:{}+is:pr+updated:>{}' .format (
87
+ self ._host , self ._github_project , window_start
57
88
)
58
89
59
90
logger .info (
@@ -69,31 +100,34 @@ def get_pull_requests(self, updated_since):
69
100
70
101
return data ['items' ]
71
102
103
+ @guard ('core' )
72
104
def add_label (self , pull_request , name ):
73
105
number = pull_request ['number' ]
74
- url = '{}/repos/{}/{}/ issues/{}/labels' .format (
75
- self ._host , self ._org , self . _repo , number
106
+ url = '{}/repos/{}/issues/{}/labels' .format (
107
+ self ._host , self ._github_project , number
76
108
)
77
109
78
110
logger .info ('Adding label "{}" for pull request #{}"' .format (number , name ))
79
111
80
112
request ('POST' , url , {'labels' : [name ]})
81
113
114
+ @guard ('core' )
82
115
def remove_label (self , pull_request , name ):
83
116
raise NotImplementedError ()
84
117
118
+ @guard ('core' )
85
119
def create_deployment (self , ref ):
86
- url = '{}/repos/{}/{}/ deployments' .format (
87
- self ._host , self ._org , self . _repo
120
+ url = '{}/repos/{}/deployments' .format (
121
+ self ._host , self ._github_project
88
122
)
89
123
90
124
logger .info ('Creating deployment for "{}"' .format (ref ))
91
125
92
126
request ('POST' , url , {'ref' : ref })
93
127
94
128
class Remote (object ):
95
- def __init__ (self , url ):
96
- self ._url = url
129
+ def __init__ (self , location ):
130
+ self ._location = location
97
131
98
132
@contextlib .contextmanager
99
133
def _make_temp_repo (self ):
@@ -113,7 +147,7 @@ def get_revision(self, refspec):
113
147
output = subprocess .check_output ([
114
148
'git' ,
115
149
'ls-remote' ,
116
- self ._url ,
150
+ self ._location ,
117
151
'refs/{}' .format (refspec )
118
152
])
119
153
@@ -130,7 +164,7 @@ def delete_ref(self, refspec):
130
164
131
165
with self ._make_temp_repo () as temp_repo :
132
166
subprocess .check_call (
133
- ['git' , 'push' , self ._url , '--delete' , 'refs/{}' .format (refspec )],
167
+ ['git' , 'push' , self ._location , '--delete' , 'refs/{}' .format (refspec )],
134
168
cwd = temp_repo
135
169
)
136
170
@@ -149,24 +183,9 @@ def should_be_mirrored(pull_request):
149
183
has_label (pull_request )
150
184
)
151
185
152
- def main (host , organization , repository ):
153
- # > Accessing this endpoint does not count against your REST API rate limit.
154
- #
155
- # https://developer.github.com/v3/rate_limit/
156
- limits = request ('GET' , 'https://api.github.com/rate_limit' )
157
-
158
- for name , values in limits ['resources' ].items ():
159
- remaining = values ['remaining' ]
160
- limit = values ['limit' ]
161
-
162
- logger .info ('Limit for "{}": {}/{}' .format (name , remaining , limit ))
163
-
164
- if limit and float (remaining ) / limit < API_RATE_LIMIT_THRESHOLD :
165
- logger .error ('Exiting to avoid GitHub.com API request throttling.' )
166
- sys .exit (1 )
167
-
168
- project = Project (host , organization , repository )
169
- remote = Remote (
'[email protected] :web-platform-tests/wpt.git' )
186
+ def main (host , github_project , repository ):
187
+ project = Project (host , github_project )
188
+ remote = Remote (repository )
170
189
pull_requests = project .get_pull_requests (
171
190
time .gmtime (time .time () - FIVE_MINUTES )
172
191
)
@@ -208,7 +227,7 @@ def main(host, organization, repository):
208
227
if __name__ == '__main__' :
209
228
parser = argparse .ArgumentParser ()
210
229
parser .add_argument ('--host' , required = True )
211
- parser .add_argument ('--organization ' , required = True )
230
+ parser .add_argument ('--github-project ' , required = True )
212
231
parser .add_argument ('--repository' , required = True )
213
232
214
233
main (** vars (parser .parse_args ()))
0 commit comments