| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2014 The Chromium Authors. All rights reserved. | 2 # Copyright 2014 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 | 6 |
| 7 """Automates creation and management of DEPS roll CLs. | 7 """Automates creation and management of DEPS roll CLs. |
| 8 | 8 |
| 9 This script is designed to be run in a loop (eg. with auto_roll_wrapper.sh) or | 9 This script is designed to be run in a loop (eg. with auto_roll_wrapper.sh) or |
| 10 on a timer. It may take one of several actions, depending on the state of | 10 on a timer. It may take one of several actions, depending on the state of |
| (...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 97 | 97 |
| 98 | 98 |
| 99 def _current_gardener_emails(): | 99 def _current_gardener_emails(): |
| 100 return _emails_from_url(BLINK_SHERIFF_URL) | 100 return _emails_from_url(BLINK_SHERIFF_URL) |
| 101 | 101 |
| 102 | 102 |
| 103 def _current_sheriff_emails(): | 103 def _current_sheriff_emails(): |
| 104 return _emails_from_url(CHROMIUM_SHERIFF_URL) | 104 return _emails_from_url(CHROMIUM_SHERIFF_URL) |
| 105 | 105 |
| 106 | 106 |
| 107 def _do_git_fetch(git_dir): |
| 108 subprocess2.check_call(['git', '--git-dir', git_dir, 'fetch']) |
| 109 |
| 110 |
| 107 PROJECT_CONFIGS = { | 111 PROJECT_CONFIGS = { |
| 108 'blink': { | 112 'blink': { |
| 109 'extra_emails_fn': _current_gardener_emails, | 113 'extra_emails_fn': _current_gardener_emails, |
| 110 'git_mode': True, | 114 'git_mode': True, |
| 111 'path_to_project': os.path.join('third_party', 'WebKit'), | 115 'path_to_project': os.path.join('third_party', 'WebKit'), |
| 112 'project_alias': 'webkit', | 116 'project_alias': 'webkit', |
| 113 'revision_link_fn': lambda before_rev, after_rev: ( | 117 'revision_link_fn': lambda before_rev, after_rev: ( |
| 114 'https://chromium.googlesource.com/chromium/blink/+log/%s..%s' % ( | 118 'https://chromium.googlesource.com/chromium/blink/+log/%s..%s' % ( |
| 115 before_rev, after_rev)), | 119 before_rev, after_rev)), |
| 116 }, | 120 }, |
| (...skipping 24 matching lines...) Expand all Loading... |
| 141 ROLL_DESCRIPTION_REGEXP = ROLL_DESCRIPTION_STR % { | 145 ROLL_DESCRIPTION_REGEXP = ROLL_DESCRIPTION_STR % { |
| 142 'project': '%(project)s', | 146 'project': '%(project)s', |
| 143 'from_revision': r'(?P<from_revision>[0-9a-fA-F]{2,40})', | 147 'from_revision': r'(?P<from_revision>[0-9a-fA-F]{2,40})', |
| 144 'to_revision': r'(?P<to_revision>[0-9a-fA-F]{2,40})' | 148 'to_revision': r'(?P<to_revision>[0-9a-fA-F]{2,40})' |
| 145 } | 149 } |
| 146 | 150 |
| 147 # FIXME: These are taken from gardeningserver.py and should be shared. | 151 # FIXME: These are taken from gardeningserver.py and should be shared. |
| 148 CHROMIUM_SVN_DEPS_URL = 'http://src.chromium.org/chrome/trunk/src/DEPS' | 152 CHROMIUM_SVN_DEPS_URL = 'http://src.chromium.org/chrome/trunk/src/DEPS' |
| 149 # 'webkit_revision': '149598', | 153 # 'webkit_revision': '149598', |
| 150 REVISION_REGEXP = ( | 154 REVISION_REGEXP = ( |
| 151 r'^ [\'"]%s_revision[\'"]: [\'"](?P<revision>[0-9a-fA-F]{2,40})[\'"],') | 155 r'^ [\'"]%s_revision[\'"]: [\'"](?P<revision>[0-9a-fA-F]{2,40})[\'"],' |
| 156 r'( # from svn revision (?P<svn_revision>\d+))?') |
| 152 | 157 |
| 153 ROLL_BOT_INSTRUCTIONS = textwrap.dedent( | 158 ROLL_BOT_INSTRUCTIONS = textwrap.dedent( |
| 154 '''This roll was created by the Blink AutoRollBot. | 159 '''This roll was created by the Blink AutoRollBot. |
| 155 http://www.chromium.org/blink/blinkrollbot''') | 160 http://www.chromium.org/blink/blinkrollbot''') |
| 156 | 161 |
| 157 PLEASE_RESUME_NAG = textwrap.dedent(''' | 162 PLEASE_RESUME_NAG = textwrap.dedent(''' |
| 158 Rollbot was stopped by the presence of 'STOP' in an earlier comment. | 163 Rollbot was stopped by the presence of 'STOP' in an earlier comment. |
| 159 The last update to this issue was over %(stop_nag_timeout)s hours ago. | 164 The last update to this issue was over %(stop_nag_timeout)s hours ago. |
| 160 Please close this issue as soon as possible to allow the bot to continue. | 165 Please close this issue as soon as possible to allow the bot to continue. |
| 161 | 166 |
| 162 Please email (%(admin)s) if the Rollbot is causing trouble. | 167 Please email (%(admin)s) if the Rollbot is causing trouble. |
| 163 ''' % {'admin': ADMIN_EMAIL, 'stop_nag_timeout': STOP_NAG_TIME_LIMIT}) | 168 ''' % {'admin': ADMIN_EMAIL, 'stop_nag_timeout': STOP_NAG_TIME_LIMIT}) |
| 164 | 169 |
| 165 def __init__(self, project, author, path_to_chrome): | 170 def __init__(self, project, author, path_to_chrome): |
| 166 self._author = author | 171 self._author = author |
| 167 self._project = project | 172 self._project = project |
| 168 self._path_to_chrome = path_to_chrome | 173 self._path_to_chrome = path_to_chrome |
| 169 self._rietveld = rietveld.Rietveld( | 174 self._rietveld = rietveld.Rietveld( |
| 170 self.RIETVELD_URL, self._author, None) | 175 self.RIETVELD_URL, self._author, None) |
| 171 self._cached_last_roll_revision = None | 176 self._cached_last_roll_revision = None |
| 177 self._cached_deps_contents = None |
| 172 | 178 |
| 173 project_config = PROJECT_CONFIGS.get(self._project, { | 179 project_config = PROJECT_CONFIGS.get(self._project, { |
| 174 'path_to_project': os.path.join('third_party', self._project), | 180 'path_to_project': os.path.join('third_party', self._project), |
| 175 'revision_link_fn': lambda before_rev, after_ref: '', | 181 'revision_link_fn': lambda before_rev, after_ref: '', |
| 176 }) | 182 }) |
| 177 self._project_alias = project_config.get('project_alias', self._project) | 183 self._project_alias = project_config.get('project_alias', self._project) |
| 178 self._path_to_project = project_config['path_to_project'] | 184 self._path_to_project = project_config['path_to_project'] |
| 179 self._get_revision_link = project_config['revision_link_fn'] | 185 self._get_revision_link = project_config['revision_link_fn'] |
| 180 self._get_extra_emails = project_config.get('extra_emails_fn', lambda: []) | 186 self._get_extra_emails = project_config.get('extra_emails_fn', lambda: []) |
| 181 self._git_mode = project_config.get('git_mode', False) | 187 self._git_mode = project_config.get('git_mode', False) |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 229 return os.path.join(self._path_to_chrome, *components) | 235 return os.path.join(self._path_to_chrome, *components) |
| 230 | 236 |
| 231 def _last_roll_revision(self): | 237 def _last_roll_revision(self): |
| 232 """Returns the revision of the last roll. | 238 """Returns the revision of the last roll. |
| 233 | 239 |
| 234 Returns: | 240 Returns: |
| 235 revision of the last roll; either a 40-character Git commit hash or an | 241 revision of the last roll; either a 40-character Git commit hash or an |
| 236 SVN revision number. | 242 SVN revision number. |
| 237 """ | 243 """ |
| 238 if not self._cached_last_roll_revision: | 244 if not self._cached_last_roll_revision: |
| 239 subprocess2.check_call(['git', '--git-dir', self._chromium_git_dir, | 245 self._cached_last_roll_revision = self._last_roll_revision_helper( |
| 240 'fetch']) | 246 'revision') |
| 241 git_show_cmd = ['git', '--git-dir', self._chromium_git_dir, 'show', | |
| 242 'origin/master:DEPS'] | |
| 243 deps_contents = subprocess2.check_output(git_show_cmd) | |
| 244 pattern = self.REVISION_REGEXP % self._project_alias | |
| 245 match = re.search(pattern, deps_contents, re.MULTILINE) | |
| 246 self._cached_last_roll_revision = match.group('revision') | |
| 247 if self._git_mode: | 247 if self._git_mode: |
| 248 assert len(self._cached_last_roll_revision) == 40 | 248 assert len(self._cached_last_roll_revision) == 40 |
| 249 return self._cached_last_roll_revision | 249 return self._cached_last_roll_revision |
| 250 | 250 |
| 251 def _last_roll_revision_svn(self): |
| 252 return self._last_roll_revision_helper('svn_revision') |
| 253 |
| 254 def _last_roll_revision_helper(self, match_group): |
| 255 if not self._cached_deps_contents: |
| 256 git_show_cmd = ['git', '--git-dir', self._chromium_git_dir, 'show', |
| 257 'origin/master:DEPS'] |
| 258 self._cached_deps_contents = subprocess2.check_output(git_show_cmd) |
| 259 |
| 260 pattern = self.REVISION_REGEXP % self._project_alias |
| 261 match = re.search(pattern, self._cached_deps_contents, re.MULTILINE) |
| 262 return match.group(match_group) |
| 263 |
| 251 def _current_revision(self): | 264 def _current_revision(self): |
| 252 subprocess2.check_call(['git', '--git-dir', self._project_git_dir, | |
| 253 'fetch']) | |
| 254 if self._git_mode: | 265 if self._git_mode: |
| 255 git_revparse_cmd = ['git', '--git-dir', self._project_git_dir, | 266 git_revparse_cmd = ['git', '--git-dir', self._project_git_dir, |
| 256 'rev-parse', 'origin/master'] | 267 'rev-parse', 'origin/master'] |
| 257 return subprocess2.check_output(git_revparse_cmd).rstrip() | 268 return subprocess2.check_output(git_revparse_cmd).rstrip() |
| 258 else: | 269 else: |
| 259 git_show_cmd = ['git', '--git-dir', self._project_git_dir, 'show', '-s', | 270 return self._current_revision_svn() |
| 260 'origin/master'] | 271 |
| 261 git_log = subprocess2.check_output(git_show_cmd) | 272 def _current_revision_svn(self): |
| 262 match = re.search('^\s*git-svn-id:.*@(?P<svn_revision>\d+)\ ', | 273 git_show_cmd = ['git', '--git-dir', self._project_git_dir, 'show', '-s', |
| 263 git_log, re.MULTILINE) | 274 'origin/master'] |
| 264 if match: | 275 git_log = subprocess2.check_output(git_show_cmd) |
| 265 return match.group('svn_revision') | 276 match = re.search('^\s*git-svn-id:.*@(?P<svn_revision>\d+)\ ', |
| 266 else: | 277 git_log, re.MULTILINE) |
| 267 raise AutoRollException( | 278 if match: |
| 268 'Could not determine the current SVN revision.') | 279 return match.group('svn_revision') |
| 280 else: |
| 281 return None |
| 269 | 282 |
| 270 def _emails_to_cc_on_rolls(self): | 283 def _emails_to_cc_on_rolls(self): |
| 271 return _filter_emails(self._get_extra_emails()) | 284 return _filter_emails(self._get_extra_emails()) |
| 272 | 285 |
| 273 def _start_roll(self, new_roll_revision, commit_msg): | 286 def _start_roll(self, new_roll_revision, commit_msg): |
| 274 roll_branch = '%s_roll' % self._project | 287 roll_branch = '%s_roll' % self._project |
| 275 cwd_kwargs = {'cwd': self._path_to_chrome} | 288 cwd_kwargs = {'cwd': self._path_to_chrome} |
| 276 subprocess2.check_call(['git', 'clean', '-d', '-f'], **cwd_kwargs) | 289 subprocess2.check_call(['git', 'clean', '-d', '-f'], **cwd_kwargs) |
| 277 subprocess2.call(['git', 'rebase', '--abort'], **cwd_kwargs) | 290 subprocess2.call(['git', 'rebase', '--abort'], **cwd_kwargs) |
| 278 subprocess2.call(['git', 'branch', '-D', roll_branch], **cwd_kwargs) | 291 subprocess2.call(['git', 'branch', '-D', roll_branch], **cwd_kwargs) |
| (...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 386 issue = None | 399 issue = None |
| 387 | 400 |
| 388 if issue: | 401 if issue: |
| 389 if self._rollbot_should_stop(issue): | 402 if self._rollbot_should_stop(issue): |
| 390 return 1 | 403 return 1 |
| 391 if not self._maybe_close_active_roll(issue): | 404 if not self._maybe_close_active_roll(issue): |
| 392 print '%s is still active, nothing to do.' % \ | 405 print '%s is still active, nothing to do.' % \ |
| 393 self._url_for_issue(issue_number) | 406 self._url_for_issue(issue_number) |
| 394 return 0 | 407 return 0 |
| 395 | 408 |
| 409 _do_git_fetch(self._chromium_git_dir) |
| 410 _do_git_fetch(self._project_git_dir) |
| 411 |
| 396 last_roll_revision = self._last_roll_revision() | 412 last_roll_revision = self._last_roll_revision() |
| 397 new_roll_revision = self._current_revision() | 413 new_roll_revision = self._current_revision() |
| 414 |
| 415 if not new_roll_revision: |
| 416 raise AutoRollException( |
| 417 'Could not determine the current revision.') |
| 418 |
| 419 if self._git_mode: |
| 420 last_roll_revision_svn = self._last_roll_revision_svn() |
| 421 new_roll_revision_svn = self._current_revision_svn() |
| 422 else: |
| 423 last_roll_revision_svn = None |
| 424 new_roll_revision_svn = None |
| 425 |
| 398 self._compare_revisions(last_roll_revision, new_roll_revision) | 426 self._compare_revisions(last_roll_revision, new_roll_revision) |
| 399 | 427 |
| 400 display_from_rev = ( | 428 display_from_rev = ( |
| 401 self._short_rev(last_roll_revision) if self._git_mode | 429 self._short_rev(last_roll_revision) if self._git_mode |
| 402 else last_roll_revision) | 430 else last_roll_revision) |
| 403 display_to_rev = ( | 431 display_to_rev = ( |
| 404 self._short_rev(new_roll_revision) if self._git_mode | 432 self._short_rev(new_roll_revision) if self._git_mode |
| 405 else new_roll_revision) | 433 else new_roll_revision) |
| 406 commit_msg = self.ROLL_DESCRIPTION_STR % { | 434 commit_msg = self.ROLL_DESCRIPTION_STR % { |
| 407 'project': self._project.title(), | 435 'project': self._project.title(), |
| 408 'from_revision': display_from_rev, | 436 'from_revision': display_from_rev, |
| 409 'to_revision': display_to_rev, | 437 'to_revision': display_to_rev, |
| 410 } | 438 } |
| 439 |
| 440 if last_roll_revision_svn and new_roll_revision_svn: |
| 441 commit_msg += ' (svn %s:%s)' % ( |
| 442 last_roll_revision_svn, new_roll_revision_svn) |
| 443 |
| 411 revlink = self._get_revision_link(last_roll_revision, new_roll_revision) | 444 revlink = self._get_revision_link(last_roll_revision, new_roll_revision) |
| 412 if revlink: | 445 if revlink: |
| 413 commit_msg += '\n\n' + revlink | 446 commit_msg += '\n\n' + revlink |
| 414 | 447 |
| 415 if self._cq_extra_trybots: | 448 if self._cq_extra_trybots: |
| 416 commit_msg += '\n\n' + CQ_EXTRA_TRYBOTS + ','.join(self._cq_extra_trybots) | 449 commit_msg += '\n\n' + CQ_EXTRA_TRYBOTS + ','.join(self._cq_extra_trybots) |
| 417 | 450 |
| 418 self._start_roll(new_roll_revision, commit_msg) | 451 self._start_roll(new_roll_revision, commit_msg) |
| 419 return 0 | 452 return 0 |
| 420 | 453 |
| (...skipping 15 matching lines...) Expand all Loading... |
| 436 _, args = parser.parse_args() | 469 _, args = parser.parse_args() |
| 437 if len(args) != 3: | 470 if len(args) != 3: |
| 438 parser.print_usage() | 471 parser.print_usage() |
| 439 return 1 | 472 return 1 |
| 440 | 473 |
| 441 AutoRoller(*args).main() | 474 AutoRoller(*args).main() |
| 442 | 475 |
| 443 | 476 |
| 444 if __name__ == '__main__': | 477 if __name__ == '__main__': |
| 445 sys.exit(main()) | 478 sys.exit(main()) |
| OLD | NEW |