| 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 |