Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(389)

Side by Side Diff: tools/push-to-trunk/common_includes.py

Issue 170583002: Refactor persisting state in push and merge scripts. (Closed) Base URL: https://v8.googlecode.com/svn/branches/bleeding_edge
Patch Set: Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « tools/push-to-trunk/auto_roll.py ('k') | tools/push-to-trunk/merge_to_branch.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
30 import os 31 import os
31 import re 32 import re
32 import subprocess 33 import subprocess
33 import sys 34 import sys
34 import textwrap 35 import textwrap
35 import time 36 import time
36 import urllib2 37 import urllib2
37 38
38 PERSISTFILE_BASENAME = "PERSISTFILE_BASENAME" 39 PERSISTFILE_BASENAME = "PERSISTFILE_BASENAME"
39 TEMP_BRANCH = "TEMP_BRANCH" 40 TEMP_BRANCH = "TEMP_BRANCH"
(...skipping 199 matching lines...) Expand 10 before | Expand all | Expand 10 after
239 self._config = config 240 self._config = config
240 self._state = state 241 self._state = state
241 self._options = options 242 self._options = options
242 self._side_effect_handler = handler 243 self._side_effect_handler = handler
243 assert self._number >= 0 244 assert self._number >= 0
244 assert self._config is not None 245 assert self._config is not None
245 assert self._state is not None 246 assert self._state is not None
246 assert self._side_effect_handler is not None 247 assert self._side_effect_handler is not None
247 assert isinstance(options, CommonOptions) 248 assert isinstance(options, CommonOptions)
248 249
250 def __getitem__(self, key):
251 # Convenience method to allow direct [] access on step classes for
252 # manipulating the backed state dict.
253 return self._state[key]
254
255 def __setitem__(self, key, value):
256 # Convenience method to allow direct [] access on step classes for
257 # manipulating the backed state dict.
258 self._state[key] = value
259
249 def Config(self, key): 260 def Config(self, key):
250 return self._config[key] 261 return self._config[key]
251 262
252 def Run(self): 263 def Run(self):
253 if self._requires: 264 # Restore state.
254 self.RestoreIfUnset(self._requires) 265 state_file = "%s-state.json" % self._config[PERSISTFILE_BASENAME]
255 if not self._state[self._requires]: 266 if not self._state and os.path.exists(state_file):
256 return 267 self._state.update(json.loads(FileToText(state_file)))
268
269 # Skip step if requirement is not met.
270 if self._requires and not self._state.get(self._requires):
271 return
272
257 print ">>> Step %d: %s" % (self._number, self._text) 273 print ">>> Step %d: %s" % (self._number, self._text)
258 self.RunStep() 274 self.RunStep()
259 275
276 # Persist state.
277 TextToFile(json.dumps(self._state), state_file)
278
260 def RunStep(self): 279 def RunStep(self):
261 raise NotImplementedError 280 raise NotImplementedError
262 281
263 def Retry(self, cb, retry_on=None, wait_plan=None): 282 def Retry(self, cb, retry_on=None, wait_plan=None):
264 """ Retry a function. 283 """ Retry a function.
265 Params: 284 Params:
266 cb: The function to retry. 285 cb: The function to retry.
267 retry_on: A callback that takes the result of the function and returns 286 retry_on: A callback that takes the result of the function and returns
268 True if the function should be retried. A function throwing an 287 True if the function should be retried. A function throwing an
269 exception is always retried. 288 exception is always retried.
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after
342 if re.match(r".*\s+%s$" % name, line): 361 if re.match(r".*\s+%s$" % name, line):
343 msg = "Branch %s exists, do you want to delete it?" % name 362 msg = "Branch %s exists, do you want to delete it?" % name
344 if self.Confirm(msg): 363 if self.Confirm(msg):
345 if self.Git("branch -D %s" % name) is None: 364 if self.Git("branch -D %s" % name) is None:
346 self.Die("Deleting branch '%s' failed." % name) 365 self.Die("Deleting branch '%s' failed." % name)
347 print "Branch %s deleted." % name 366 print "Branch %s deleted." % name
348 else: 367 else:
349 msg = "Can't continue. Please delete branch %s and try again." % name 368 msg = "Can't continue. Please delete branch %s and try again." % name
350 self.Die(msg) 369 self.Die(msg)
351 370
352 def Persist(self, var, value):
353 value = value or "__EMPTY__"
354 TextToFile(value, "%s-%s" % (self._config[PERSISTFILE_BASENAME], var))
355
356 def Restore(self, var):
357 value = FileToText("%s-%s" % (self._config[PERSISTFILE_BASENAME], var))
358 value = value or self.Die("Variable '%s' could not be restored." % var)
359 return "" if value == "__EMPTY__" else value
360
361 def RestoreIfUnset(self, var_name):
362 if self._state.get(var_name) is None:
363 self._state[var_name] = self.Restore(var_name)
364
365 def InitialEnvironmentChecks(self): 371 def InitialEnvironmentChecks(self):
366 # Cancel if this is not a git checkout. 372 # Cancel if this is not a git checkout.
367 if not os.path.exists(self._config[DOT_GIT_LOCATION]): 373 if not os.path.exists(self._config[DOT_GIT_LOCATION]):
368 self.Die("This is not a git checkout, this script won't work for you.") 374 self.Die("This is not a git checkout, this script won't work for you.")
369 375
370 # Cancel if EDITOR is unset or not executable. 376 # Cancel if EDITOR is unset or not executable.
371 if (self._options.requires_editor and (not os.environ.get("EDITOR") or 377 if (self._options.requires_editor and (not os.environ.get("EDITOR") or
372 Command("which", os.environ["EDITOR"]) is None)): 378 Command("which", os.environ["EDITOR"]) is None)):
373 self.Die("Please set your EDITOR environment variable, you'll need it.") 379 self.Die("Please set your EDITOR environment variable, you'll need it.")
374 380
375 def CommonPrepare(self): 381 def CommonPrepare(self):
376 # Check for a clean workdir. 382 # Check for a clean workdir.
377 if self.Git("status -s -uno").strip() != "": 383 if self.Git("status -s -uno").strip() != "":
378 self.Die("Workspace is not clean. Please commit or undo your changes.") 384 self.Die("Workspace is not clean. Please commit or undo your changes.")
379 385
380 # Persist current branch. 386 # Persist current branch.
381 current_branch = "" 387 self["current_branch"] = ""
382 git_result = self.Git("status -s -b -uno").strip() 388 git_result = self.Git("status -s -b -uno").strip()
383 for line in git_result.splitlines(): 389 for line in git_result.splitlines():
384 match = re.match(r"^## (.+)", line) 390 match = re.match(r"^## (.+)", line)
385 if match: 391 if match:
386 current_branch = match.group(1) 392 self["current_branch"] = match.group(1)
387 break 393 break
388 self.Persist("current_branch", current_branch)
389 394
390 # Fetch unfetched revisions. 395 # Fetch unfetched revisions.
391 if self.Git("svn fetch") is None: 396 if self.Git("svn fetch") is None:
392 self.Die("'git svn fetch' failed.") 397 self.Die("'git svn fetch' failed.")
393 398
394 def PrepareBranch(self): 399 def PrepareBranch(self):
395 # Get ahold of a safe temporary branch and check it out. 400 # Get ahold of a safe temporary branch and check it out.
396 self.RestoreIfUnset("current_branch") 401 if self["current_branch"] != self._config[TEMP_BRANCH]:
397 if self._state["current_branch"] != self._config[TEMP_BRANCH]:
398 self.DeleteBranch(self._config[TEMP_BRANCH]) 402 self.DeleteBranch(self._config[TEMP_BRANCH])
399 self.Git("checkout -b %s" % self._config[TEMP_BRANCH]) 403 self.Git("checkout -b %s" % self._config[TEMP_BRANCH])
400 404
401 # Delete the branch that will be created later if it exists already. 405 # Delete the branch that will be created later if it exists already.
402 self.DeleteBranch(self._config[BRANCHNAME]) 406 self.DeleteBranch(self._config[BRANCHNAME])
403 407
404 def CommonCleanup(self): 408 def CommonCleanup(self):
405 self.RestoreIfUnset("current_branch") 409 self.Git("checkout -f %s" % self["current_branch"])
406 self.Git("checkout -f %s" % self._state["current_branch"]) 410 if self._config[TEMP_BRANCH] != self["current_branch"]:
407 if self._config[TEMP_BRANCH] != self._state["current_branch"]:
408 self.Git("branch -D %s" % self._config[TEMP_BRANCH]) 411 self.Git("branch -D %s" % self._config[TEMP_BRANCH])
409 if self._config[BRANCHNAME] != self._state["current_branch"]: 412 if self._config[BRANCHNAME] != self["current_branch"]:
410 self.Git("branch -D %s" % self._config[BRANCHNAME]) 413 self.Git("branch -D %s" % self._config[BRANCHNAME])
411 414
412 # Clean up all temporary files. 415 # Clean up all temporary files.
413 Command("rm", "-f %s*" % self._config[PERSISTFILE_BASENAME]) 416 Command("rm", "-f %s*" % self._config[PERSISTFILE_BASENAME])
414 417
415 def ReadAndPersistVersion(self, prefix=""): 418 def ReadAndPersistVersion(self, prefix=""):
416 def ReadAndPersist(var_name, def_name): 419 def ReadAndPersist(var_name, def_name):
417 match = re.match(r"^#define %s\s+(\d*)" % def_name, line) 420 match = re.match(r"^#define %s\s+(\d*)" % def_name, line)
418 if match: 421 if match:
419 value = match.group(1) 422 value = match.group(1)
420 self.Persist("%s%s" % (prefix, var_name), value) 423 self["%s%s" % (prefix, var_name)] = value
421 self._state["%s%s" % (prefix, var_name)] = value
422 for line in LinesInFile(self._config[VERSION_FILE]): 424 for line in LinesInFile(self._config[VERSION_FILE]):
423 for (var_name, def_name) in [("major", "MAJOR_VERSION"), 425 for (var_name, def_name) in [("major", "MAJOR_VERSION"),
424 ("minor", "MINOR_VERSION"), 426 ("minor", "MINOR_VERSION"),
425 ("build", "BUILD_NUMBER"), 427 ("build", "BUILD_NUMBER"),
426 ("patch", "PATCH_LEVEL")]: 428 ("patch", "PATCH_LEVEL")]:
427 ReadAndPersist(var_name, def_name) 429 ReadAndPersist(var_name, def_name)
428 430
429 def RestoreVersionIfUnset(self, prefix=""):
430 for v in ["major", "minor", "build", "patch"]:
431 self.RestoreIfUnset("%s%s" % (prefix, v))
432
433 def WaitForLGTM(self): 431 def WaitForLGTM(self):
434 print ("Please wait for an LGTM, then type \"LGTM<Return>\" to commit " 432 print ("Please wait for an LGTM, then type \"LGTM<Return>\" to commit "
435 "your change. (If you need to iterate on the patch or double check " 433 "your change. (If you need to iterate on the patch or double check "
436 "that it's sane, do so in another shell, but remember to not " 434 "that it's sane, do so in another shell, but remember to not "
437 "change the headline of the uploaded CL.") 435 "change the headline of the uploaded CL.")
438 answer = "" 436 answer = ""
439 while answer != "LGTM": 437 while answer != "LGTM":
440 print "> ", 438 print "> ",
441 answer = self.ReadLine(None if self._options.wait_for_lgtm else "LGTM") 439 answer = self.ReadLine(None if self._options.wait_for_lgtm else "LGTM")
442 if answer != "LGTM": 440 if answer != "LGTM":
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
502 500
503 return step_class(message, requires, number=number, config=config, 501 return step_class(message, requires, number=number, config=config,
504 state=state, options=options, 502 state=state, options=options,
505 handler=side_effect_handler) 503 handler=side_effect_handler)
506 504
507 505
508 def RunScript(step_classes, 506 def RunScript(step_classes,
509 config, 507 config,
510 options, 508 options,
511 side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER): 509 side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER):
510 state_file = "%s-state.json" % config[PERSISTFILE_BASENAME]
511 if options.s == 0 and os.path.exists(state_file):
512 os.remove(state_file)
512 state = {} 513 state = {}
513 steps = [] 514 steps = []
514 for (number, step_class) in enumerate(step_classes): 515 for (number, step_class) in enumerate(step_classes):
515 steps.append(MakeStep(step_class, number, state, config, 516 steps.append(MakeStep(step_class, number, state, config,
516 options, side_effect_handler)) 517 options, side_effect_handler))
517 518
518 for step in steps[options.s:]: 519 for step in steps[options.s:]:
519 step.Run() 520 step.Run()
OLDNEW
« no previous file with comments | « tools/push-to-trunk/auto_roll.py ('k') | tools/push-to-trunk/merge_to_branch.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698