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

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

Issue 110573004: Merge bleeding_edge 17696:18016. (Closed) Base URL: https://v8.googlecode.com/svn/branches/experimental/parser
Patch Set: Created 7 years 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
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
(...skipping 12 matching lines...) Expand all
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 os 29 import os
30 import re 30 import re
31 import subprocess 31 import subprocess
32 import sys 32 import sys
33 import textwrap
34 import urllib2
33 35
34 PERSISTFILE_BASENAME = "PERSISTFILE_BASENAME" 36 PERSISTFILE_BASENAME = "PERSISTFILE_BASENAME"
35 TEMP_BRANCH = "TEMP_BRANCH" 37 TEMP_BRANCH = "TEMP_BRANCH"
36 BRANCHNAME = "BRANCHNAME" 38 BRANCHNAME = "BRANCHNAME"
37 DOT_GIT_LOCATION = "DOT_GIT_LOCATION" 39 DOT_GIT_LOCATION = "DOT_GIT_LOCATION"
38 VERSION_FILE = "VERSION_FILE" 40 VERSION_FILE = "VERSION_FILE"
39 CHANGELOG_FILE = "CHANGELOG_FILE" 41 CHANGELOG_FILE = "CHANGELOG_FILE"
40 CHANGELOG_ENTRY_FILE = "CHANGELOG_ENTRY_FILE" 42 CHANGELOG_ENTRY_FILE = "CHANGELOG_ENTRY_FILE"
41 COMMITMSG_FILE = "COMMITMSG_FILE" 43 COMMITMSG_FILE = "COMMITMSG_FILE"
42 PATCH_FILE = "PATCH_FILE" 44 PATCH_FILE = "PATCH_FILE"
(...skipping 17 matching lines...) Expand all
60 62
61 def FileToText(file_name): 63 def FileToText(file_name):
62 with open(file_name) as f: 64 with open(file_name) as f:
63 return f.read() 65 return f.read()
64 66
65 67
66 def MSub(rexp, replacement, text): 68 def MSub(rexp, replacement, text):
67 return re.sub(rexp, replacement, text, flags=re.MULTILINE) 69 return re.sub(rexp, replacement, text, flags=re.MULTILINE)
68 70
69 71
72 def Fill80(line):
73 # Replace tabs and remove surrounding space.
74 line = re.sub(r"\t", r" ", line.strip())
75
76 # Format with 8 characters indentation and line width 80.
77 return textwrap.fill(line, width=80, initial_indent=" ",
78 subsequent_indent=" ")
79
80
70 def GetLastChangeLogEntries(change_log_file): 81 def GetLastChangeLogEntries(change_log_file):
71 result = [] 82 result = []
72 for line in LinesInFile(change_log_file): 83 for line in LinesInFile(change_log_file):
73 if re.search(r"^\d{4}-\d{2}-\d{2}:", line) and result: break 84 if re.search(r"^\d{4}-\d{2}-\d{2}:", line) and result: break
74 result.append(line) 85 result.append(line)
75 return "".join(result) 86 return "".join(result)
76 87
77 88
89 def MakeComment(text):
90 return MSub(r"^( ?)", "#", text)
91
92
93 def StripComments(text):
94 # Use split not splitlines to keep terminal newlines.
95 return "\n".join(filter(lambda x: not x.startswith("#"), text.split("\n")))
96
97
98 def MakeChangeLogBody(commit_messages, auto_format=False):
99 result = ""
100 added_titles = set()
101 for (title, body, author) in commit_messages:
102 # TODO(machenbach): Reload the commit description from rietveld in order to
103 # catch late changes.
104 title = title.strip()
105 if auto_format:
106 # Only add commits that set the LOG flag correctly.
107 log_exp = r"^[ \t]*LOG[ \t]*=[ \t]*(?:Y(?:ES)?)|TRUE"
108 if not re.search(log_exp, body, flags=re.I | re.M):
109 continue
110 # Never include reverts.
111 if title.startswith("Revert "):
112 continue
113 # Don't include duplicates.
114 if title in added_titles:
115 continue
116
117 # TODO(machenbach): Let python do all formatting. Get raw git title, attach
118 # issue and add/move dot to the end - all in one line. Make formatting and
119 # indentation afterwards.
120
121 # Add the commit's title line.
122 result += "%s\n" % Fill80(title)
123 added_titles.add(title)
124
125 # Add bug references.
126 result += MakeChangeLogBugReference(body)
127
128 # Append the commit's author for reference if not in auto-format mode.
129 if not auto_format:
130 result += "%s\n" % Fill80("(%s)" % author.strip())
131
132 result += "\n"
133 return result
134
135
136 def MakeChangeLogBugReference(body):
137 """Grep for "BUG=xxxx" lines in the commit message and convert them to
138 "(issue xxxx)".
139 """
140 crbugs = []
141 v8bugs = []
142
143 def AddIssues(text):
144 ref = re.match(r"^BUG[ \t]*=[ \t]*(.+)$", text.strip())
145 if not ref:
146 return
147 for bug in ref.group(1).split(","):
148 bug = bug.strip()
149 match = re.match(r"^v8:(\d+)$", bug)
150 if match: v8bugs.append(int(match.group(1)))
151 else:
152 match = re.match(r"^(?:chromium:)?(\d+)$", bug)
153 if match: crbugs.append(int(match.group(1)))
154
155 # Add issues to crbugs and v8bugs.
156 map(AddIssues, body.splitlines())
157
158 # Filter duplicates, sort, stringify.
159 crbugs = map(str, sorted(set(crbugs)))
160 v8bugs = map(str, sorted(set(v8bugs)))
161
162 bug_groups = []
163 def FormatIssues(prefix, bugs):
164 if len(bugs) > 0:
165 plural = "s" if len(bugs) > 1 else ""
166 bug_groups.append("%sissue%s %s" % (prefix, plural, ", ".join(bugs)))
167
168 FormatIssues("", v8bugs)
169 FormatIssues("Chromium ", crbugs)
170
171 if len(bug_groups) > 0:
172 # Format with 8 characters indentation and max 80 character lines.
173 return "%s\n" % Fill80("(%s)" % ", ".join(bug_groups))
174 else:
175 return ""
176
177
78 # Some commands don't like the pipe, e.g. calling vi from within the script or 178 # Some commands don't like the pipe, e.g. calling vi from within the script or
79 # from subscripts like git cl upload. 179 # from subscripts like git cl upload.
80 def Command(cmd, args="", prefix="", pipe=True): 180 def Command(cmd, args="", prefix="", pipe=True):
81 cmd_line = "%s %s %s" % (prefix, cmd, args) 181 cmd_line = "%s %s %s" % (prefix, cmd, args)
82 print "Command: %s" % cmd_line 182 print "Command: %s" % cmd_line
83 try: 183 try:
84 if pipe: 184 if pipe:
85 return subprocess.check_output(cmd_line, shell=True) 185 return subprocess.check_output(cmd_line, shell=True)
86 else: 186 else:
87 return subprocess.check_call(cmd_line, shell=True) 187 return subprocess.check_call(cmd_line, shell=True)
88 except subprocess.CalledProcessError: 188 except subprocess.CalledProcessError:
89 return None 189 return None
90 190
91 191
92 # Wrapper for side effects. 192 # Wrapper for side effects.
93 class SideEffectHandler(object): 193 class SideEffectHandler(object):
94 def Command(self, cmd, args="", prefix="", pipe=True): 194 def Command(self, cmd, args="", prefix="", pipe=True):
95 return Command(cmd, args, prefix, pipe) 195 return Command(cmd, args, prefix, pipe)
96 196
97 def ReadLine(self): 197 def ReadLine(self):
98 return sys.stdin.readline().strip() 198 return sys.stdin.readline().strip()
99 199
200 def ReadURL(self, url):
201 # pylint: disable=E1121
202 url_fh = urllib2.urlopen(url, None, 60)
203 try:
204 return url_fh.read()
205 finally:
206 url_fh.close()
207
100 DEFAULT_SIDE_EFFECT_HANDLER = SideEffectHandler() 208 DEFAULT_SIDE_EFFECT_HANDLER = SideEffectHandler()
101 209
102 210
103 class Step(object): 211 class Step(object):
104 def __init__(self, text="", requires=None): 212 def __init__(self, text, requires, number, config, state, options, handler):
105 self._text = text 213 self._text = text
106 self._number = -1
107 self._requires = requires 214 self._requires = requires
108 self._side_effect_handler = DEFAULT_SIDE_EFFECT_HANDLER
109
110 def SetNumber(self, number):
111 self._number = number 215 self._number = number
112
113 def SetConfig(self, config):
114 self._config = config 216 self._config = config
115
116 def SetState(self, state):
117 self._state = state 217 self._state = state
118
119 def SetOptions(self, options):
120 self._options = options 218 self._options = options
121
122 def SetSideEffectHandler(self, handler):
123 self._side_effect_handler = handler 219 self._side_effect_handler = handler
220 assert self._number >= 0
221 assert self._config is not None
222 assert self._state is not None
223 assert self._side_effect_handler is not None
124 224
125 def Config(self, key): 225 def Config(self, key):
126 return self._config[key] 226 return self._config[key]
127 227
128 def Run(self): 228 def Run(self):
129 assert self._number >= 0
130 assert self._config is not None
131 assert self._state is not None
132 assert self._side_effect_handler is not None
133 if self._requires: 229 if self._requires:
134 self.RestoreIfUnset(self._requires) 230 self.RestoreIfUnset(self._requires)
135 if not self._state[self._requires]: 231 if not self._state[self._requires]:
136 return 232 return
137 print ">>> Step %d: %s" % (self._number, self._text) 233 print ">>> Step %d: %s" % (self._number, self._text)
138 self.RunStep() 234 self.RunStep()
139 235
140 def RunStep(self): 236 def RunStep(self):
141 raise NotImplementedError 237 raise NotImplementedError
142 238
143 def ReadLine(self): 239 def ReadLine(self, default=None):
144 return self._side_effect_handler.ReadLine() 240 # Don't prompt in forced mode.
241 if self._options and self._options.f and default is not None:
242 print "%s (forced)" % default
243 return default
244 else:
245 return self._side_effect_handler.ReadLine()
145 246
146 def Git(self, args="", prefix="", pipe=True): 247 def Git(self, args="", prefix="", pipe=True):
147 return self._side_effect_handler.Command("git", args, prefix, pipe) 248 return self._side_effect_handler.Command("git", args, prefix, pipe)
148 249
149 def Editor(self, args): 250 def Editor(self, args):
150 return self._side_effect_handler.Command(os.environ["EDITOR"], args, 251 return self._side_effect_handler.Command(os.environ["EDITOR"], args,
151 pipe=False) 252 pipe=False)
152 253
254 def ReadURL(self, url):
255 return self._side_effect_handler.ReadURL(url)
256
153 def Die(self, msg=""): 257 def Die(self, msg=""):
154 if msg != "": 258 if msg != "":
155 print "Error: %s" % msg 259 print "Error: %s" % msg
156 print "Exiting" 260 print "Exiting"
157 raise Exception(msg) 261 raise Exception(msg)
158 262
263 def DieInForcedMode(self, msg=""):
264 if self._options and self._options.f:
265 msg = msg or "Not implemented in forced mode."
266 self.Die(msg)
267
159 def Confirm(self, msg): 268 def Confirm(self, msg):
160 print "%s [Y/n] " % msg, 269 print "%s [Y/n] " % msg,
161 answer = self.ReadLine() 270 answer = self.ReadLine(default="Y")
162 return answer == "" or answer == "Y" or answer == "y" 271 return answer == "" or answer == "Y" or answer == "y"
163 272
164 def DeleteBranch(self, name): 273 def DeleteBranch(self, name):
165 git_result = self.Git("branch").strip() 274 git_result = self.Git("branch").strip()
166 for line in git_result.splitlines(): 275 for line in git_result.splitlines():
167 if re.match(r".*\s+%s$" % name, line): 276 if re.match(r".*\s+%s$" % name, line):
168 msg = "Branch %s exists, do you want to delete it?" % name 277 msg = "Branch %s exists, do you want to delete it?" % name
169 if self.Confirm(msg): 278 if self.Confirm(msg):
170 if self.Git("branch -D %s" % name) is None: 279 if self.Git("branch -D %s" % name) is None:
171 self.Die("Deleting branch '%s' failed." % name) 280 self.Die("Deleting branch '%s' failed." % name)
(...skipping 13 matching lines...) Expand all
185 294
186 def RestoreIfUnset(self, var_name): 295 def RestoreIfUnset(self, var_name):
187 if self._state.get(var_name) is None: 296 if self._state.get(var_name) is None:
188 self._state[var_name] = self.Restore(var_name) 297 self._state[var_name] = self.Restore(var_name)
189 298
190 def InitialEnvironmentChecks(self): 299 def InitialEnvironmentChecks(self):
191 # Cancel if this is not a git checkout. 300 # Cancel if this is not a git checkout.
192 if not os.path.exists(self._config[DOT_GIT_LOCATION]): 301 if not os.path.exists(self._config[DOT_GIT_LOCATION]):
193 self.Die("This is not a git checkout, this script won't work for you.") 302 self.Die("This is not a git checkout, this script won't work for you.")
194 303
304 # TODO(machenbach): Don't use EDITOR in forced mode as soon as script is
305 # well tested.
195 # Cancel if EDITOR is unset or not executable. 306 # Cancel if EDITOR is unset or not executable.
196 if (not os.environ.get("EDITOR") or 307 if (not os.environ.get("EDITOR") or
197 Command("which", os.environ["EDITOR"]) is None): 308 Command("which", os.environ["EDITOR"]) is None):
198 self.Die("Please set your EDITOR environment variable, you'll need it.") 309 self.Die("Please set your EDITOR environment variable, you'll need it.")
199 310
200 def CommonPrepare(self): 311 def CommonPrepare(self):
201 # Check for a clean workdir. 312 # Check for a clean workdir.
202 if self.Git("status -s -uno").strip() != "": 313 if self.Git("status -s -uno").strip() != "":
203 self.Die("Workspace is not clean. Please commit or undo your changes.") 314 self.Die("Workspace is not clean. Please commit or undo your changes.")
204 315
205 # Persist current branch. 316 # Persist current branch.
206 current_branch = "" 317 current_branch = ""
207 git_result = self.Git("status -s -b -uno").strip() 318 git_result = self.Git("status -s -b -uno").strip()
208 for line in git_result.splitlines(): 319 for line in git_result.splitlines():
209 match = re.match(r"^## (.+)", line) 320 match = re.match(r"^## (.+)", line)
210 if match: 321 if match:
211 current_branch = match.group(1) 322 current_branch = match.group(1)
212 break 323 break
213 self.Persist("current_branch", current_branch) 324 self.Persist("current_branch", current_branch)
214 325
215 # Fetch unfetched revisions. 326 # Fetch unfetched revisions.
216 if self.Git("svn fetch") is None: 327 if self.Git("svn fetch") is None:
217 self.Die("'git svn fetch' failed.") 328 self.Die("'git svn fetch' failed.")
218 329
330 def PrepareBranch(self):
219 # Get ahold of a safe temporary branch and check it out. 331 # Get ahold of a safe temporary branch and check it out.
220 if current_branch != self._config[TEMP_BRANCH]: 332 self.RestoreIfUnset("current_branch")
333 if self._state["current_branch"] != self._config[TEMP_BRANCH]:
221 self.DeleteBranch(self._config[TEMP_BRANCH]) 334 self.DeleteBranch(self._config[TEMP_BRANCH])
222 self.Git("checkout -b %s" % self._config[TEMP_BRANCH]) 335 self.Git("checkout -b %s" % self._config[TEMP_BRANCH])
223 336
224 # Delete the branch that will be created later if it exists already. 337 # Delete the branch that will be created later if it exists already.
225 self.DeleteBranch(self._config[BRANCHNAME]) 338 self.DeleteBranch(self._config[BRANCHNAME])
226 339
227 def CommonCleanup(self): 340 def CommonCleanup(self):
228 self.RestoreIfUnset("current_branch") 341 self.RestoreIfUnset("current_branch")
229 self.Git("checkout -f %s" % self._state["current_branch"]) 342 self.Git("checkout -f %s" % self._state["current_branch"])
230 if self._config[TEMP_BRANCH] != self._state["current_branch"]: 343 if self._config[TEMP_BRANCH] != self._state["current_branch"]:
(...skipping 23 matching lines...) Expand all
254 self.RestoreIfUnset("%s%s" % (prefix, v)) 367 self.RestoreIfUnset("%s%s" % (prefix, v))
255 368
256 def WaitForLGTM(self): 369 def WaitForLGTM(self):
257 print ("Please wait for an LGTM, then type \"LGTM<Return>\" to commit " 370 print ("Please wait for an LGTM, then type \"LGTM<Return>\" to commit "
258 "your change. (If you need to iterate on the patch or double check " 371 "your change. (If you need to iterate on the patch or double check "
259 "that it's sane, do so in another shell, but remember to not " 372 "that it's sane, do so in another shell, but remember to not "
260 "change the headline of the uploaded CL.") 373 "change the headline of the uploaded CL.")
261 answer = "" 374 answer = ""
262 while answer != "LGTM": 375 while answer != "LGTM":
263 print "> ", 376 print "> ",
377 # TODO(machenbach): Add default="LGTM" to avoid prompt when script is
378 # well tested and when prepare push cl has TBR flag.
264 answer = self.ReadLine() 379 answer = self.ReadLine()
265 if answer != "LGTM": 380 if answer != "LGTM":
266 print "That was not 'LGTM'." 381 print "That was not 'LGTM'."
267 382
268 def WaitForResolvingConflicts(self, patch_file): 383 def WaitForResolvingConflicts(self, patch_file):
269 print("Applying the patch \"%s\" failed. Either type \"ABORT<Return>\", " 384 print("Applying the patch \"%s\" failed. Either type \"ABORT<Return>\", "
270 "or resolve the conflicts, stage *all* touched files with " 385 "or resolve the conflicts, stage *all* touched files with "
271 "'git add', and type \"RESOLVED<Return>\"") 386 "'git add', and type \"RESOLVED<Return>\"")
387 self.DieInForcedMode()
272 answer = "" 388 answer = ""
273 while answer != "RESOLVED": 389 while answer != "RESOLVED":
274 if answer == "ABORT": 390 if answer == "ABORT":
275 self.Die("Applying the patch failed.") 391 self.Die("Applying the patch failed.")
276 if answer != "": 392 if answer != "":
277 print "That was not 'RESOLVED' or 'ABORT'." 393 print "That was not 'RESOLVED' or 'ABORT'."
278 print "> ", 394 print "> ",
279 answer = self.ReadLine() 395 answer = self.ReadLine()
280 396
281 # Takes a file containing the patch to apply as first argument. 397 # Takes a file containing the patch to apply as first argument.
282 def ApplyPatch(self, patch_file, reverse_patch=""): 398 def ApplyPatch(self, patch_file, reverse_patch=""):
283 args = "apply --index --reject %s \"%s\"" % (reverse_patch, patch_file) 399 args = "apply --index --reject %s \"%s\"" % (reverse_patch, patch_file)
284 if self.Git(args) is None: 400 if self.Git(args) is None:
285 self.WaitForResolvingConflicts(patch_file) 401 self.WaitForResolvingConflicts(patch_file)
286 402
287 403
288 class UploadStep(Step): 404 class UploadStep(Step):
289 def __init__(self): 405 MESSAGE = "Upload for code review."
290 Step.__init__(self, "Upload for code review.")
291 406
292 def RunStep(self): 407 def RunStep(self):
293 print "Please enter the email address of a V8 reviewer for your patch: ", 408 if self._options.r:
294 reviewer = self.ReadLine() 409 print "Using account %s for review." % self._options.r
295 args = "cl upload -r \"%s\" --send-mail" % reviewer 410 reviewer = self._options.r
296 if self.Git(args,pipe=False) is None: 411 else:
412 print "Please enter the email address of a V8 reviewer for your patch: ",
413 self.DieInForcedMode("A reviewer must be specified in forced mode.")
414 reviewer = self.ReadLine()
415 force_flag = " -f" if self._options.f else ""
416 args = "cl upload -r \"%s\" --send-mail%s" % (reviewer, force_flag)
417 # TODO(machenbach): Check output in forced mode. Verify that all required
418 # base files were uploaded, if not retry.
419 if self.Git(args, pipe=False) is None:
297 self.Die("'git cl upload' failed, please try again.") 420 self.Die("'git cl upload' failed, please try again.")
421
422
423 def MakeStep(step_class=Step, number=0, state=None, config=None,
424 options=None, side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER):
425 # Allow to pass in empty dictionaries.
426 state = state if state is not None else {}
427 config = config if config is not None else {}
428
429 try:
430 message = step_class.MESSAGE
431 except AttributeError:
432 message = step_class.__name__
433 try:
434 requires = step_class.REQUIRES
435 except AttributeError:
436 requires = None
437
438 return step_class(message, requires, number=number, config=config,
439 state=state, options=options,
440 handler=side_effect_handler)
441
442
443 def RunScript(step_classes,
444 config,
445 options,
446 side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER):
447 state = {}
448 steps = []
449 for (number, step_class) in enumerate(step_classes):
450 steps.append(MakeStep(step_class, number, state, config,
451 options, side_effect_handler))
452
453 for step in steps[options.s:]:
454 step.Run()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698