| 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 datetime |
| 30 import json | |
| 31 import os | 30 import os |
| 32 import re | 31 import re |
| 33 import subprocess | 32 import subprocess |
| 34 import sys | 33 import sys |
| 35 import textwrap | 34 import textwrap |
| 36 import time | 35 import time |
| 37 import urllib2 | 36 import urllib2 |
| 38 | 37 |
| 39 from git_recipes import GitRecipesMixin | |
| 40 | |
| 41 PERSISTFILE_BASENAME = "PERSISTFILE_BASENAME" | 38 PERSISTFILE_BASENAME = "PERSISTFILE_BASENAME" |
| 42 TEMP_BRANCH = "TEMP_BRANCH" | 39 TEMP_BRANCH = "TEMP_BRANCH" |
| 43 BRANCHNAME = "BRANCHNAME" | 40 BRANCHNAME = "BRANCHNAME" |
| 44 DOT_GIT_LOCATION = "DOT_GIT_LOCATION" | 41 DOT_GIT_LOCATION = "DOT_GIT_LOCATION" |
| 45 VERSION_FILE = "VERSION_FILE" | 42 VERSION_FILE = "VERSION_FILE" |
| 46 CHANGELOG_FILE = "CHANGELOG_FILE" | 43 CHANGELOG_FILE = "CHANGELOG_FILE" |
| 47 CHANGELOG_ENTRY_FILE = "CHANGELOG_ENTRY_FILE" | 44 CHANGELOG_ENTRY_FILE = "CHANGELOG_ENTRY_FILE" |
| 48 COMMITMSG_FILE = "COMMITMSG_FILE" | 45 COMMITMSG_FILE = "COMMITMSG_FILE" |
| 49 PATCH_FILE = "PATCH_FILE" | 46 PATCH_FILE = "PATCH_FILE" |
| 50 | 47 |
| (...skipping 164 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 215 def GetDate(self): | 212 def GetDate(self): |
| 216 return datetime.date.today().strftime("%Y-%m-%d") | 213 return datetime.date.today().strftime("%Y-%m-%d") |
| 217 | 214 |
| 218 DEFAULT_SIDE_EFFECT_HANDLER = SideEffectHandler() | 215 DEFAULT_SIDE_EFFECT_HANDLER = SideEffectHandler() |
| 219 | 216 |
| 220 | 217 |
| 221 class NoRetryException(Exception): | 218 class NoRetryException(Exception): |
| 222 pass | 219 pass |
| 223 | 220 |
| 224 | 221 |
| 225 class GitFailedException(Exception): | |
| 226 pass | |
| 227 | |
| 228 | |
| 229 class CommonOptions(object): | 222 class CommonOptions(object): |
| 230 def __init__(self, options, manual=True): | 223 def __init__(self, options, manual=True): |
| 231 self.requires_editor = True | 224 self.requires_editor = True |
| 232 self.wait_for_lgtm = True | 225 self.wait_for_lgtm = True |
| 233 self.s = options.s | 226 self.s = options.s |
| 234 self.force_readline_defaults = not manual | 227 self.force_readline_defaults = not manual |
| 235 self.force_upload = not manual | 228 self.force_upload = not manual |
| 236 self.manual = manual | 229 self.manual = manual |
| 237 self.reviewer = getattr(options, 'reviewer', "") | |
| 238 self.author = getattr(options, 'a', "") | |
| 239 | 230 |
| 240 | 231 |
| 241 class Step(GitRecipesMixin): | 232 class Step(object): |
| 242 def __init__(self, text, requires, number, config, state, options, handler): | 233 def __init__(self, text, requires, number, config, state, options, handler): |
| 243 self._text = text | 234 self._text = text |
| 244 self._requires = requires | 235 self._requires = requires |
| 245 self._number = number | 236 self._number = number |
| 246 self._config = config | 237 self._config = config |
| 247 self._state = state | 238 self._state = state |
| 248 self._options = options | 239 self._options = options |
| 249 self._side_effect_handler = handler | 240 self._side_effect_handler = handler |
| 250 assert self._number >= 0 | 241 assert self._number >= 0 |
| 251 assert self._config is not None | 242 assert self._config is not None |
| 252 assert self._state is not None | 243 assert self._state is not None |
| 253 assert self._side_effect_handler is not None | 244 assert self._side_effect_handler is not None |
| 254 assert isinstance(options, CommonOptions) | 245 assert isinstance(options, CommonOptions) |
| 255 | 246 |
| 256 def __getitem__(self, key): | |
| 257 # Convenience method to allow direct [] access on step classes for | |
| 258 # manipulating the backed state dict. | |
| 259 return self._state[key] | |
| 260 | |
| 261 def __setitem__(self, key, value): | |
| 262 # Convenience method to allow direct [] access on step classes for | |
| 263 # manipulating the backed state dict. | |
| 264 self._state[key] = value | |
| 265 | |
| 266 def Config(self, key): | 247 def Config(self, key): |
| 267 return self._config[key] | 248 return self._config[key] |
| 268 | 249 |
| 269 def Run(self): | 250 def Run(self): |
| 270 # Restore state. | 251 if self._requires: |
| 271 state_file = "%s-state.json" % self._config[PERSISTFILE_BASENAME] | 252 self.RestoreIfUnset(self._requires) |
| 272 if not self._state and os.path.exists(state_file): | 253 if not self._state[self._requires]: |
| 273 self._state.update(json.loads(FileToText(state_file))) | 254 return |
| 274 | |
| 275 # Skip step if requirement is not met. | |
| 276 if self._requires and not self._state.get(self._requires): | |
| 277 return | |
| 278 | |
| 279 print ">>> Step %d: %s" % (self._number, self._text) | 255 print ">>> Step %d: %s" % (self._number, self._text) |
| 280 self.RunStep() | 256 self.RunStep() |
| 281 | 257 |
| 282 # Persist state. | |
| 283 TextToFile(json.dumps(self._state), state_file) | |
| 284 | |
| 285 def RunStep(self): | 258 def RunStep(self): |
| 286 raise NotImplementedError | 259 raise NotImplementedError |
| 287 | 260 |
| 288 def Retry(self, cb, retry_on=None, wait_plan=None): | 261 def Retry(self, cb, retry_on=None, wait_plan=None): |
| 289 """ Retry a function. | 262 """ Retry a function. |
| 290 Params: | 263 Params: |
| 291 cb: The function to retry. | 264 cb: The function to retry. |
| 292 retry_on: A callback that takes the result of the function and returns | 265 retry_on: A callback that takes the result of the function and returns |
| 293 True if the function should be retried. A function throwing an | 266 True if the function should be retried. A function throwing an |
| 294 exception is always retried. | 267 exception is always retried. |
| (...skipping 24 matching lines...) Expand all Loading... |
| 319 def ReadLine(self, default=None): | 292 def ReadLine(self, default=None): |
| 320 # Don't prompt in forced mode. | 293 # Don't prompt in forced mode. |
| 321 if self._options.force_readline_defaults and default is not None: | 294 if self._options.force_readline_defaults and default is not None: |
| 322 print "%s (forced)" % default | 295 print "%s (forced)" % default |
| 323 return default | 296 return default |
| 324 else: | 297 else: |
| 325 return self._side_effect_handler.ReadLine() | 298 return self._side_effect_handler.ReadLine() |
| 326 | 299 |
| 327 def Git(self, args="", prefix="", pipe=True, retry_on=None): | 300 def Git(self, args="", prefix="", pipe=True, retry_on=None): |
| 328 cmd = lambda: self._side_effect_handler.Command("git", args, prefix, pipe) | 301 cmd = lambda: self._side_effect_handler.Command("git", args, prefix, pipe) |
| 329 result = self.Retry(cmd, retry_on, [5, 30]) | |
| 330 if result is None: | |
| 331 raise GitFailedException("'git %s' failed." % args) | |
| 332 return result | |
| 333 | |
| 334 def SVN(self, args="", prefix="", pipe=True, retry_on=None): | |
| 335 cmd = lambda: self._side_effect_handler.Command("svn", args, prefix, pipe) | |
| 336 return self.Retry(cmd, retry_on, [5, 30]) | 302 return self.Retry(cmd, retry_on, [5, 30]) |
| 337 | 303 |
| 338 def Editor(self, args): | 304 def Editor(self, args): |
| 339 if self._options.requires_editor: | 305 if self._options.requires_editor: |
| 340 return self._side_effect_handler.Command(os.environ["EDITOR"], args, | 306 return self._side_effect_handler.Command(os.environ["EDITOR"], args, |
| 341 pipe=False) | 307 pipe=False) |
| 342 | 308 |
| 343 def ReadURL(self, url, params=None, retry_on=None, wait_plan=None): | 309 def ReadURL(self, url, params=None, retry_on=None, wait_plan=None): |
| 344 wait_plan = wait_plan or [3, 60, 600] | 310 wait_plan = wait_plan or [3, 60, 600] |
| 345 cmd = lambda: self._side_effect_handler.ReadURL(url, params) | 311 cmd = lambda: self._side_effect_handler.ReadURL(url, params) |
| (...skipping 12 matching lines...) Expand all Loading... |
| 358 if not self._options.manual: | 324 if not self._options.manual: |
| 359 msg = msg or "Only available in manual mode." | 325 msg = msg or "Only available in manual mode." |
| 360 self.Die(msg) | 326 self.Die(msg) |
| 361 | 327 |
| 362 def Confirm(self, msg): | 328 def Confirm(self, msg): |
| 363 print "%s [Y/n] " % msg, | 329 print "%s [Y/n] " % msg, |
| 364 answer = self.ReadLine(default="Y") | 330 answer = self.ReadLine(default="Y") |
| 365 return answer == "" or answer == "Y" or answer == "y" | 331 return answer == "" or answer == "Y" or answer == "y" |
| 366 | 332 |
| 367 def DeleteBranch(self, name): | 333 def DeleteBranch(self, name): |
| 368 for line in self.GitBranch().splitlines(): | 334 git_result = self.Git("branch").strip() |
| 335 for line in git_result.splitlines(): |
| 369 if re.match(r".*\s+%s$" % name, line): | 336 if re.match(r".*\s+%s$" % name, line): |
| 370 msg = "Branch %s exists, do you want to delete it?" % name | 337 msg = "Branch %s exists, do you want to delete it?" % name |
| 371 if self.Confirm(msg): | 338 if self.Confirm(msg): |
| 372 self.GitDeleteBranch(name) | 339 if self.Git("branch -D %s" % name) is None: |
| 340 self.Die("Deleting branch '%s' failed." % name) |
| 373 print "Branch %s deleted." % name | 341 print "Branch %s deleted." % name |
| 374 else: | 342 else: |
| 375 msg = "Can't continue. Please delete branch %s and try again." % name | 343 msg = "Can't continue. Please delete branch %s and try again." % name |
| 376 self.Die(msg) | 344 self.Die(msg) |
| 377 | 345 |
| 346 def Persist(self, var, value): |
| 347 value = value or "__EMPTY__" |
| 348 TextToFile(value, "%s-%s" % (self._config[PERSISTFILE_BASENAME], var)) |
| 349 |
| 350 def Restore(self, var): |
| 351 value = FileToText("%s-%s" % (self._config[PERSISTFILE_BASENAME], var)) |
| 352 value = value or self.Die("Variable '%s' could not be restored." % var) |
| 353 return "" if value == "__EMPTY__" else value |
| 354 |
| 355 def RestoreIfUnset(self, var_name): |
| 356 if self._state.get(var_name) is None: |
| 357 self._state[var_name] = self.Restore(var_name) |
| 358 |
| 378 def InitialEnvironmentChecks(self): | 359 def InitialEnvironmentChecks(self): |
| 379 # Cancel if this is not a git checkout. | 360 # Cancel if this is not a git checkout. |
| 380 if not os.path.exists(self._config[DOT_GIT_LOCATION]): | 361 if not os.path.exists(self._config[DOT_GIT_LOCATION]): |
| 381 self.Die("This is not a git checkout, this script won't work for you.") | 362 self.Die("This is not a git checkout, this script won't work for you.") |
| 382 | 363 |
| 383 # Cancel if EDITOR is unset or not executable. | 364 # Cancel if EDITOR is unset or not executable. |
| 384 if (self._options.requires_editor and (not os.environ.get("EDITOR") or | 365 if (self._options.requires_editor and (not os.environ.get("EDITOR") or |
| 385 Command("which", os.environ["EDITOR"]) is None)): | 366 Command("which", os.environ["EDITOR"]) is None)): |
| 386 self.Die("Please set your EDITOR environment variable, you'll need it.") | 367 self.Die("Please set your EDITOR environment variable, you'll need it.") |
| 387 | 368 |
| 388 def CommonPrepare(self): | 369 def CommonPrepare(self): |
| 389 # Check for a clean workdir. | 370 # Check for a clean workdir. |
| 390 if not self.GitIsWorkdirClean(): | 371 if self.Git("status -s -uno").strip() != "": |
| 391 self.Die("Workspace is not clean. Please commit or undo your changes.") | 372 self.Die("Workspace is not clean. Please commit or undo your changes.") |
| 392 | 373 |
| 393 # Persist current branch. | 374 # Persist current branch. |
| 394 self["current_branch"] = self.GitCurrentBranch() | 375 current_branch = "" |
| 376 git_result = self.Git("status -s -b -uno").strip() |
| 377 for line in git_result.splitlines(): |
| 378 match = re.match(r"^## (.+)", line) |
| 379 if match: |
| 380 current_branch = match.group(1) |
| 381 break |
| 382 self.Persist("current_branch", current_branch) |
| 395 | 383 |
| 396 # Fetch unfetched revisions. | 384 # Fetch unfetched revisions. |
| 397 self.GitSVNFetch() | 385 if self.Git("svn fetch") is None: |
| 386 self.Die("'git svn fetch' failed.") |
| 398 | 387 |
| 399 def PrepareBranch(self): | 388 def PrepareBranch(self): |
| 400 # Get ahold of a safe temporary branch and check it out. | 389 # Get ahold of a safe temporary branch and check it out. |
| 401 if self["current_branch"] != self._config[TEMP_BRANCH]: | 390 self.RestoreIfUnset("current_branch") |
| 391 if self._state["current_branch"] != self._config[TEMP_BRANCH]: |
| 402 self.DeleteBranch(self._config[TEMP_BRANCH]) | 392 self.DeleteBranch(self._config[TEMP_BRANCH]) |
| 403 self.GitCreateBranch(self._config[TEMP_BRANCH]) | 393 self.Git("checkout -b %s" % self._config[TEMP_BRANCH]) |
| 404 | 394 |
| 405 # Delete the branch that will be created later if it exists already. | 395 # Delete the branch that will be created later if it exists already. |
| 406 self.DeleteBranch(self._config[BRANCHNAME]) | 396 self.DeleteBranch(self._config[BRANCHNAME]) |
| 407 | 397 |
| 408 def CommonCleanup(self): | 398 def CommonCleanup(self): |
| 409 self.GitCheckout(self["current_branch"]) | 399 self.RestoreIfUnset("current_branch") |
| 410 if self._config[TEMP_BRANCH] != self["current_branch"]: | 400 self.Git("checkout -f %s" % self._state["current_branch"]) |
| 411 self.GitDeleteBranch(self._config[TEMP_BRANCH]) | 401 if self._config[TEMP_BRANCH] != self._state["current_branch"]: |
| 412 if self._config[BRANCHNAME] != self["current_branch"]: | 402 self.Git("branch -D %s" % self._config[TEMP_BRANCH]) |
| 413 self.GitDeleteBranch(self._config[BRANCHNAME]) | 403 if self._config[BRANCHNAME] != self._state["current_branch"]: |
| 404 self.Git("branch -D %s" % self._config[BRANCHNAME]) |
| 414 | 405 |
| 415 # Clean up all temporary files. | 406 # Clean up all temporary files. |
| 416 Command("rm", "-f %s*" % self._config[PERSISTFILE_BASENAME]) | 407 Command("rm", "-f %s*" % self._config[PERSISTFILE_BASENAME]) |
| 417 | 408 |
| 418 def ReadAndPersistVersion(self, prefix=""): | 409 def ReadAndPersistVersion(self, prefix=""): |
| 419 def ReadAndPersist(var_name, def_name): | 410 def ReadAndPersist(var_name, def_name): |
| 420 match = re.match(r"^#define %s\s+(\d*)" % def_name, line) | 411 match = re.match(r"^#define %s\s+(\d*)" % def_name, line) |
| 421 if match: | 412 if match: |
| 422 value = match.group(1) | 413 value = match.group(1) |
| 423 self["%s%s" % (prefix, var_name)] = value | 414 self.Persist("%s%s" % (prefix, var_name), value) |
| 415 self._state["%s%s" % (prefix, var_name)] = value |
| 424 for line in LinesInFile(self._config[VERSION_FILE]): | 416 for line in LinesInFile(self._config[VERSION_FILE]): |
| 425 for (var_name, def_name) in [("major", "MAJOR_VERSION"), | 417 for (var_name, def_name) in [("major", "MAJOR_VERSION"), |
| 426 ("minor", "MINOR_VERSION"), | 418 ("minor", "MINOR_VERSION"), |
| 427 ("build", "BUILD_NUMBER"), | 419 ("build", "BUILD_NUMBER"), |
| 428 ("patch", "PATCH_LEVEL")]: | 420 ("patch", "PATCH_LEVEL")]: |
| 429 ReadAndPersist(var_name, def_name) | 421 ReadAndPersist(var_name, def_name) |
| 430 | 422 |
| 423 def RestoreVersionIfUnset(self, prefix=""): |
| 424 for v in ["major", "minor", "build", "patch"]: |
| 425 self.RestoreIfUnset("%s%s" % (prefix, v)) |
| 426 |
| 431 def WaitForLGTM(self): | 427 def WaitForLGTM(self): |
| 432 print ("Please wait for an LGTM, then type \"LGTM<Return>\" to commit " | 428 print ("Please wait for an LGTM, then type \"LGTM<Return>\" to commit " |
| 433 "your change. (If you need to iterate on the patch or double check " | 429 "your change. (If you need to iterate on the patch or double check " |
| 434 "that it's sane, do so in another shell, but remember to not " | 430 "that it's sane, do so in another shell, but remember to not " |
| 435 "change the headline of the uploaded CL.") | 431 "change the headline of the uploaded CL.") |
| 436 answer = "" | 432 answer = "" |
| 437 while answer != "LGTM": | 433 while answer != "LGTM": |
| 438 print "> ", | 434 print "> ", |
| 439 answer = self.ReadLine(None if self._options.wait_for_lgtm else "LGTM") | 435 answer = self.ReadLine(None if self._options.wait_for_lgtm else "LGTM") |
| 440 if answer != "LGTM": | 436 if answer != "LGTM": |
| 441 print "That was not 'LGTM'." | 437 print "That was not 'LGTM'." |
| 442 | 438 |
| 443 def WaitForResolvingConflicts(self, patch_file): | 439 def WaitForResolvingConflicts(self, patch_file): |
| 444 print("Applying the patch \"%s\" failed. Either type \"ABORT<Return>\", " | 440 print("Applying the patch \"%s\" failed. Either type \"ABORT<Return>\", " |
| 445 "or resolve the conflicts, stage *all* touched files with " | 441 "or resolve the conflicts, stage *all* touched files with " |
| 446 "'git add', and type \"RESOLVED<Return>\"") | 442 "'git add', and type \"RESOLVED<Return>\"") |
| 447 self.DieNoManualMode() | 443 self.DieNoManualMode() |
| 448 answer = "" | 444 answer = "" |
| 449 while answer != "RESOLVED": | 445 while answer != "RESOLVED": |
| 450 if answer == "ABORT": | 446 if answer == "ABORT": |
| 451 self.Die("Applying the patch failed.") | 447 self.Die("Applying the patch failed.") |
| 452 if answer != "": | 448 if answer != "": |
| 453 print "That was not 'RESOLVED' or 'ABORT'." | 449 print "That was not 'RESOLVED' or 'ABORT'." |
| 454 print "> ", | 450 print "> ", |
| 455 answer = self.ReadLine() | 451 answer = self.ReadLine() |
| 456 | 452 |
| 457 # Takes a file containing the patch to apply as first argument. | 453 # Takes a file containing the patch to apply as first argument. |
| 458 def ApplyPatch(self, patch_file, revert=False): | 454 def ApplyPatch(self, patch_file, reverse_patch=""): |
| 459 try: | 455 args = "apply --index --reject %s \"%s\"" % (reverse_patch, patch_file) |
| 460 self.GitApplyPatch(patch_file, revert) | 456 if self.Git(args) is None: |
| 461 except GitFailedException: | |
| 462 self.WaitForResolvingConflicts(patch_file) | 457 self.WaitForResolvingConflicts(patch_file) |
| 463 | 458 |
| 464 def FindLastTrunkPush(self, parent_hash=""): | |
| 465 push_pattern = "^Version [[:digit:]]*\.[[:digit:]]*\.[[:digit:]]* (based" | |
| 466 branch = "" if parent_hash else "svn/trunk" | |
| 467 return self.GitLog(n=1, format="%H", grep=push_pattern, | |
| 468 parent_hash=parent_hash, branch=branch) | |
| 469 | |
| 470 | 459 |
| 471 class UploadStep(Step): | 460 class UploadStep(Step): |
| 472 MESSAGE = "Upload for code review." | 461 MESSAGE = "Upload for code review." |
| 473 | 462 |
| 474 def RunStep(self): | 463 def RunStep(self): |
| 475 if self._options.reviewer: | 464 if self._options.r: |
| 476 print "Using account %s for review." % self._options.reviewer | 465 print "Using account %s for review." % self._options.r |
| 477 reviewer = self._options.reviewer | 466 reviewer = self._options.r |
| 478 else: | 467 else: |
| 479 print "Please enter the email address of a V8 reviewer for your patch: ", | 468 print "Please enter the email address of a V8 reviewer for your patch: ", |
| 480 self.DieNoManualMode("A reviewer must be specified in forced mode.") | 469 self.DieNoManualMode("A reviewer must be specified in forced mode.") |
| 481 reviewer = self.ReadLine() | 470 reviewer = self.ReadLine() |
| 482 self.GitUpload(reviewer, self._options.author, self._options.force_upload) | 471 force_flag = " -f" if self._options.force_upload else "" |
| 472 args = "cl upload -r \"%s\" --send-mail%s" % (reviewer, force_flag) |
| 473 # TODO(machenbach): Check output in forced mode. Verify that all required |
| 474 # base files were uploaded, if not retry. |
| 475 if self.Git(args, pipe=False) is None: |
| 476 self.Die("'git cl upload' failed, please try again.") |
| 483 | 477 |
| 484 | 478 |
| 485 def MakeStep(step_class=Step, number=0, state=None, config=None, | 479 def MakeStep(step_class=Step, number=0, state=None, config=None, |
| 486 options=None, side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER): | 480 options=None, side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER): |
| 487 # Allow to pass in empty dictionaries. | 481 # Allow to pass in empty dictionaries. |
| 488 state = state if state is not None else {} | 482 state = state if state is not None else {} |
| 489 config = config if config is not None else {} | 483 config = config if config is not None else {} |
| 490 | 484 |
| 491 try: | 485 try: |
| 492 message = step_class.MESSAGE | 486 message = step_class.MESSAGE |
| 493 except AttributeError: | 487 except AttributeError: |
| 494 message = step_class.__name__ | 488 message = step_class.__name__ |
| 495 try: | 489 try: |
| 496 requires = step_class.REQUIRES | 490 requires = step_class.REQUIRES |
| 497 except AttributeError: | 491 except AttributeError: |
| 498 requires = None | 492 requires = None |
| 499 | 493 |
| 500 return step_class(message, requires, number=number, config=config, | 494 return step_class(message, requires, number=number, config=config, |
| 501 state=state, options=options, | 495 state=state, options=options, |
| 502 handler=side_effect_handler) | 496 handler=side_effect_handler) |
| 503 | 497 |
| 504 | 498 |
| 505 def RunScript(step_classes, | 499 def RunScript(step_classes, |
| 506 config, | 500 config, |
| 507 options, | 501 options, |
| 508 side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER): | 502 side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER): |
| 509 state_file = "%s-state.json" % config[PERSISTFILE_BASENAME] | |
| 510 if options.s == 0 and os.path.exists(state_file): | |
| 511 os.remove(state_file) | |
| 512 state = {} | 503 state = {} |
| 513 steps = [] | 504 steps = [] |
| 514 for (number, step_class) in enumerate(step_classes): | 505 for (number, step_class) in enumerate(step_classes): |
| 515 steps.append(MakeStep(step_class, number, state, config, | 506 steps.append(MakeStep(step_class, number, state, config, |
| 516 options, side_effect_handler)) | 507 options, side_effect_handler)) |
| 517 | 508 |
| 518 for step in steps[options.s:]: | 509 for step in steps[options.s:]: |
| 519 step.Run() | 510 step.Run() |
| OLD | NEW |