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 148 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
159 def ReadLine(self): | 159 def ReadLine(self): |
160 return sys.stdin.readline().strip() | 160 return sys.stdin.readline().strip() |
161 | 161 |
162 DEFAULT_SIDE_EFFECT_HANDLER = SideEffectHandler() | 162 DEFAULT_SIDE_EFFECT_HANDLER = SideEffectHandler() |
163 | 163 |
164 | 164 |
165 class Step(object): | 165 class Step(object): |
166 def __init__(self, text="", requires=None): | 166 def __init__(self, text="", requires=None): |
167 self._text = text | 167 self._text = text |
168 self._number = -1 | 168 self._number = -1 |
| 169 self._options = None |
169 self._requires = requires | 170 self._requires = requires |
170 self._side_effect_handler = DEFAULT_SIDE_EFFECT_HANDLER | 171 self._side_effect_handler = DEFAULT_SIDE_EFFECT_HANDLER |
171 | 172 |
172 def SetNumber(self, number): | 173 def SetNumber(self, number): |
173 self._number = number | 174 self._number = number |
174 | 175 |
175 def SetConfig(self, config): | 176 def SetConfig(self, config): |
176 self._config = config | 177 self._config = config |
177 | 178 |
178 def SetState(self, state): | 179 def SetState(self, state): |
(...skipping 16 matching lines...) Expand all Loading... |
195 if self._requires: | 196 if self._requires: |
196 self.RestoreIfUnset(self._requires) | 197 self.RestoreIfUnset(self._requires) |
197 if not self._state[self._requires]: | 198 if not self._state[self._requires]: |
198 return | 199 return |
199 print ">>> Step %d: %s" % (self._number, self._text) | 200 print ">>> Step %d: %s" % (self._number, self._text) |
200 self.RunStep() | 201 self.RunStep() |
201 | 202 |
202 def RunStep(self): | 203 def RunStep(self): |
203 raise NotImplementedError | 204 raise NotImplementedError |
204 | 205 |
205 def ReadLine(self): | 206 def ReadLine(self, default=None): |
206 return self._side_effect_handler.ReadLine() | 207 # Don't prompt in forced mode. |
| 208 if self._options and self._options.f and default is not None: |
| 209 print "%s (forced)" % default |
| 210 return default |
| 211 else: |
| 212 return self._side_effect_handler.ReadLine() |
207 | 213 |
208 def Git(self, args="", prefix="", pipe=True): | 214 def Git(self, args="", prefix="", pipe=True): |
209 return self._side_effect_handler.Command("git", args, prefix, pipe) | 215 return self._side_effect_handler.Command("git", args, prefix, pipe) |
210 | 216 |
211 def Editor(self, args): | 217 def Editor(self, args): |
212 return self._side_effect_handler.Command(os.environ["EDITOR"], args, | 218 return self._side_effect_handler.Command(os.environ["EDITOR"], args, |
213 pipe=False) | 219 pipe=False) |
214 | 220 |
215 def Die(self, msg=""): | 221 def Die(self, msg=""): |
216 if msg != "": | 222 if msg != "": |
217 print "Error: %s" % msg | 223 print "Error: %s" % msg |
218 print "Exiting" | 224 print "Exiting" |
219 raise Exception(msg) | 225 raise Exception(msg) |
220 | 226 |
| 227 def DieInForcedMode(self, msg=""): |
| 228 if self._options and self._options.f: |
| 229 msg = msg or "Not implemented in forced mode." |
| 230 self.Die(msg) |
| 231 |
221 def Confirm(self, msg): | 232 def Confirm(self, msg): |
222 print "%s [Y/n] " % msg, | 233 print "%s [Y/n] " % msg, |
223 answer = self.ReadLine() | 234 answer = self.ReadLine(default="Y") |
224 return answer == "" or answer == "Y" or answer == "y" | 235 return answer == "" or answer == "Y" or answer == "y" |
225 | 236 |
226 def DeleteBranch(self, name): | 237 def DeleteBranch(self, name): |
227 git_result = self.Git("branch").strip() | 238 git_result = self.Git("branch").strip() |
228 for line in git_result.splitlines(): | 239 for line in git_result.splitlines(): |
229 if re.match(r".*\s+%s$" % name, line): | 240 if re.match(r".*\s+%s$" % name, line): |
230 msg = "Branch %s exists, do you want to delete it?" % name | 241 msg = "Branch %s exists, do you want to delete it?" % name |
231 if self.Confirm(msg): | 242 if self.Confirm(msg): |
232 if self.Git("branch -D %s" % name) is None: | 243 if self.Git("branch -D %s" % name) is None: |
233 self.Die("Deleting branch '%s' failed." % name) | 244 self.Die("Deleting branch '%s' failed." % name) |
(...skipping 13 matching lines...) Expand all Loading... |
247 | 258 |
248 def RestoreIfUnset(self, var_name): | 259 def RestoreIfUnset(self, var_name): |
249 if self._state.get(var_name) is None: | 260 if self._state.get(var_name) is None: |
250 self._state[var_name] = self.Restore(var_name) | 261 self._state[var_name] = self.Restore(var_name) |
251 | 262 |
252 def InitialEnvironmentChecks(self): | 263 def InitialEnvironmentChecks(self): |
253 # Cancel if this is not a git checkout. | 264 # Cancel if this is not a git checkout. |
254 if not os.path.exists(self._config[DOT_GIT_LOCATION]): | 265 if not os.path.exists(self._config[DOT_GIT_LOCATION]): |
255 self.Die("This is not a git checkout, this script won't work for you.") | 266 self.Die("This is not a git checkout, this script won't work for you.") |
256 | 267 |
| 268 # TODO(machenbach): Don't use EDITOR in forced mode as soon as script is |
| 269 # well tested. |
257 # Cancel if EDITOR is unset or not executable. | 270 # Cancel if EDITOR is unset or not executable. |
258 if (not os.environ.get("EDITOR") or | 271 if (not os.environ.get("EDITOR") or |
259 Command("which", os.environ["EDITOR"]) is None): | 272 Command("which", os.environ["EDITOR"]) is None): |
260 self.Die("Please set your EDITOR environment variable, you'll need it.") | 273 self.Die("Please set your EDITOR environment variable, you'll need it.") |
261 | 274 |
262 def CommonPrepare(self): | 275 def CommonPrepare(self): |
263 # Check for a clean workdir. | 276 # Check for a clean workdir. |
264 if self.Git("status -s -uno").strip() != "": | 277 if self.Git("status -s -uno").strip() != "": |
265 self.Die("Workspace is not clean. Please commit or undo your changes.") | 278 self.Die("Workspace is not clean. Please commit or undo your changes.") |
266 | 279 |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
318 self.RestoreIfUnset("%s%s" % (prefix, v)) | 331 self.RestoreIfUnset("%s%s" % (prefix, v)) |
319 | 332 |
320 def WaitForLGTM(self): | 333 def WaitForLGTM(self): |
321 print ("Please wait for an LGTM, then type \"LGTM<Return>\" to commit " | 334 print ("Please wait for an LGTM, then type \"LGTM<Return>\" to commit " |
322 "your change. (If you need to iterate on the patch or double check " | 335 "your change. (If you need to iterate on the patch or double check " |
323 "that it's sane, do so in another shell, but remember to not " | 336 "that it's sane, do so in another shell, but remember to not " |
324 "change the headline of the uploaded CL.") | 337 "change the headline of the uploaded CL.") |
325 answer = "" | 338 answer = "" |
326 while answer != "LGTM": | 339 while answer != "LGTM": |
327 print "> ", | 340 print "> ", |
| 341 # TODO(machenbach): Add default="LGTM" to avoid prompt when script is |
| 342 # well tested and when prepare push cl has TBR flag. |
328 answer = self.ReadLine() | 343 answer = self.ReadLine() |
329 if answer != "LGTM": | 344 if answer != "LGTM": |
330 print "That was not 'LGTM'." | 345 print "That was not 'LGTM'." |
331 | 346 |
332 def WaitForResolvingConflicts(self, patch_file): | 347 def WaitForResolvingConflicts(self, patch_file): |
333 print("Applying the patch \"%s\" failed. Either type \"ABORT<Return>\", " | 348 print("Applying the patch \"%s\" failed. Either type \"ABORT<Return>\", " |
334 "or resolve the conflicts, stage *all* touched files with " | 349 "or resolve the conflicts, stage *all* touched files with " |
335 "'git add', and type \"RESOLVED<Return>\"") | 350 "'git add', and type \"RESOLVED<Return>\"") |
| 351 self.DieInForcedMode() |
336 answer = "" | 352 answer = "" |
337 while answer != "RESOLVED": | 353 while answer != "RESOLVED": |
338 if answer == "ABORT": | 354 if answer == "ABORT": |
339 self.Die("Applying the patch failed.") | 355 self.Die("Applying the patch failed.") |
340 if answer != "": | 356 if answer != "": |
341 print "That was not 'RESOLVED' or 'ABORT'." | 357 print "That was not 'RESOLVED' or 'ABORT'." |
342 print "> ", | 358 print "> ", |
343 answer = self.ReadLine() | 359 answer = self.ReadLine() |
344 | 360 |
345 # Takes a file containing the patch to apply as first argument. | 361 # Takes a file containing the patch to apply as first argument. |
346 def ApplyPatch(self, patch_file, reverse_patch=""): | 362 def ApplyPatch(self, patch_file, reverse_patch=""): |
347 args = "apply --index --reject %s \"%s\"" % (reverse_patch, patch_file) | 363 args = "apply --index --reject %s \"%s\"" % (reverse_patch, patch_file) |
348 if self.Git(args) is None: | 364 if self.Git(args) is None: |
349 self.WaitForResolvingConflicts(patch_file) | 365 self.WaitForResolvingConflicts(patch_file) |
350 | 366 |
351 | 367 |
352 class UploadStep(Step): | 368 class UploadStep(Step): |
353 def __init__(self): | 369 def __init__(self): |
354 Step.__init__(self, "Upload for code review.") | 370 Step.__init__(self, "Upload for code review.") |
355 | 371 |
356 def RunStep(self): | 372 def RunStep(self): |
357 print "Please enter the email address of a V8 reviewer for your patch: ", | 373 if self._options and self._options.r: |
358 reviewer = self.ReadLine() | 374 print "Using account %s for review." % self._options.r |
| 375 reviewer = self._options.r |
| 376 else: |
| 377 print "Please enter the email address of a V8 reviewer for your patch: ", |
| 378 self.DieInForcedMode("A reviewer must be specified in forced mode.") |
| 379 reviewer = self.ReadLine() |
359 args = "cl upload -r \"%s\" --send-mail" % reviewer | 380 args = "cl upload -r \"%s\" --send-mail" % reviewer |
360 if self.Git(args,pipe=False) is None: | 381 if self.Git(args,pipe=False) is None: |
361 self.Die("'git cl upload' failed, please try again.") | 382 self.Die("'git cl upload' failed, please try again.") |
362 | 383 |
363 | 384 |
364 def RunScript(step_classes, | 385 def RunScript(step_classes, |
365 config, | 386 config, |
366 options, | 387 options, |
367 side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER): | 388 side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER): |
368 state = {} | 389 state = {} |
369 steps = [] | 390 steps = [] |
370 number = 0 | 391 number = 0 |
371 | 392 |
372 for step_class in step_classes: | 393 for step_class in step_classes: |
373 # TODO(machenbach): Factory methods. | 394 # TODO(machenbach): Factory methods. |
374 step = step_class() | 395 step = step_class() |
375 step.SetNumber(number) | 396 step.SetNumber(number) |
376 step.SetConfig(config) | 397 step.SetConfig(config) |
377 step.SetOptions(options) | 398 step.SetOptions(options) |
378 step.SetState(state) | 399 step.SetState(state) |
379 step.SetSideEffectHandler(side_effect_handler) | 400 step.SetSideEffectHandler(side_effect_handler) |
380 steps.append(step) | 401 steps.append(step) |
381 number += 1 | 402 number += 1 |
382 | 403 |
383 for step in steps[options.s:]: | 404 for step in steps[options.s:]: |
384 step.Run() | 405 step.Run() |
OLD | NEW |