| 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 |