OLD | NEW |
1 # Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2009 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 import optparse | 5 import optparse |
6 import os | 6 import os |
7 import re | 7 import re |
8 import subprocess | 8 import subprocess |
9 import sys | 9 import sys |
10 import webbrowser | 10 import webbrowser |
11 | 11 |
12 USAGE = """ | 12 USAGE = """ |
13 WARNING: Please use this tool in an empty directory | 13 WARNING: Please use this tool in an empty directory |
14 (or at least one that you don't mind clobbering.) | 14 (or at least one that you don't mind clobbering.) |
15 | 15 |
16 REQUIRES: SVN 1.5+ | 16 REQUIRES: SVN 1.5+ |
17 NOTE: NO NEED TO CHECKOUT ANYTHING IN ADVANCE OF USING THIS TOOL." | 17 NOTE: NO NEED TO CHECKOUT ANYTHING IN ADVANCE OF USING THIS TOOL." |
18 Valid parameters: | 18 Valid parameters: |
19 | 19 |
20 [Merge from trunk to branch] | 20 [Merge from trunk to branch] |
21 <revision> --merge <branch_num> | 21 --merge <revision> --branch <branch_num> |
22 Example: %(app)s 12345 --merge 187 | 22 Example: %(app)s --merge 12345 --branch 187 |
23 | |
24 [Merge from trunk to branch, ignoring revision history] | |
25 <revision> --mplus <branch_num> | |
26 Example: %(app)s 12345 --mplus 187 | |
27 | 23 |
28 [Revert from trunk] | 24 [Revert from trunk] |
29 <revision> --revert | 25 --revert <revision> |
30 Example: %(app)s 12345 --revert | 26 Example: %(app)s --revert 12345 |
31 | |
32 | 27 |
33 [Revert from branch] | 28 [Revert from branch] |
34 <revision> --revert <branch_num> | 29 --revert <revision> --branch <branch_num> |
35 Example: %(app)s 12345 --revert 187 | 30 Example: %(app)s --revert 12345 --branch 187 |
36 """ | 31 """ |
37 | 32 |
38 export_map_ = None | 33 export_map_ = None |
39 files_info_ = None | 34 files_info_ = None |
40 delete_map_ = None | 35 delete_map_ = None |
41 file_pattern_ = r"[ ]+([MADUC])[ ]+/((?:trunk|branches/\d+)/src(.*)/(.*))" | 36 file_pattern_ = r"[ ]+([MADUC])[ ]+/((?:trunk|branches/\d+)/src(.*)/(.*))" |
42 | 37 |
43 def deltree(root): | 38 def deltree(root): |
44 """Removes a given directory""" | 39 """Removes a given directory""" |
45 if (not os.path.exists(root)): | 40 if (not os.path.exists(root)): |
(...skipping 196 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
242 # pass... | 237 # pass... |
243 match = re.search(r"(.*) \(from.*\)", line) | 238 match = re.search(r"(.*) \(from.*\)", line) |
244 if match: | 239 if match: |
245 line = match.group(1) | 240 line = match.group(1) |
246 match = re.search(file_pattern_, line) | 241 match = re.search(file_pattern_, line) |
247 if match: | 242 if match: |
248 info.append([match.group(1).strip(), match.group(2).strip(), | 243 info.append([match.group(1).strip(), match.group(2).strip(), |
249 match.group(3).strip(),match.group(4).strip()]) | 244 match.group(3).strip(),match.group(4).strip()]) |
250 | 245 |
251 files_info_ = info | 246 files_info_ = info |
252 return rtn | 247 return info |
253 | 248 |
254 def getBestMergePaths(url, revision): | 249 def getBestMergePaths(url, revision): |
255 """Takes an svn url and gets the associated revision.""" | 250 """Takes an svn url and gets the associated revision.""" |
256 return getBestMergePaths2(getFileInfo(url, revision), revision) | 251 return getBestMergePaths2(getFileInfo(url, revision), revision) |
257 | 252 |
258 def getBestMergePaths2(files_info, revision): | 253 def getBestMergePaths2(files_info, revision): |
259 """Takes an svn url and gets the associated revision.""" | 254 """Takes an svn url and gets the associated revision.""" |
260 | 255 |
261 map = dict() | 256 map = dict() |
262 for file_info in files_info: | 257 for file_info in files_info: |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
322 export + add) | 317 export + add) |
323 """ | 318 """ |
324 map = [] | 319 map = [] |
325 for file_info in files_info: | 320 for file_info in files_info: |
326 map.append(file_info[2] + "/" + file_info[3]) | 321 map.append(file_info[2] + "/" + file_info[3]) |
327 return map | 322 return map |
328 | 323 |
329 def prompt(question): | 324 def prompt(question): |
330 answer = None | 325 answer = None |
331 | 326 |
332 while not p: | 327 while not answer: |
333 print question + " [y|n]:" | 328 print question + " [y|n]:" |
334 answer = sys.stdin.readline() | 329 answer = sys.stdin.readline() |
335 if answer.lower().startswith('n'): | 330 if answer.lower().startswith('n'): |
336 return False | 331 return False |
337 elif answer.lower().startswith('y'): | 332 elif answer.lower().startswith('y'): |
338 return True | 333 return True |
339 else: | 334 else: |
340 answer = None | 335 answer = None |
341 | 336 |
342 def text_prompt(question, default): | 337 def text_prompt(question, default): |
343 print question + " [" + default + "]:" | 338 print question + " [" + default + "]:" |
344 answer = sys.stdin.readline() | 339 answer = sys.stdin.readline() |
345 if answer.strip() == "": | 340 if answer.strip() == "": |
346 return default | 341 return default |
347 return answer | 342 return answer |
348 | 343 |
349 def main(argv=None): | 344 def main(options, args): |
350 BASE_URL = "svn://chrome-svn/chrome" | 345 BASE_URL = "svn://chrome-svn/chrome" |
351 TRUNK_URL = BASE_URL + "/trunk/src" | 346 TRUNK_URL = BASE_URL + "/trunk/src" |
352 BRANCH_URL = BASE_URL + "/branches/$branch/src" | 347 BRANCH_URL = BASE_URL + "/branches/$branch/src" |
353 DEFAULT_WORKING = "working" | 348 DEFAULT_WORKING = "working" |
354 SKIP_CHECK_WORKING = True | 349 SKIP_CHECK_WORKING = True |
355 PROMPT_FOR_AUTHOR = False | 350 PROMPT_FOR_AUTHOR = False |
356 | 351 |
357 # Override the default properties if there is a drover.properties file. | 352 # Override the default properties if there is a drover.properties file. |
358 global file_pattern_ | 353 global file_pattern_ |
359 if os.path.exists("drover.properties"): | 354 if os.path.exists("drover.properties"): |
360 file = open("drover.properties") | 355 file = open("drover.properties") |
361 exec(file) | 356 exec(file) |
362 file.close() | 357 file.close() |
363 if FILE_PATTERN: | 358 if FILE_PATTERN: |
364 file_pattern_ = FILE_PATTERN | 359 file_pattern_ = FILE_PATTERN |
365 | 360 |
366 if (len(sys.argv) == 1): | 361 revision = options.revert or options.merge |
367 print USAGE % {"app": sys.argv[0]} | |
368 sys.exit(0) | |
369 | 362 |
370 revision = int(sys.argv[1]) | 363 if options.revert and options.branch: |
371 if ((len(sys.argv) >= 4) and (sys.argv[2] in ['--revert','-r'])): | 364 url = BRANCH_URL.replace("$branch", options.branch) |
372 url = BRANCH_URL.replace("$branch", sys.argv[3]) | |
373 else: | 365 else: |
374 url = TRUNK_URL | 366 url = TRUNK_URL |
375 action = "Merge" | |
376 | 367 |
377 working = DEFAULT_WORKING | 368 working = DEFAULT_WORKING |
378 | 369 |
379 command = 'svn log ' + url + " -r "+str(revision) + " -v" | 370 command = 'svn log ' + url + " -r "+str(revision) + " -v" |
380 os.system(command) | 371 os.system(command) |
381 | 372 |
382 if not prompt("Is this the correct revision?"): | 373 if not (options.revertbot or prompt("Is this the correct revision?")): |
383 sys.exit(0) | 374 sys.exit(0) |
384 | 375 |
385 if (os.path.exists(working)): | 376 if (os.path.exists(working)): |
386 if not (SKIP_CHECK_WORKING or prompt("Working directory: '" + working + "' a
lready exists, clobber?")): | 377 if not (options.revertbot or SKIP_CHECK_WORKING or |
| 378 prompt("Working directory: '%s' already exists, clobber?" % working)): |
387 sys.exit(0) | 379 sys.exit(0) |
388 deltree(working) | 380 deltree(working) |
389 | 381 |
390 os.makedirs(working) | 382 os.makedirs(working) |
391 os.chdir(working) | 383 os.chdir(working) |
392 | 384 |
393 if (len(sys.argv) > 1): | 385 if options.merge: |
394 if sys.argv[2] in ['--merge','-m']: | 386 action = "Merge" |
395 if (len(sys.argv) != 4): | 387 branch_url = BRANCH_URL.replace("$branch", options.branch) |
396 print "Please specify the branch # you want (i.e. 182) after --merge" | 388 # Checkout everything but stuff that got added into a new dir |
397 sys.exit(0) | 389 checkoutRevision(url, revision, branch_url) |
398 | 390 # Merge everything that changed |
399 branch_url = BRANCH_URL.replace("$branch", sys.argv[3]) | 391 mergeRevision(url, revision) |
400 # Checkout everything but stuff that got added into a new dir | 392 # "Export" files that were added from the source and add them to branch |
401 checkoutRevision(url, revision, branch_url) | 393 exportRevision(url, revision) |
402 # Merge everything that changed | 394 # Delete directories that were deleted (file deletes are handled in the |
403 mergeRevision(url, revision) | 395 # merge). |
404 # "Export" files that were added from the source and add them to branch | 396 deleteRevision(url, revision) |
405 exportRevision(url, revision) | 397 elif options.revert: |
406 # Delete directories that were deleted (file deletes are handled in the | 398 action = "Revert" |
407 # merge). | 399 if options.branch: |
408 deleteRevision(url, revision) | 400 url = BRANCH_URL.replace("$branch", options.branch) |
409 elif sys.argv[2] in ['--revert','-r']: | 401 checkoutRevision(url, revision, url, True) |
410 if (len(sys.argv) == 4): | 402 revertRevision(url, revision) |
411 url = BRANCH_URL.replace("$branch", sys.argv[3]) | 403 revertExportRevision(url, revision) |
412 checkoutRevision(url, revision, url, True) | |
413 revertRevision(url, revision) | |
414 revertExportRevision(url, revision) | |
415 action = "Revert" | |
416 else: | |
417 print "Unknown parameter " + sys.argv[2] | |
418 sys.exit(0) | |
419 | 404 |
420 # Check the base url so we actually find the author who made the change | 405 # Check the base url so we actually find the author who made the change |
421 author = getAuthor(TRUNK_URL, revision) | 406 author = getAuthor(TRUNK_URL, revision) |
422 | 407 |
423 filename = str(revision)+".txt" | 408 filename = str(revision)+".txt" |
424 out = open(filename,"w") | 409 out = open(filename,"w") |
425 out.write(action +" " + str(revision) + " - ") | 410 out.write(action +" " + str(revision) + " - ") |
426 out.write(getRevisionLog(url, revision)) | 411 out.write(getRevisionLog(url, revision)) |
427 if (author): | 412 if (author): |
428 out.write("TBR=" + author) | 413 out.write("TBR=" + author) |
429 out.close() | 414 out.close() |
430 | 415 |
431 os.system('gcl change ' + str(revision) + " " + filename) | 416 change_cmd = 'gcl change ' + str(revision) + " " + filename |
| 417 if options.revertbot: |
| 418 change_cmd += ' --silent' |
| 419 os.system(change_cmd) |
432 os.unlink(filename) | 420 os.unlink(filename) |
433 print author | 421 print author |
434 print revision | 422 print revision |
435 print ("gcl upload " + str(revision) + | 423 print ("gcl upload " + str(revision) + |
436 " --send_mail --no_try --no_presubmit --reviewers=" + author) | 424 " --send_mail --no_try --no_presubmit --reviewers=" + author) |
437 print "gcl commit " + str(revision) + " --no_presubmit --force" | |
438 print "gcl delete " + str(revision) | |
439 | 425 |
440 if prompt("Would you like to upload?"): | 426 if options.revertbot or prompt("Would you like to upload?"): |
441 if PROMPT_FOR_AUTHOR: | 427 if PROMPT_FOR_AUTHOR: |
442 author = text_prompt("Enter a new author or press enter to accept default"
, author) | 428 author = text_prompt("Enter new author or press enter to accept default", |
| 429 author) |
| 430 if options.revertbot and options.revertbot_reviewers: |
| 431 author += "," |
| 432 author += options.revertbot_reviewers |
443 gclUpload(revision, author) | 433 gclUpload(revision, author) |
444 else: | 434 else: |
445 print "Deleting the changelist." | 435 print "Deleting the changelist." |
| 436 print "gcl delete " + str(revision) |
446 os.system("gcl delete " + str(revision)) | 437 os.system("gcl delete " + str(revision)) |
447 sys.exit(0) | 438 sys.exit(0) |
448 | 439 |
449 if prompt("Would you like to commit?"): | 440 # We commit if the reverbot is set to commit automatically, or if this is |
| 441 # not the revertbot and the user agrees. |
| 442 if options.revertbot_commit or (not options.revertbot and |
| 443 prompt("Would you like to commit?")): |
| 444 print "gcl commit " + str(revision) + " --no_presubmit --force" |
450 os.system("gcl commit " + str(revision) + " --no_presubmit --force") | 445 os.system("gcl commit " + str(revision) + " --no_presubmit --force") |
451 else: | 446 else: |
452 sys.exit(0) | 447 sys.exit(0) |
453 | 448 |
454 if __name__ == "__main__": | 449 if __name__ == "__main__": |
455 sys.exit(main()) | 450 option_parser = optparse.OptionParser(usage=USAGE % {"app": sys.argv[0]}) |
| 451 option_parser.add_option('-m', '--merge', type="int", |
| 452 help='Revision to merge from trunk to branch') |
| 453 option_parser.add_option('-b', '--branch', |
| 454 help='Branch to revert or merge from') |
| 455 option_parser.add_option('-r', '--revert', type="int", |
| 456 help='Revision to revert') |
| 457 option_parser.add_option('', '--revertbot', action='store_true', |
| 458 default=False) |
| 459 option_parser.add_option('', '--revertbot-commit', action='store_true', |
| 460 default=False) |
| 461 option_parser.add_option('', '--revertbot-reviewers') |
| 462 options, args = option_parser.parse_args() |
| 463 |
| 464 if not options.merge and not options.revert: |
| 465 option_parser.error("You need at least --merge or --revert") |
| 466 sys.exit(1) |
| 467 |
| 468 if options.merge and not options.branch: |
| 469 option_parser.error("--merge requires a --branch") |
| 470 sys.exit(1) |
| 471 |
| 472 sys.exit(main(options, args)) |
OLD | NEW |