| 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 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 63 def FileToText(file_name): | 63 def FileToText(file_name): |
| 64 with open(file_name) as f: | 64 with open(file_name) as f: |
| 65 return f.read() | 65 return f.read() |
| 66 | 66 |
| 67 | 67 |
| 68 def MSub(rexp, replacement, text): | 68 def MSub(rexp, replacement, text): |
| 69 return re.sub(rexp, replacement, text, flags=re.MULTILINE) | 69 return re.sub(rexp, replacement, text, flags=re.MULTILINE) |
| 70 | 70 |
| 71 | 71 |
| 72 def Fill80(line): | 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. |
| 73 return textwrap.fill(line, width=80, initial_indent=" ", | 77 return textwrap.fill(line, width=80, initial_indent=" ", |
| 74 subsequent_indent=" ") | 78 subsequent_indent=" ") |
| 75 | 79 |
| 76 | 80 |
| 77 def GetLastChangeLogEntries(change_log_file): | 81 def GetLastChangeLogEntries(change_log_file): |
| 78 result = [] | 82 result = [] |
| 79 for line in LinesInFile(change_log_file): | 83 for line in LinesInFile(change_log_file): |
| 80 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 |
| 81 result.append(line) | 85 result.append(line) |
| 82 return "".join(result) | 86 return "".join(result) |
| 83 | 87 |
| 84 | 88 |
| 85 def MakeComment(text): | 89 def MakeComment(text): |
| 86 return MSub(r"^( ?)", "#", text) | 90 return MSub(r"^( ?)", "#", text) |
| 87 | 91 |
| 88 | 92 |
| 89 def StripComments(text): | 93 def StripComments(text): |
| 90 # Use split not splitlines to keep terminal newlines. | 94 # Use split not splitlines to keep terminal newlines. |
| 91 return "\n".join(filter(lambda x: not x.startswith("#"), text.split("\n"))) | 95 return "\n".join(filter(lambda x: not x.startswith("#"), text.split("\n"))) |
| 92 | 96 |
| 93 | 97 |
| 94 def MakeChangeLogBody(commit_messages, auto_format=False): | 98 def MakeChangeLogBody(commit_messages, auto_format=False): |
| 95 result = "" | 99 result = "" |
| 96 added_titles = set() | 100 added_titles = set() |
| 97 for (title, body, author) in commit_messages: | 101 for (title, body, author) in commit_messages: |
| 98 # TODO(machenbach): Reload the commit description from rietveld in order to | 102 # TODO(machenbach): Reload the commit description from rietveld in order to |
| 99 # catch late changes. | 103 # catch late changes. |
| 100 title = title.rstrip() | 104 title = title.strip() |
| 101 if auto_format: | 105 if auto_format: |
| 102 # Only add commits that set the LOG flag correctly. | 106 # Only add commits that set the LOG flag correctly. |
| 103 log_exp = r"^[ \t]*LOG[ \t]*=[ \t]*(?:Y(?:ES)?)|TRUE" | 107 log_exp = r"^[ \t]*LOG[ \t]*=[ \t]*(?:Y(?:ES)?)|TRUE" |
| 104 if not re.search(log_exp, body, flags=re.I | re.M): | 108 if not re.search(log_exp, body, flags=re.I | re.M): |
| 105 continue | 109 continue |
| 106 # Never include reverts. | 110 # Never include reverts. |
| 107 if title.startswith("Revert "): | 111 if title.startswith("Revert "): |
| 108 continue | 112 continue |
| 109 # Don't include duplicates. | 113 # Don't include duplicates. |
| 110 if title in added_titles: | 114 if title in added_titles: |
| 111 continue | 115 continue |
| 112 | 116 |
| 113 # TODO(machenbach): Let python do all formatting. Get raw git title, attach | 117 # TODO(machenbach): Let python do all formatting. Get raw git title, attach |
| 114 # issue and add/move dot to the end - all in one line. Make formatting and | 118 # issue and add/move dot to the end - all in one line. Make formatting and |
| 115 # indentation afterwards. | 119 # indentation afterwards. |
| 116 | 120 |
| 117 # Add the commit's title line. | 121 # Add the commit's title line. |
| 118 result += "%s\n" % title | 122 result += "%s\n" % Fill80(title) |
| 119 added_titles.add(title) | 123 added_titles.add(title) |
| 120 | 124 |
| 121 # Add bug references. | 125 # Add bug references. |
| 122 result += MakeChangeLogBugReference(body) | 126 result += MakeChangeLogBugReference(body) |
| 123 | 127 |
| 124 # Append the commit's author for reference if not in auto-format mode. | 128 # Append the commit's author for reference if not in auto-format mode. |
| 125 if not auto_format: | 129 if not auto_format: |
| 126 result += "%s\n" % author.rstrip() | 130 result += "%s\n" % Fill80("(%s)" % author.strip()) |
| 127 | 131 |
| 128 result += "\n" | 132 result += "\n" |
| 129 return result | 133 return result |
| 130 | 134 |
| 131 | 135 |
| 132 def MakeChangeLogBugReference(body): | 136 def MakeChangeLogBugReference(body): |
| 133 """Grep for "BUG=xxxx" lines in the commit message and convert them to | 137 """Grep for "BUG=xxxx" lines in the commit message and convert them to |
| 134 "(issue xxxx)". | 138 "(issue xxxx)". |
| 135 """ | 139 """ |
| 136 crbugs = [] | 140 crbugs = [] |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 198 url_fh = urllib2.urlopen(url, None, 60) | 202 url_fh = urllib2.urlopen(url, None, 60) |
| 199 try: | 203 try: |
| 200 return url_fh.read() | 204 return url_fh.read() |
| 201 finally: | 205 finally: |
| 202 url_fh.close() | 206 url_fh.close() |
| 203 | 207 |
| 204 DEFAULT_SIDE_EFFECT_HANDLER = SideEffectHandler() | 208 DEFAULT_SIDE_EFFECT_HANDLER = SideEffectHandler() |
| 205 | 209 |
| 206 | 210 |
| 207 class Step(object): | 211 class Step(object): |
| 208 def __init__(self, text="", requires=None): | 212 def __init__(self, text, requires, number, config, state, options, handler): |
| 209 self._text = text | 213 self._text = text |
| 210 self._number = -1 | |
| 211 self._options = None | |
| 212 self._requires = requires | 214 self._requires = requires |
| 213 self._side_effect_handler = DEFAULT_SIDE_EFFECT_HANDLER | |
| 214 | |
| 215 def SetNumber(self, number): | |
| 216 self._number = number | 215 self._number = number |
| 217 | |
| 218 def SetConfig(self, config): | |
| 219 self._config = config | 216 self._config = config |
| 220 | |
| 221 def SetState(self, state): | |
| 222 self._state = state | 217 self._state = state |
| 223 | |
| 224 def SetOptions(self, options): | |
| 225 self._options = options | 218 self._options = options |
| 226 | |
| 227 def SetSideEffectHandler(self, handler): | |
| 228 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 |
| 229 | 224 |
| 230 def Config(self, key): | 225 def Config(self, key): |
| 231 return self._config[key] | 226 return self._config[key] |
| 232 | 227 |
| 233 def Run(self): | 228 def Run(self): |
| 234 assert self._number >= 0 | |
| 235 assert self._config is not None | |
| 236 assert self._state is not None | |
| 237 assert self._side_effect_handler is not None | |
| 238 if self._requires: | 229 if self._requires: |
| 239 self.RestoreIfUnset(self._requires) | 230 self.RestoreIfUnset(self._requires) |
| 240 if not self._state[self._requires]: | 231 if not self._state[self._requires]: |
| 241 return | 232 return |
| 242 print ">>> Step %d: %s" % (self._number, self._text) | 233 print ">>> Step %d: %s" % (self._number, self._text) |
| 243 self.RunStep() | 234 self.RunStep() |
| 244 | 235 |
| 245 def RunStep(self): | 236 def RunStep(self): |
| 246 raise NotImplementedError | 237 raise NotImplementedError |
| 247 | 238 |
| (...skipping 156 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 404 answer = self.ReadLine() | 395 answer = self.ReadLine() |
| 405 | 396 |
| 406 # Takes a file containing the patch to apply as first argument. | 397 # Takes a file containing the patch to apply as first argument. |
| 407 def ApplyPatch(self, patch_file, reverse_patch=""): | 398 def ApplyPatch(self, patch_file, reverse_patch=""): |
| 408 args = "apply --index --reject %s \"%s\"" % (reverse_patch, patch_file) | 399 args = "apply --index --reject %s \"%s\"" % (reverse_patch, patch_file) |
| 409 if self.Git(args) is None: | 400 if self.Git(args) is None: |
| 410 self.WaitForResolvingConflicts(patch_file) | 401 self.WaitForResolvingConflicts(patch_file) |
| 411 | 402 |
| 412 | 403 |
| 413 class UploadStep(Step): | 404 class UploadStep(Step): |
| 414 def __init__(self): | 405 MESSAGE = "Upload for code review." |
| 415 Step.__init__(self, "Upload for code review.") | |
| 416 | 406 |
| 417 def RunStep(self): | 407 def RunStep(self): |
| 418 if self._options.r: | 408 if self._options.r: |
| 419 print "Using account %s for review." % self._options.r | 409 print "Using account %s for review." % self._options.r |
| 420 reviewer = self._options.r | 410 reviewer = self._options.r |
| 421 else: | 411 else: |
| 422 print "Please enter the email address of a V8 reviewer for your patch: ", | 412 print "Please enter the email address of a V8 reviewer for your patch: ", |
| 423 self.DieInForcedMode("A reviewer must be specified in forced mode.") | 413 self.DieInForcedMode("A reviewer must be specified in forced mode.") |
| 424 reviewer = self.ReadLine() | 414 reviewer = self.ReadLine() |
| 425 force_flag = " -f" if self._options.f else "" | 415 force_flag = " -f" if self._options.f else "" |
| 426 args = "cl upload -r \"%s\" --send-mail%s" % (reviewer, force_flag) | 416 args = "cl upload -r \"%s\" --send-mail%s" % (reviewer, force_flag) |
| 427 # TODO(machenbach): Check output in forced mode. Verify that all required | 417 # TODO(machenbach): Check output in forced mode. Verify that all required |
| 428 # base files were uploaded, if not retry. | 418 # base files were uploaded, if not retry. |
| 429 if self.Git(args, pipe=False) is None: | 419 if self.Git(args, pipe=False) is None: |
| 430 self.Die("'git cl upload' failed, please try again.") | 420 self.Die("'git cl upload' failed, please try again.") |
| 431 | 421 |
| 432 | 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 |
| 433 def RunScript(step_classes, | 443 def RunScript(step_classes, |
| 434 config, | 444 config, |
| 435 options, | 445 options, |
| 436 side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER): | 446 side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER): |
| 437 state = {} | 447 state = {} |
| 438 steps = [] | 448 steps = [] |
| 439 number = 0 | 449 for (number, step_class) in enumerate(step_classes): |
| 440 | 450 steps.append(MakeStep(step_class, number, state, config, |
| 441 for step_class in step_classes: | 451 options, side_effect_handler)) |
| 442 # TODO(machenbach): Factory methods. | |
| 443 step = step_class() | |
| 444 step.SetNumber(number) | |
| 445 step.SetConfig(config) | |
| 446 step.SetOptions(options) | |
| 447 step.SetState(state) | |
| 448 step.SetSideEffectHandler(side_effect_handler) | |
| 449 steps.append(step) | |
| 450 number += 1 | |
| 451 | 452 |
| 452 for step in steps[options.s:]: | 453 for step in steps[options.s:]: |
| 453 step.Run() | 454 step.Run() |
| OLD | NEW |