OLD | NEW |
---|---|
1 #!/usr/bin/python2 | 1 #!/usr/bin/python2 |
2 | 2 |
3 # Copyright 2014 Google Inc. | 3 # Copyright 2014 Google Inc. |
4 # | 4 # |
5 # Use of this source code is governed by a BSD-style license that can be | 5 # Use of this source code is governed by a BSD-style license that can be |
6 # found in the LICENSE file. | 6 # found in the LICENSE file. |
7 | 7 |
8 """Skia's Chromium DEPS roll script. | 8 """Skia's Chromium DEPS roll script. |
9 | 9 |
10 This script: | 10 This script: |
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
118 ' a temporary will be cloned. Defaults to SKIA_GIT_CHECKOUT' | 118 ' a temporary will be cloned. Defaults to SKIA_GIT_CHECKOUT' |
119 '_PATH, if that environment variable is set.', | 119 '_PATH, if that environment variable is set.', |
120 default=os.environ.get('SKIA_GIT_CHECKOUT_PATH')) | 120 default=os.environ.get('SKIA_GIT_CHECKOUT_PATH')) |
121 option_parser.add_option( | 121 option_parser.add_option( |
122 '', '--search_depth', type='int', default=100, | 122 '', '--search_depth', type='int', default=100, |
123 help='How far back to look for the revision.') | 123 help='How far back to look for the revision.') |
124 option_parser.add_option( | 124 option_parser.add_option( |
125 '', '--git_path', help='Git executable, defaults to "git".', | 125 '', '--git_path', help='Git executable, defaults to "git".', |
126 default='git') | 126 default='git') |
127 option_parser.add_option( | 127 option_parser.add_option( |
128 '', '--save_branches', help='Save the temporary branches', | 128 '', '--save_branches', |
129 action='store_true', dest='save_branches', default=False) | 129 help='Save the temporary branches (default)', |
130 action='store_true', dest='save_branches', default=True) | |
131 option_parser.add_option( | |
132 '', '--delete_branches', help='Delete the temporary branches', | |
133 action='store_false', dest='save_branches', default=True) | |
borenet
2014/01/07 21:39:34
If these are changing the same variable, why do we
hal.canary
2014/01/08 18:57:38
Done.
| |
130 option_parser.add_option( | 134 option_parser.add_option( |
131 '', '--verbose', help='Do not suppress the output from `git cl`.', | 135 '', '--verbose', help='Do not suppress the output from `git cl`.', |
132 action='store_true', dest='verbose', default=False) | 136 action='store_true', dest='verbose', default=False) |
133 option_parser.add_option( | 137 option_parser.add_option( |
134 '', '--skip_cl_upload', help='Skip the cl upload step; useful' | 138 '', '--skip_cl_upload', help='Skip the cl upload step; useful' |
135 ' for testing or with --save_branches.', | 139 ' for testing or with --save_branches.', |
136 action='store_true', default=False) | 140 action='store_true', default=False) |
137 | 141 |
138 default_bots_help = ( | 142 default_bots_help = ( |
139 'Comma-separated list of bots, defaults to a list of %d bots.' | 143 'Comma-separated list of bots, defaults to a list of %d bots.' |
(...skipping 121 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
261 'origin/master'], cwd=skia_dir) | 265 'origin/master'], cwd=skia_dir) |
262 | 266 |
263 if revision < 0 or not git_hash: | 267 if revision < 0 or not git_hash: |
264 raise DepsRollError('Git hash can not be found.') | 268 raise DepsRollError('Git hash can not be found.') |
265 return revision, git_hash | 269 return revision, git_hash |
266 finally: | 270 finally: |
267 if use_temp: | 271 if use_temp: |
268 shutil.rmtree(skia_dir) | 272 shutil.rmtree(skia_dir) |
269 | 273 |
270 | 274 |
275 class ChangeDir(object): | |
276 """Use with a with-statement to temporarily change directories.""" | |
277 # pylint: disable=I0011,R0903 | |
278 | |
279 def __init__(self, directory): | |
280 self._directory = directory | |
281 | |
282 def __enter__(self): | |
283 cwd = os.getcwd() | |
284 os.chdir(self._directory) | |
285 self._directory = cwd | |
286 | |
287 def __exit__(self, etype, value, traceback): | |
288 os.chdir(self._directory) | |
289 | |
290 | |
271 class GitBranchCLUpload(object): | 291 class GitBranchCLUpload(object): |
272 """Class to manage git branches and git-cl-upload. | 292 """Class to manage git branches and git-cl-upload. |
273 | 293 |
274 This class allows one to create a new branch in a repository based | 294 This class allows one to create a new branch in a repository based |
275 off of origin/master, make changes to the tree inside the | 295 off of origin/master, make changes to the tree inside the |
276 with-block, upload that new branch to Rietveld, restore the original | 296 with-block, upload that new branch to Rietveld, restore the original |
277 tree state, and delete the local copy of the new branch. | 297 tree state, and delete the local copy of the new branch. |
278 | 298 |
279 See roll_deps() for an example of use. | 299 See roll_deps() for an example of use. |
280 | 300 |
(...skipping 25 matching lines...) Expand all Loading... | |
306 self.issue = None | 326 self.issue = None |
307 | 327 |
308 def stage_for_commit(self, *paths): | 328 def stage_for_commit(self, *paths): |
309 """Calls `git add ...` on each argument. | 329 """Calls `git add ...` on each argument. |
310 | 330 |
311 Args: | 331 Args: |
312 *paths: (list of strings) list of filenames to pass to `git add`. | 332 *paths: (list of strings) list of filenames to pass to `git add`. |
313 """ | 333 """ |
314 self._file_list.extend(paths) | 334 self._file_list.extend(paths) |
315 | 335 |
336 def set_message(self, message): | |
337 """Change the message.""" | |
338 self._message = message | |
339 | |
316 def __enter__(self): | 340 def __enter__(self): |
317 git = self._config.git | 341 git = self._config.git |
318 def branch_exists(branch): | 342 def branch_exists(branch): |
319 """Return true iff branch exists.""" | 343 """Return true iff branch exists.""" |
320 return 0 == subprocess.call( | 344 return 0 == subprocess.call( |
321 [git, 'show-ref', '--quiet', branch]) | 345 [git, 'show-ref', '--quiet', branch]) |
322 def has_diff(): | 346 def has_diff(): |
323 """Return true iff repository has uncommited changes.""" | 347 """Return true iff repository has uncommited changes.""" |
324 return bool(subprocess.call([git, 'diff', '--quiet', 'HEAD'])) | 348 return bool(subprocess.call([git, 'diff', '--quiet', 'HEAD'])) |
349 def get_svn_revision(): | |
350 """Works in both git and git-svn. returns a string""" | |
351 last_log_message = subprocess.check_output( | |
352 [git, 'log', '-n', '1', '--format=format:%B']) | |
353 svn_format = ( | |
354 '(git-svn-id: svn://svn.chromium.org/chrome/trunk/src@|' | |
355 'SVN changes up to revision )([0-9]+)') | |
356 search = re.search(svn_format, last_log_message) | |
357 if not search: | |
358 raise DepsRollError( | |
359 'Revision number missing from Chromium origin/master.') | |
360 return search.group(2) | |
325 self._stash = has_diff() | 361 self._stash = has_diff() |
326 if self._stash: | 362 if self._stash: |
327 check_call([git, 'stash', 'save']) | 363 check_call([git, 'stash', 'save']) |
328 try: | 364 try: |
329 self._original_branch = strip_output( | 365 self._original_branch = strip_output( |
330 [git, 'symbolic-ref', '--short', 'HEAD']) | 366 [git, 'symbolic-ref', '--short', 'HEAD']) |
331 except (subprocess.CalledProcessError,): | 367 except (subprocess.CalledProcessError,): |
332 self._original_branch = strip_output( | 368 self._original_branch = strip_output( |
333 [git, 'rev-parse', 'HEAD']) | 369 [git, 'rev-parse', 'HEAD']) |
334 | 370 |
335 if not self._branch_name: | 371 if not self._branch_name: |
336 self._branch_name = self._config.default_branch_name | 372 self._branch_name = self._config.default_branch_name |
337 | 373 |
338 if branch_exists(self._branch_name): | 374 if branch_exists(self._branch_name): |
339 check_call([git, 'checkout', '-q', 'master']) | 375 check_call([git, 'checkout', '-q', 'master']) |
340 check_call([git, 'branch', '-q', '-D', self._branch_name]) | 376 check_call([git, 'branch', '-q', '-D', self._branch_name]) |
341 | 377 |
342 check_call( | 378 check_call( |
343 [git, 'checkout', '-q', '-b', | 379 [git, 'checkout', '-q', '-b', |
344 self._branch_name, 'origin/master']) | 380 self._branch_name, 'origin/master']) |
345 | 381 self._svn_info = get_svn_revision() |
346 svn_info = subprocess.check_output(['git', 'svn', 'info']) | |
347 svn_info_search = re.search(r'Last Changed Rev: ([0-9]+)\W', svn_info) | |
348 assert svn_info_search | |
349 self._svn_info = svn_info_search.group(1) | |
350 | 382 |
351 def __exit__(self, etype, value, traceback): | 383 def __exit__(self, etype, value, traceback): |
352 # pylint: disable=I0011,R0912 | 384 # pylint: disable=I0011,R0912 |
353 git = self._config.git | 385 git = self._config.git |
354 def quiet_check_call(*args, **kwargs): | 386 def quiet_check_call(*args, **kwargs): |
355 """Call check_call, but pipe output to devnull.""" | 387 """Call check_call, but pipe output to devnull.""" |
356 with open(os.devnull, 'w') as devnull: | 388 with open(os.devnull, 'w') as devnull: |
357 check_call(*args, stdout=devnull, **kwargs) | 389 check_call(*args, stdout=devnull, **kwargs) |
358 | 390 |
359 for filename in self._file_list: | 391 for filename in self._file_list: |
(...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
450 revision: (int) Skia SVN revision. | 482 revision: (int) Skia SVN revision. |
451 git_hash: (string) Skia Git hash. | 483 git_hash: (string) Skia Git hash. |
452 | 484 |
453 Returns: | 485 Returns: |
454 a tuple containing textual description of the two issues. | 486 a tuple containing textual description of the two issues. |
455 | 487 |
456 Raises: | 488 Raises: |
457 OSError: failed to execute git or git-cl. | 489 OSError: failed to execute git or git-cl. |
458 subprocess.CalledProcessError: git returned unexpected status. | 490 subprocess.CalledProcessError: git returned unexpected status. |
459 """ | 491 """ |
492 def search_within_file(path, pattern, default=None): | |
borenet
2014/01/07 21:31:12
I think this function could live outside of roll_d
hal.canary
2014/01/08 18:57:38
Done.
It's in a utility class. If you want to ma
| |
493 """Search for regular expression in a file. | |
494 | |
495 Opens a file for reading and searches line by line for a match to | |
496 the regex and returns the parenthesized group named return for the | |
497 first match. | |
borenet
2014/01/07 21:31:12
Please document that the pattern can't contain new
hal.canary
2014/01/08 18:57:38
Done.
| |
498 | |
499 For example: | |
500 pattern = '^root(:[^:]*){4}:(?P<return>[^:]*)' | |
501 search_within_file('/etc/passwd', pattern) | |
502 should return root's home directory (/root on my system). | |
503 | |
504 Args: | |
505 path: (string) filename | |
506 pattern: (string) to be passed to re.compile | |
507 default: what to return if no match | |
508 | |
509 Returns: | |
510 A string or whatever default is | |
511 """ | |
512 with open(path, 'r') as input_stream: | |
513 pattern_object = re.compile(pattern) | |
514 for line in input_stream: | |
515 match = pattern_object.search(line) | |
516 if match: | |
517 return match.group('return') | |
518 return default | |
519 | |
520 def search_within_string(input_string, pattern, default=None): | |
borenet
2014/01/07 21:31:12
Same comment about this function: I think it could
hal.canary
2014/01/08 18:57:38
Done.
| |
521 """Search for regular expression in a string. | |
522 | |
523 Args: | |
524 input_string: (string) to be searched | |
525 pattern: (string) to be passed to re.compile | |
526 default: what to return if no match | |
527 | |
528 Returns: | |
529 A string or whatever default is | |
530 """ | |
531 match = re.search(pattern, input_string) | |
532 return match.group('return') if match else default | |
533 | |
460 git = config.git | 534 git = config.git |
461 cwd = os.getcwd() | 535 with ChangeDir(config.chromium_path): |
462 os.chdir(config.chromium_path) | |
463 try: | |
464 check_call([git, 'fetch', '-q', 'origin']) | 536 check_call([git, 'fetch', '-q', 'origin']) |
465 master_hash = strip_output( | 537 master_hash = strip_output( |
466 [git, 'show-ref', 'origin/master', '--hash']) | 538 [git, 'show-ref', 'origin/master', '--hash']) |
467 | 539 |
540 branch = None | |
541 | |
468 # master_hash[8] gives each whitespace CL a unique name. | 542 # master_hash[8] gives each whitespace CL a unique name. |
469 message = ('whitespace change %s\n\nThis CL was created by' | 543 message = ('whitespace change %s\n\nThis CL was created by' |
470 ' Skia\'s roll_deps.py script.\n') % master_hash[:8] | 544 ' Skia\'s roll_deps.py script.\n') % master_hash[:8] |
471 branch = branch_name(message) if config.save_branches else None | 545 if config.save_branches: |
546 branch = 'control_%s' % master_hash[:8] | |
borenet
2014/01/07 21:31:12
Maybe we should have a short_hash(hash) function?
hal.canary
2014/01/08 18:57:38
In Python, string[:8] is clear enough.
| |
472 | 547 |
473 codereview = GitBranchCLUpload(config, message, branch) | 548 codereview = GitBranchCLUpload(config, message, branch) |
474 with codereview: | 549 with codereview: |
475 with open('build/whitespace_file.txt', 'a') as output_stream: | 550 with open('build/whitespace_file.txt', 'a') as output_stream: |
476 output_stream.write('\nCONTROL\n') | 551 output_stream.write('\nCONTROL\n') |
477 codereview.stage_for_commit('build/whitespace_file.txt') | 552 codereview.stage_for_commit('build/whitespace_file.txt') |
478 whitespace_cl = codereview.issue | 553 whitespace_cl = codereview.issue |
479 if branch: | 554 if branch: |
480 whitespace_cl = '%s\n branch: %s' % (whitespace_cl, branch) | 555 whitespace_cl = '%s\n branch: %s' % (whitespace_cl, branch) |
481 control_url_match = re.search('https?://[^) ]+', codereview.issue) | 556 |
482 if control_url_match: | 557 control_url = search_within_string( |
483 message = ('roll skia DEPS to %d\n\nThis CL was created by' | 558 codereview.issue, '(?P<return>https?://[^) ]+)', '?') |
484 ' Skia\'s roll_deps.py script.\n\ncontrol: %s' | 559 |
485 % (revision, control_url_match.group(0))) | 560 if config.save_branches: |
486 else: | 561 branch = 'roll_%d_%s' % (revision, master_hash[:8]) |
borenet
2014/01/07 21:39:34
I just found this: http://stackoverflow.com/a/6065
hal.canary
2014/01/08 18:57:38
Good idea, but defering to robert.
| |
487 message = ('roll skia DEPS to %d\n\nThis CL was created by' | 562 codereview = GitBranchCLUpload(config, '?', branch) |
488 ' Skia\'s roll_deps.py script.') % revision | |
489 branch = branch_name(message) if config.save_branches else None | |
490 codereview = GitBranchCLUpload(config, message, branch) | |
491 with codereview: | 563 with codereview: |
564 old_revision = search_within_file( | |
565 'DEPS', '"skia_revision": "(?P<return>[0-9]+)",', '?') | |
566 assert revision != int(old_revision) | |
borenet
2014/01/07 21:31:12
Can we move the whitespace CL uploading to after t
hal.canary
2014/01/08 18:57:38
I moved that assert up. And turned it into a chec
| |
567 codereview.set_message( | |
568 'roll skia DEPS to %d\n\nold revision:%s\n' | |
569 'new revision:%d\nThis CL was created by' | |
570 ' Skia\'s roll_deps.py script.\n\ncontrol: %s' | |
571 % (revision, old_revision, revision, control_url)) | |
492 change_skia_deps(revision, git_hash, 'DEPS') | 572 change_skia_deps(revision, git_hash, 'DEPS') |
493 codereview.stage_for_commit('DEPS') | 573 codereview.stage_for_commit('DEPS') |
494 deps_cl = codereview.issue | 574 deps_cl = codereview.issue |
495 if branch: | 575 if branch: |
496 deps_cl = '%s\n branch: %s' % (deps_cl, branch) | 576 deps_cl = '%s\n branch: %s' % (deps_cl, branch) |
497 | 577 |
498 return deps_cl, whitespace_cl | 578 return deps_cl, whitespace_cl |
499 finally: | |
500 os.chdir(cwd) | |
501 | 579 |
502 | 580 |
503 def find_hash_and_roll_deps(config, revision): | 581 def find_hash_and_roll_deps(config, revision): |
504 """Call find_hash_from_revision() and roll_deps(). | 582 """Call find_hash_from_revision() and roll_deps(). |
505 | 583 |
506 The calls to git will be verbose on standard output. After a | 584 The calls to git will be verbose on standard output. After a |
507 successful upload of both issues, print links to the new | 585 successful upload of both issues, print links to the new |
508 codereview issues. | 586 codereview issues. |
509 | 587 |
510 Args: | 588 Args: |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
543 if not test_git_executable(options.git_path): | 621 if not test_git_executable(options.git_path): |
544 option_parser.error('Invalid git executable.') | 622 option_parser.error('Invalid git executable.') |
545 | 623 |
546 config = DepsRollConfig(options) | 624 config = DepsRollConfig(options) |
547 find_hash_and_roll_deps(config, options.revision) | 625 find_hash_and_roll_deps(config, options.revision) |
548 | 626 |
549 | 627 |
550 if __name__ == '__main__': | 628 if __name__ == '__main__': |
551 main(sys.argv[1:]) | 629 main(sys.argv[1:]) |
552 | 630 |
OLD | NEW |