OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright 2013 the V8 project authors. All rights reserved. | 2 # Copyright 2013 the V8 project authors. All rights reserved. |
3 # Redistribution and use in source and binary forms, with or without | 3 # Redistribution and use in source and binary forms, with or without |
4 # modification, are permitted provided that the following conditions are | 4 # modification, are permitted provided that the following conditions are |
5 # met: | 5 # met: |
6 # | 6 # |
7 # * Redistributions of source code must retain the above copyright | 7 # * Redistributions of source code must retain the above copyright |
8 # notice, this list of conditions and the following disclaimer. | 8 # notice, this list of conditions and the following disclaimer. |
9 # * Redistributions in binary form must reproduce the above | 9 # * Redistributions in binary form must reproduce the above |
10 # copyright notice, this list of conditions and the following | 10 # copyright notice, this list of conditions and the following |
(...skipping 194 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
205 | 205 |
206 def Sleep(self, seconds): | 206 def Sleep(self, seconds): |
207 time.sleep(seconds) | 207 time.sleep(seconds) |
208 | 208 |
209 def GetDate(self): | 209 def GetDate(self): |
210 return datetime.date.today().strftime("%Y-%m-%d") | 210 return datetime.date.today().strftime("%Y-%m-%d") |
211 | 211 |
212 DEFAULT_SIDE_EFFECT_HANDLER = SideEffectHandler() | 212 DEFAULT_SIDE_EFFECT_HANDLER = SideEffectHandler() |
213 | 213 |
214 | 214 |
| 215 class NoRetryException(Exception): |
| 216 pass |
| 217 |
| 218 class CommonOptions(object): |
| 219 def __init__(self, options, manual=True): |
| 220 self.requires_editor = True |
| 221 self.wait_for_lgtm = True |
| 222 self.s = options.s |
| 223 self.force_readline_defaults = not manual |
| 224 self.force_upload = not manual |
| 225 self.manual = manual |
| 226 |
| 227 |
215 class Step(object): | 228 class Step(object): |
216 def __init__(self, text, requires, number, config, state, options, handler): | 229 def __init__(self, text, requires, number, config, state, options, handler): |
217 self._text = text | 230 self._text = text |
218 self._requires = requires | 231 self._requires = requires |
219 self._number = number | 232 self._number = number |
220 self._config = config | 233 self._config = config |
221 self._state = state | 234 self._state = state |
222 self._options = options | 235 self._options = options |
223 self._side_effect_handler = handler | 236 self._side_effect_handler = handler |
224 assert self._number >= 0 | 237 assert self._number >= 0 |
225 assert self._config is not None | 238 assert self._config is not None |
226 assert self._state is not None | 239 assert self._state is not None |
227 assert self._side_effect_handler is not None | 240 assert self._side_effect_handler is not None |
| 241 assert isinstance(options, CommonOptions) |
228 | 242 |
229 def Config(self, key): | 243 def Config(self, key): |
230 return self._config[key] | 244 return self._config[key] |
231 | 245 |
232 def IsForced(self): | |
233 return self._options and self._options.f | |
234 | |
235 def IsManual(self): | |
236 return self._options and self._options.m | |
237 | |
238 def Run(self): | 246 def Run(self): |
239 if self._requires: | 247 if self._requires: |
240 self.RestoreIfUnset(self._requires) | 248 self.RestoreIfUnset(self._requires) |
241 if not self._state[self._requires]: | 249 if not self._state[self._requires]: |
242 return | 250 return |
243 print ">>> Step %d: %s" % (self._number, self._text) | 251 print ">>> Step %d: %s" % (self._number, self._text) |
244 self.RunStep() | 252 self.RunStep() |
245 | 253 |
246 def RunStep(self): | 254 def RunStep(self): |
247 raise NotImplementedError | 255 raise NotImplementedError |
248 | 256 |
249 def Retry(self, cb, retry_on=None, wait_plan=None): | 257 def Retry(self, cb, retry_on=None, wait_plan=None): |
250 """ Retry a function. | 258 """ Retry a function. |
251 Params: | 259 Params: |
252 cb: The function to retry. | 260 cb: The function to retry. |
253 retry_on: A callback that takes the result of the function and returns | 261 retry_on: A callback that takes the result of the function and returns |
254 True if the function should be retried. A function throwing an | 262 True if the function should be retried. A function throwing an |
255 exception is always retried. | 263 exception is always retried. |
256 wait_plan: A list of waiting delays between retries in seconds. The | 264 wait_plan: A list of waiting delays between retries in seconds. The |
257 maximum number of retries is len(wait_plan). | 265 maximum number of retries is len(wait_plan). |
258 """ | 266 """ |
259 retry_on = retry_on or (lambda x: False) | 267 retry_on = retry_on or (lambda x: False) |
260 wait_plan = list(wait_plan or []) | 268 wait_plan = list(wait_plan or []) |
261 wait_plan.reverse() | 269 wait_plan.reverse() |
262 while True: | 270 while True: |
263 got_exception = False | 271 got_exception = False |
264 try: | 272 try: |
265 result = cb() | 273 result = cb() |
| 274 except NoRetryException, e: |
| 275 raise e |
266 except Exception: | 276 except Exception: |
267 got_exception = True | 277 got_exception = True |
268 if got_exception or retry_on(result): | 278 if got_exception or retry_on(result): |
269 if not wait_plan: | 279 if not wait_plan: |
270 raise Exception("Retried too often. Giving up.") | 280 raise Exception("Retried too often. Giving up.") |
271 wait_time = wait_plan.pop() | 281 wait_time = wait_plan.pop() |
272 print "Waiting for %f seconds." % wait_time | 282 print "Waiting for %f seconds." % wait_time |
273 self._side_effect_handler.Sleep(wait_time) | 283 self._side_effect_handler.Sleep(wait_time) |
274 print "Retrying..." | 284 print "Retrying..." |
275 else: | 285 else: |
276 return result | 286 return result |
277 | 287 |
278 def ReadLine(self, default=None): | 288 def ReadLine(self, default=None): |
279 # Don't prompt in forced mode. | 289 # Don't prompt in forced mode. |
280 if not self.IsManual() and default is not None: | 290 if self._options.force_readline_defaults and default is not None: |
281 print "%s (forced)" % default | 291 print "%s (forced)" % default |
282 return default | 292 return default |
283 else: | 293 else: |
284 return self._side_effect_handler.ReadLine() | 294 return self._side_effect_handler.ReadLine() |
285 | 295 |
286 def Git(self, args="", prefix="", pipe=True, retry_on=None): | 296 def Git(self, args="", prefix="", pipe=True, retry_on=None): |
287 cmd = lambda: self._side_effect_handler.Command("git", args, prefix, pipe) | 297 cmd = lambda: self._side_effect_handler.Command("git", args, prefix, pipe) |
288 return self.Retry(cmd, retry_on, [5, 30]) | 298 return self.Retry(cmd, retry_on, [5, 30]) |
289 | 299 |
290 def Editor(self, args): | 300 def Editor(self, args): |
291 if not self.IsForced(): | 301 if self._options.requires_editor: |
292 return self._side_effect_handler.Command(os.environ["EDITOR"], args, | 302 return self._side_effect_handler.Command(os.environ["EDITOR"], args, |
293 pipe=False) | 303 pipe=False) |
294 | 304 |
295 def ReadURL(self, url, retry_on=None, wait_plan=None): | 305 def ReadURL(self, url, retry_on=None, wait_plan=None): |
296 wait_plan = wait_plan or [3, 60, 600] | 306 wait_plan = wait_plan or [3, 60, 600] |
297 cmd = lambda: self._side_effect_handler.ReadURL(url) | 307 cmd = lambda: self._side_effect_handler.ReadURL(url) |
298 return self.Retry(cmd, retry_on, wait_plan) | 308 return self.Retry(cmd, retry_on, wait_plan) |
299 | 309 |
300 def GetDate(self): | 310 def GetDate(self): |
301 return self._side_effect_handler.GetDate() | 311 return self._side_effect_handler.GetDate() |
302 | 312 |
303 def Die(self, msg=""): | 313 def Die(self, msg=""): |
304 if msg != "": | 314 if msg != "": |
305 print "Error: %s" % msg | 315 print "Error: %s" % msg |
306 print "Exiting" | 316 print "Exiting" |
307 raise Exception(msg) | 317 raise Exception(msg) |
308 | 318 |
309 def DieNoManualMode(self, msg=""): | 319 def DieNoManualMode(self, msg=""): |
310 if not self.IsManual(): | 320 if not self._options.manual: |
311 msg = msg or "Only available in manual mode." | 321 msg = msg or "Only available in manual mode." |
312 self.Die(msg) | 322 self.Die(msg) |
313 | 323 |
314 def Confirm(self, msg): | 324 def Confirm(self, msg): |
315 print "%s [Y/n] " % msg, | 325 print "%s [Y/n] " % msg, |
316 answer = self.ReadLine(default="Y") | 326 answer = self.ReadLine(default="Y") |
317 return answer == "" or answer == "Y" or answer == "y" | 327 return answer == "" or answer == "Y" or answer == "y" |
318 | 328 |
319 def DeleteBranch(self, name): | 329 def DeleteBranch(self, name): |
320 git_result = self.Git("branch").strip() | 330 git_result = self.Git("branch").strip() |
(...skipping 20 matching lines...) Expand all Loading... |
341 def RestoreIfUnset(self, var_name): | 351 def RestoreIfUnset(self, var_name): |
342 if self._state.get(var_name) is None: | 352 if self._state.get(var_name) is None: |
343 self._state[var_name] = self.Restore(var_name) | 353 self._state[var_name] = self.Restore(var_name) |
344 | 354 |
345 def InitialEnvironmentChecks(self): | 355 def InitialEnvironmentChecks(self): |
346 # Cancel if this is not a git checkout. | 356 # Cancel if this is not a git checkout. |
347 if not os.path.exists(self._config[DOT_GIT_LOCATION]): | 357 if not os.path.exists(self._config[DOT_GIT_LOCATION]): |
348 self.Die("This is not a git checkout, this script won't work for you.") | 358 self.Die("This is not a git checkout, this script won't work for you.") |
349 | 359 |
350 # Cancel if EDITOR is unset or not executable. | 360 # Cancel if EDITOR is unset or not executable. |
351 if (not self.IsForced() and (not os.environ.get("EDITOR") or | 361 if (self._options.requires_editor and (not os.environ.get("EDITOR") or |
352 Command("which", os.environ["EDITOR"]) is None)): | 362 Command("which", os.environ["EDITOR"]) is None)): |
353 self.Die("Please set your EDITOR environment variable, you'll need it.") | 363 self.Die("Please set your EDITOR environment variable, you'll need it.") |
354 | 364 |
355 def CommonPrepare(self): | 365 def CommonPrepare(self): |
356 # Check for a clean workdir. | 366 # Check for a clean workdir. |
357 if self.Git("status -s -uno").strip() != "": | 367 if self.Git("status -s -uno").strip() != "": |
358 self.Die("Workspace is not clean. Please commit or undo your changes.") | 368 self.Die("Workspace is not clean. Please commit or undo your changes.") |
359 | 369 |
360 # Persist current branch. | 370 # Persist current branch. |
361 current_branch = "" | 371 current_branch = "" |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
411 self.RestoreIfUnset("%s%s" % (prefix, v)) | 421 self.RestoreIfUnset("%s%s" % (prefix, v)) |
412 | 422 |
413 def WaitForLGTM(self): | 423 def WaitForLGTM(self): |
414 print ("Please wait for an LGTM, then type \"LGTM<Return>\" to commit " | 424 print ("Please wait for an LGTM, then type \"LGTM<Return>\" to commit " |
415 "your change. (If you need to iterate on the patch or double check " | 425 "your change. (If you need to iterate on the patch or double check " |
416 "that it's sane, do so in another shell, but remember to not " | 426 "that it's sane, do so in another shell, but remember to not " |
417 "change the headline of the uploaded CL.") | 427 "change the headline of the uploaded CL.") |
418 answer = "" | 428 answer = "" |
419 while answer != "LGTM": | 429 while answer != "LGTM": |
420 print "> ", | 430 print "> ", |
421 answer = self.ReadLine("LGTM" if self.IsForced() else None) | 431 answer = self.ReadLine(None if self._options.wait_for_lgtm else "LGTM") |
422 if answer != "LGTM": | 432 if answer != "LGTM": |
423 print "That was not 'LGTM'." | 433 print "That was not 'LGTM'." |
424 | 434 |
425 def WaitForResolvingConflicts(self, patch_file): | 435 def WaitForResolvingConflicts(self, patch_file): |
426 print("Applying the patch \"%s\" failed. Either type \"ABORT<Return>\", " | 436 print("Applying the patch \"%s\" failed. Either type \"ABORT<Return>\", " |
427 "or resolve the conflicts, stage *all* touched files with " | 437 "or resolve the conflicts, stage *all* touched files with " |
428 "'git add', and type \"RESOLVED<Return>\"") | 438 "'git add', and type \"RESOLVED<Return>\"") |
429 self.DieNoManualMode() | 439 self.DieNoManualMode() |
430 answer = "" | 440 answer = "" |
431 while answer != "RESOLVED": | 441 while answer != "RESOLVED": |
(...skipping 15 matching lines...) Expand all Loading... |
447 MESSAGE = "Upload for code review." | 457 MESSAGE = "Upload for code review." |
448 | 458 |
449 def RunStep(self): | 459 def RunStep(self): |
450 if self._options.r: | 460 if self._options.r: |
451 print "Using account %s for review." % self._options.r | 461 print "Using account %s for review." % self._options.r |
452 reviewer = self._options.r | 462 reviewer = self._options.r |
453 else: | 463 else: |
454 print "Please enter the email address of a V8 reviewer for your patch: ", | 464 print "Please enter the email address of a V8 reviewer for your patch: ", |
455 self.DieNoManualMode("A reviewer must be specified in forced mode.") | 465 self.DieNoManualMode("A reviewer must be specified in forced mode.") |
456 reviewer = self.ReadLine() | 466 reviewer = self.ReadLine() |
457 force_flag = " -f" if not self.IsManual() else "" | 467 force_flag = " -f" if self._options.force_upload else "" |
458 args = "cl upload -r \"%s\" --send-mail%s" % (reviewer, force_flag) | 468 args = "cl upload -r \"%s\" --send-mail%s" % (reviewer, force_flag) |
459 # TODO(machenbach): Check output in forced mode. Verify that all required | 469 # TODO(machenbach): Check output in forced mode. Verify that all required |
460 # base files were uploaded, if not retry. | 470 # base files were uploaded, if not retry. |
461 if self.Git(args, pipe=False) is None: | 471 if self.Git(args, pipe=False) is None: |
462 self.Die("'git cl upload' failed, please try again.") | 472 self.Die("'git cl upload' failed, please try again.") |
463 | 473 |
464 | 474 |
465 def MakeStep(step_class=Step, number=0, state=None, config=None, | 475 def MakeStep(step_class=Step, number=0, state=None, config=None, |
466 options=None, side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER): | 476 options=None, side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER): |
467 # Allow to pass in empty dictionaries. | 477 # Allow to pass in empty dictionaries. |
(...skipping 19 matching lines...) Expand all Loading... |
487 options, | 497 options, |
488 side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER): | 498 side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER): |
489 state = {} | 499 state = {} |
490 steps = [] | 500 steps = [] |
491 for (number, step_class) in enumerate(step_classes): | 501 for (number, step_class) in enumerate(step_classes): |
492 steps.append(MakeStep(step_class, number, state, config, | 502 steps.append(MakeStep(step_class, number, state, config, |
493 options, side_effect_handler)) | 503 options, side_effect_handler)) |
494 | 504 |
495 for step in steps[options.s:]: | 505 for step in steps[options.s:]: |
496 step.Run() | 506 step.Run() |
OLD | NEW |