| 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 |
| 11 # disclaimer in the documentation and/or other materials provided | 11 # disclaimer in the documentation and/or other materials provided |
| 12 # with the distribution. | 12 # with the distribution. |
| 13 # * Neither the name of Google Inc. nor the names of its | 13 # * Neither the name of Google Inc. nor the names of its |
| 14 # contributors may be used to endorse or promote products derived | 14 # contributors may be used to endorse or promote products derived |
| 15 # from this software without specific prior written permission. | 15 # from this software without specific prior written permission. |
| 16 # | 16 # |
| 17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 28 | 28 |
| 29 import datetime |
| 29 import os | 30 import os |
| 30 import re | 31 import re |
| 31 import subprocess | 32 import subprocess |
| 32 import sys | 33 import sys |
| 33 import textwrap | 34 import textwrap |
| 35 import time |
| 34 import urllib2 | 36 import urllib2 |
| 35 | 37 |
| 36 PERSISTFILE_BASENAME = "PERSISTFILE_BASENAME" | 38 PERSISTFILE_BASENAME = "PERSISTFILE_BASENAME" |
| 37 TEMP_BRANCH = "TEMP_BRANCH" | 39 TEMP_BRANCH = "TEMP_BRANCH" |
| 38 BRANCHNAME = "BRANCHNAME" | 40 BRANCHNAME = "BRANCHNAME" |
| 39 DOT_GIT_LOCATION = "DOT_GIT_LOCATION" | 41 DOT_GIT_LOCATION = "DOT_GIT_LOCATION" |
| 40 VERSION_FILE = "VERSION_FILE" | 42 VERSION_FILE = "VERSION_FILE" |
| 41 CHANGELOG_FILE = "CHANGELOG_FILE" | 43 CHANGELOG_FILE = "CHANGELOG_FILE" |
| 42 CHANGELOG_ENTRY_FILE = "CHANGELOG_ENTRY_FILE" | 44 CHANGELOG_ENTRY_FILE = "CHANGELOG_ENTRY_FILE" |
| 43 COMMITMSG_FILE = "COMMITMSG_FILE" | 45 COMMITMSG_FILE = "COMMITMSG_FILE" |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 97 | 99 |
| 98 def MakeChangeLogBody(commit_messages, auto_format=False): | 100 def MakeChangeLogBody(commit_messages, auto_format=False): |
| 99 result = "" | 101 result = "" |
| 100 added_titles = set() | 102 added_titles = set() |
| 101 for (title, body, author) in commit_messages: | 103 for (title, body, author) in commit_messages: |
| 102 # TODO(machenbach): Better check for reverts. A revert should remove the | 104 # TODO(machenbach): Better check for reverts. A revert should remove the |
| 103 # original CL from the actual log entry. | 105 # original CL from the actual log entry. |
| 104 title = title.strip() | 106 title = title.strip() |
| 105 if auto_format: | 107 if auto_format: |
| 106 # Only add commits that set the LOG flag correctly. | 108 # Only add commits that set the LOG flag correctly. |
| 107 log_exp = r"^[ \t]*LOG[ \t]*=[ \t]*(?:Y(?:ES)?)|TRUE" | 109 log_exp = r"^[ \t]*LOG[ \t]*=[ \t]*(?:(?:Y(?:ES)?)|TRUE)" |
| 108 if not re.search(log_exp, body, flags=re.I | re.M): | 110 if not re.search(log_exp, body, flags=re.I | re.M): |
| 109 continue | 111 continue |
| 110 # Never include reverts. | 112 # Never include reverts. |
| 111 if title.startswith("Revert "): | 113 if title.startswith("Revert "): |
| 112 continue | 114 continue |
| 113 # Don't include duplicates. | 115 # Don't include duplicates. |
| 114 if title in added_titles: | 116 if title in added_titles: |
| 115 continue | 117 continue |
| 116 | 118 |
| 117 # Add and format the commit's title and bug reference. Move dot to the end. | 119 # Add and format the commit's title and bug reference. Move dot to the end. |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 166 | 168 |
| 167 if len(bug_groups) > 0: | 169 if len(bug_groups) > 0: |
| 168 return "(%s)" % ", ".join(bug_groups) | 170 return "(%s)" % ", ".join(bug_groups) |
| 169 else: | 171 else: |
| 170 return "" | 172 return "" |
| 171 | 173 |
| 172 | 174 |
| 173 # Some commands don't like the pipe, e.g. calling vi from within the script or | 175 # Some commands don't like the pipe, e.g. calling vi from within the script or |
| 174 # from subscripts like git cl upload. | 176 # from subscripts like git cl upload. |
| 175 def Command(cmd, args="", prefix="", pipe=True): | 177 def Command(cmd, args="", prefix="", pipe=True): |
| 178 # TODO(machenbach): Use timeout. |
| 176 cmd_line = "%s %s %s" % (prefix, cmd, args) | 179 cmd_line = "%s %s %s" % (prefix, cmd, args) |
| 177 print "Command: %s" % cmd_line | 180 print "Command: %s" % cmd_line |
| 178 try: | 181 try: |
| 179 if pipe: | 182 if pipe: |
| 180 return subprocess.check_output(cmd_line, shell=True) | 183 return subprocess.check_output(cmd_line, shell=True) |
| 181 else: | 184 else: |
| 182 return subprocess.check_call(cmd_line, shell=True) | 185 return subprocess.check_call(cmd_line, shell=True) |
| 183 except subprocess.CalledProcessError: | 186 except subprocess.CalledProcessError: |
| 184 return None | 187 return None |
| 185 | 188 |
| 186 | 189 |
| 187 # Wrapper for side effects. | 190 # Wrapper for side effects. |
| 188 class SideEffectHandler(object): | 191 class SideEffectHandler(object): |
| 189 def Command(self, cmd, args="", prefix="", pipe=True): | 192 def Command(self, cmd, args="", prefix="", pipe=True): |
| 190 return Command(cmd, args, prefix, pipe) | 193 return Command(cmd, args, prefix, pipe) |
| 191 | 194 |
| 192 def ReadLine(self): | 195 def ReadLine(self): |
| 193 return sys.stdin.readline().strip() | 196 return sys.stdin.readline().strip() |
| 194 | 197 |
| 195 def ReadURL(self, url): | 198 def ReadURL(self, url): |
| 196 # pylint: disable=E1121 | 199 # pylint: disable=E1121 |
| 197 url_fh = urllib2.urlopen(url, None, 60) | 200 url_fh = urllib2.urlopen(url, None, 60) |
| 198 try: | 201 try: |
| 199 return url_fh.read() | 202 return url_fh.read() |
| 200 finally: | 203 finally: |
| 201 url_fh.close() | 204 url_fh.close() |
| 202 | 205 |
| 206 def Sleep(self, seconds): |
| 207 time.sleep(seconds) |
| 208 |
| 209 def GetDate(self): |
| 210 return datetime.date.today().strftime("%Y-%m-%d") |
| 211 |
| 203 DEFAULT_SIDE_EFFECT_HANDLER = SideEffectHandler() | 212 DEFAULT_SIDE_EFFECT_HANDLER = SideEffectHandler() |
| 204 | 213 |
| 205 | 214 |
| 206 class Step(object): | 215 class Step(object): |
| 207 def __init__(self, text, requires, number, config, state, options, handler): | 216 def __init__(self, text, requires, number, config, state, options, handler): |
| 208 self._text = text | 217 self._text = text |
| 209 self._requires = requires | 218 self._requires = requires |
| 210 self._number = number | 219 self._number = number |
| 211 self._config = config | 220 self._config = config |
| 212 self._state = state | 221 self._state = state |
| 213 self._options = options | 222 self._options = options |
| 214 self._side_effect_handler = handler | 223 self._side_effect_handler = handler |
| 215 assert self._number >= 0 | 224 assert self._number >= 0 |
| 216 assert self._config is not None | 225 assert self._config is not None |
| 217 assert self._state is not None | 226 assert self._state is not None |
| 218 assert self._side_effect_handler is not None | 227 assert self._side_effect_handler is not None |
| 219 | 228 |
| 220 def Config(self, key): | 229 def Config(self, key): |
| 221 return self._config[key] | 230 return self._config[key] |
| 222 | 231 |
| 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 |
| 223 def Run(self): | 238 def Run(self): |
| 224 if self._requires: | 239 if self._requires: |
| 225 self.RestoreIfUnset(self._requires) | 240 self.RestoreIfUnset(self._requires) |
| 226 if not self._state[self._requires]: | 241 if not self._state[self._requires]: |
| 227 return | 242 return |
| 228 print ">>> Step %d: %s" % (self._number, self._text) | 243 print ">>> Step %d: %s" % (self._number, self._text) |
| 229 self.RunStep() | 244 self.RunStep() |
| 230 | 245 |
| 231 def RunStep(self): | 246 def RunStep(self): |
| 232 raise NotImplementedError | 247 raise NotImplementedError |
| 233 | 248 |
| 249 def Retry(self, cb, retry_on=None, wait_plan=None): |
| 250 """ Retry a function. |
| 251 Params: |
| 252 cb: The function to retry. |
| 253 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 |
| 255 exception is always retried. |
| 256 wait_plan: A list of waiting delays between retries in seconds. The |
| 257 maximum number of retries is len(wait_plan). |
| 258 """ |
| 259 retry_on = retry_on or (lambda x: False) |
| 260 wait_plan = list(wait_plan or []) |
| 261 wait_plan.reverse() |
| 262 while True: |
| 263 got_exception = False |
| 264 try: |
| 265 result = cb() |
| 266 except Exception: |
| 267 got_exception = True |
| 268 if got_exception or retry_on(result): |
| 269 if not wait_plan: |
| 270 raise Exception("Retried too often. Giving up.") |
| 271 wait_time = wait_plan.pop() |
| 272 print "Waiting for %f seconds." % wait_time |
| 273 self._side_effect_handler.Sleep(wait_time) |
| 274 print "Retrying..." |
| 275 else: |
| 276 return result |
| 277 |
| 234 def ReadLine(self, default=None): | 278 def ReadLine(self, default=None): |
| 235 # Don't prompt in forced mode. | 279 # Don't prompt in forced mode. |
| 236 if self._options and self._options.f and default is not None: | 280 if not self.IsManual() and default is not None: |
| 237 print "%s (forced)" % default | 281 print "%s (forced)" % default |
| 238 return default | 282 return default |
| 239 else: | 283 else: |
| 240 return self._side_effect_handler.ReadLine() | 284 return self._side_effect_handler.ReadLine() |
| 241 | 285 |
| 242 def Git(self, args="", prefix="", pipe=True): | 286 def Git(self, args="", prefix="", pipe=True, retry_on=None): |
| 243 return self._side_effect_handler.Command("git", args, prefix, pipe) | 287 cmd = lambda: self._side_effect_handler.Command("git", args, prefix, pipe) |
| 288 return self.Retry(cmd, retry_on, [5, 30]) |
| 244 | 289 |
| 245 def Editor(self, args): | 290 def Editor(self, args): |
| 246 return self._side_effect_handler.Command(os.environ["EDITOR"], args, | 291 if not self.IsForced(): |
| 247 pipe=False) | 292 return self._side_effect_handler.Command(os.environ["EDITOR"], args, |
| 293 pipe=False) |
| 248 | 294 |
| 249 def ReadURL(self, url): | 295 def ReadURL(self, url, retry_on=None, wait_plan=None): |
| 250 return self._side_effect_handler.ReadURL(url) | 296 wait_plan = wait_plan or [3, 60, 600] |
| 297 cmd = lambda: self._side_effect_handler.ReadURL(url) |
| 298 return self.Retry(cmd, retry_on, wait_plan) |
| 299 |
| 300 def GetDate(self): |
| 301 return self._side_effect_handler.GetDate() |
| 251 | 302 |
| 252 def Die(self, msg=""): | 303 def Die(self, msg=""): |
| 253 if msg != "": | 304 if msg != "": |
| 254 print "Error: %s" % msg | 305 print "Error: %s" % msg |
| 255 print "Exiting" | 306 print "Exiting" |
| 256 raise Exception(msg) | 307 raise Exception(msg) |
| 257 | 308 |
| 258 def DieInForcedMode(self, msg=""): | 309 def DieNoManualMode(self, msg=""): |
| 259 if self._options and self._options.f: | 310 if not self.IsManual(): |
| 260 msg = msg or "Not implemented in forced mode." | 311 msg = msg or "Only available in manual mode." |
| 261 self.Die(msg) | 312 self.Die(msg) |
| 262 | 313 |
| 263 def Confirm(self, msg): | 314 def Confirm(self, msg): |
| 264 print "%s [Y/n] " % msg, | 315 print "%s [Y/n] " % msg, |
| 265 answer = self.ReadLine(default="Y") | 316 answer = self.ReadLine(default="Y") |
| 266 return answer == "" or answer == "Y" or answer == "y" | 317 return answer == "" or answer == "Y" or answer == "y" |
| 267 | 318 |
| 268 def DeleteBranch(self, name): | 319 def DeleteBranch(self, name): |
| 269 git_result = self.Git("branch").strip() | 320 git_result = self.Git("branch").strip() |
| 270 for line in git_result.splitlines(): | 321 for line in git_result.splitlines(): |
| (...skipping 18 matching lines...) Expand all Loading... |
| 289 | 340 |
| 290 def RestoreIfUnset(self, var_name): | 341 def RestoreIfUnset(self, var_name): |
| 291 if self._state.get(var_name) is None: | 342 if self._state.get(var_name) is None: |
| 292 self._state[var_name] = self.Restore(var_name) | 343 self._state[var_name] = self.Restore(var_name) |
| 293 | 344 |
| 294 def InitialEnvironmentChecks(self): | 345 def InitialEnvironmentChecks(self): |
| 295 # Cancel if this is not a git checkout. | 346 # Cancel if this is not a git checkout. |
| 296 if not os.path.exists(self._config[DOT_GIT_LOCATION]): | 347 if not os.path.exists(self._config[DOT_GIT_LOCATION]): |
| 297 self.Die("This is not a git checkout, this script won't work for you.") | 348 self.Die("This is not a git checkout, this script won't work for you.") |
| 298 | 349 |
| 299 # TODO(machenbach): Don't use EDITOR in forced mode as soon as script is | |
| 300 # well tested. | |
| 301 # Cancel if EDITOR is unset or not executable. | 350 # Cancel if EDITOR is unset or not executable. |
| 302 if (not os.environ.get("EDITOR") or | 351 if (not self.IsForced() and (not os.environ.get("EDITOR") or |
| 303 Command("which", os.environ["EDITOR"]) is None): | 352 Command("which", os.environ["EDITOR"]) is None)): |
| 304 self.Die("Please set your EDITOR environment variable, you'll need it.") | 353 self.Die("Please set your EDITOR environment variable, you'll need it.") |
| 305 | 354 |
| 306 def CommonPrepare(self): | 355 def CommonPrepare(self): |
| 307 # Check for a clean workdir. | 356 # Check for a clean workdir. |
| 308 if self.Git("status -s -uno").strip() != "": | 357 if self.Git("status -s -uno").strip() != "": |
| 309 self.Die("Workspace is not clean. Please commit or undo your changes.") | 358 self.Die("Workspace is not clean. Please commit or undo your changes.") |
| 310 | 359 |
| 311 # Persist current branch. | 360 # Persist current branch. |
| 312 current_branch = "" | 361 current_branch = "" |
| 313 git_result = self.Git("status -s -b -uno").strip() | 362 git_result = self.Git("status -s -b -uno").strip() |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 362 self.RestoreIfUnset("%s%s" % (prefix, v)) | 411 self.RestoreIfUnset("%s%s" % (prefix, v)) |
| 363 | 412 |
| 364 def WaitForLGTM(self): | 413 def WaitForLGTM(self): |
| 365 print ("Please wait for an LGTM, then type \"LGTM<Return>\" to commit " | 414 print ("Please wait for an LGTM, then type \"LGTM<Return>\" to commit " |
| 366 "your change. (If you need to iterate on the patch or double check " | 415 "your change. (If you need to iterate on the patch or double check " |
| 367 "that it's sane, do so in another shell, but remember to not " | 416 "that it's sane, do so in another shell, but remember to not " |
| 368 "change the headline of the uploaded CL.") | 417 "change the headline of the uploaded CL.") |
| 369 answer = "" | 418 answer = "" |
| 370 while answer != "LGTM": | 419 while answer != "LGTM": |
| 371 print "> ", | 420 print "> ", |
| 372 # TODO(machenbach): Add default="LGTM" to avoid prompt when script is | 421 answer = self.ReadLine("LGTM" if self.IsForced() else None) |
| 373 # well tested and when prepare push cl has TBR flag. | |
| 374 answer = self.ReadLine() | |
| 375 if answer != "LGTM": | 422 if answer != "LGTM": |
| 376 print "That was not 'LGTM'." | 423 print "That was not 'LGTM'." |
| 377 | 424 |
| 378 def WaitForResolvingConflicts(self, patch_file): | 425 def WaitForResolvingConflicts(self, patch_file): |
| 379 print("Applying the patch \"%s\" failed. Either type \"ABORT<Return>\", " | 426 print("Applying the patch \"%s\" failed. Either type \"ABORT<Return>\", " |
| 380 "or resolve the conflicts, stage *all* touched files with " | 427 "or resolve the conflicts, stage *all* touched files with " |
| 381 "'git add', and type \"RESOLVED<Return>\"") | 428 "'git add', and type \"RESOLVED<Return>\"") |
| 382 self.DieInForcedMode() | 429 self.DieNoManualMode() |
| 383 answer = "" | 430 answer = "" |
| 384 while answer != "RESOLVED": | 431 while answer != "RESOLVED": |
| 385 if answer == "ABORT": | 432 if answer == "ABORT": |
| 386 self.Die("Applying the patch failed.") | 433 self.Die("Applying the patch failed.") |
| 387 if answer != "": | 434 if answer != "": |
| 388 print "That was not 'RESOLVED' or 'ABORT'." | 435 print "That was not 'RESOLVED' or 'ABORT'." |
| 389 print "> ", | 436 print "> ", |
| 390 answer = self.ReadLine() | 437 answer = self.ReadLine() |
| 391 | 438 |
| 392 # Takes a file containing the patch to apply as first argument. | 439 # Takes a file containing the patch to apply as first argument. |
| 393 def ApplyPatch(self, patch_file, reverse_patch=""): | 440 def ApplyPatch(self, patch_file, reverse_patch=""): |
| 394 args = "apply --index --reject %s \"%s\"" % (reverse_patch, patch_file) | 441 args = "apply --index --reject %s \"%s\"" % (reverse_patch, patch_file) |
| 395 if self.Git(args) is None: | 442 if self.Git(args) is None: |
| 396 self.WaitForResolvingConflicts(patch_file) | 443 self.WaitForResolvingConflicts(patch_file) |
| 397 | 444 |
| 398 | 445 |
| 399 class UploadStep(Step): | 446 class UploadStep(Step): |
| 400 MESSAGE = "Upload for code review." | 447 MESSAGE = "Upload for code review." |
| 401 | 448 |
| 402 def RunStep(self): | 449 def RunStep(self): |
| 403 if self._options.r: | 450 if self._options.r: |
| 404 print "Using account %s for review." % self._options.r | 451 print "Using account %s for review." % self._options.r |
| 405 reviewer = self._options.r | 452 reviewer = self._options.r |
| 406 else: | 453 else: |
| 407 print "Please enter the email address of a V8 reviewer for your patch: ", | 454 print "Please enter the email address of a V8 reviewer for your patch: ", |
| 408 self.DieInForcedMode("A reviewer must be specified in forced mode.") | 455 self.DieNoManualMode("A reviewer must be specified in forced mode.") |
| 409 reviewer = self.ReadLine() | 456 reviewer = self.ReadLine() |
| 410 force_flag = " -f" if self._options.f else "" | 457 force_flag = " -f" if not self.IsManual() else "" |
| 411 args = "cl upload -r \"%s\" --send-mail%s" % (reviewer, force_flag) | 458 args = "cl upload -r \"%s\" --send-mail%s" % (reviewer, force_flag) |
| 412 # TODO(machenbach): Check output in forced mode. Verify that all required | 459 # TODO(machenbach): Check output in forced mode. Verify that all required |
| 413 # base files were uploaded, if not retry. | 460 # base files were uploaded, if not retry. |
| 414 if self.Git(args, pipe=False) is None: | 461 if self.Git(args, pipe=False) is None: |
| 415 self.Die("'git cl upload' failed, please try again.") | 462 self.Die("'git cl upload' failed, please try again.") |
| 416 | 463 |
| 417 | 464 |
| 418 def MakeStep(step_class=Step, number=0, state=None, config=None, | 465 def MakeStep(step_class=Step, number=0, state=None, config=None, |
| 419 options=None, side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER): | 466 options=None, side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER): |
| 420 # Allow to pass in empty dictionaries. | 467 # Allow to pass in empty dictionaries. |
| (...skipping 19 matching lines...) Expand all Loading... |
| 440 options, | 487 options, |
| 441 side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER): | 488 side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER): |
| 442 state = {} | 489 state = {} |
| 443 steps = [] | 490 steps = [] |
| 444 for (number, step_class) in enumerate(step_classes): | 491 for (number, step_class) in enumerate(step_classes): |
| 445 steps.append(MakeStep(step_class, number, state, config, | 492 steps.append(MakeStep(step_class, number, state, config, |
| 446 options, side_effect_handler)) | 493 options, side_effect_handler)) |
| 447 | 494 |
| 448 for step in steps[options.s:]: | 495 for step in steps[options.s:]: |
| 449 step.Run() | 496 step.Run() |
| OLD | NEW |