| # Copyright 2020 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import re |
| from urlparse import urlparse |
| |
| from recipe_engine import post_process |
| |
| DEPS = [ |
| 'fuchsia/gerrit', |
| 'fuchsia/gitiles', |
| 'fuchsia/status_check', |
| 'recipe_engine/buildbucket', |
| 'recipe_engine/json', |
| 'recipe_engine/properties', |
| 'recipe_engine/step', |
| ] |
| |
| # The value of this footer should be an https git repo URL. |
| ROLLED_REPO_FOOTER = 'Rolled-Repo' |
| |
| # This footer should have values of the form "<start_commit>..<end_commit>" |
| # (where <start_commit> is the old pinned revision, rather than the first |
| # rolled commit). For example, "9c21da0..5051fd4" includes all the commits |
| # after 9c21da0, up to and including 5051fd4. |
| ROLLED_COMMITS_FOOTER = 'Rolled-Commits' |
| |
| |
| def read_footer(commit_msg, footer_name): |
| footer_line_prefix = '%s: ' % footer_name |
| for line in reversed(commit_msg.splitlines()): |
| if line.startswith(footer_line_prefix): |
| return line[len(footer_line_prefix):].strip() |
| return None |
| |
| |
| def gitiles_to_gerrit_host(gitiles_url): |
| gitiles_host_parts = urlparse(gitiles_url).netloc.split('.') |
| gerrit_hostname = '.'.join([gitiles_host_parts[0] + '-review'] + |
| gitiles_host_parts[1:]) |
| return 'https://%s' % gerrit_hostname |
| |
| |
| def RunSteps(api): |
| commit = api.buildbucket.build.input.gitiles_commit |
| assert commit.host and commit.project and commit.id, ( |
| 'recipe must be triggered by a gitiles commit') |
| |
| dest_repo = 'https://%s/%s' % (commit.host, commit.project) |
| dest_log = api.gitiles.log( |
| dest_repo, commit.id, limit=1, step_name='get commit message') |
| commit_msg = dest_log[0]['message'] |
| |
| source_repo = read_footer(commit_msg, ROLLED_REPO_FOOTER) |
| if not source_repo: |
| api.step('no "%s" footer in commit message' % ROLLED_REPO_FOOTER, None) |
| return |
| |
| commit_range = read_footer(commit_msg, ROLLED_COMMITS_FOOTER) |
| assert commit_range, 'found "%s" footer but no "%s" footer' % ( |
| ROLLED_REPO_FOOTER, ROLLED_COMMITS_FOOTER) |
| |
| source_log = api.gitiles.log( |
| source_repo, commit_range, step_name='log source repo') |
| |
| api.gerrit.host = gitiles_to_gerrit_host(source_repo) |
| roll_cl_url = 'http://go/roll-cl/%s' % commit.id |
| |
| # Defer results because comment failures shouldn't prevent the roller from |
| # commenting on the rest of the changes. |
| with api.step.nest('comment successful roll'), api.step.defer_results(): |
| change_id_regex = re.compile(r'^Change-Id:\s*(\w+)\s*$', re.MULTILINE) |
| for rolled_commit in source_log: |
| # Check for Change-Id in the commit message. If no Change-Id is in the |
| # message then commenting will fail, so don't bother trying to comment. |
| # We still pass revision hash into gerrit.set_review() because Change-Ids |
| # are sometimes copied when cherry-picking and are less reliable than a |
| # revision hash at uniquely identifying a CL. |
| if not change_id_regex.search(rolled_commit['message']): |
| continue |
| |
| revision = rolled_commit['id'] |
| api.gerrit.set_review( |
| 'comment on %s' % revision, |
| revision, |
| message='Change has been successfully rolled: %s' % roll_cl_url, |
| test_data=api.json.test_api.output({}), |
| ) |
| cl_link = '%s/q/%s' % (api.gerrit.host, revision) |
| api.step.active_result.presentation.links['gerrit link'] = cl_link |
| |
| |
| def GenTests(api): |
| # yapf: disable |
| yield ( |
| api.status_check.test('basic') |
| + api.buildbucket.ci_build() |
| + api.gitiles.log( |
| 'get commit message', |
| 'A', |
| n=1, |
| extra_footers={ |
| ROLLED_REPO_FOOTER: 'https://foo.googlesource.com/bar-project', |
| ROLLED_COMMITS_FOOTER: '1000005..100000a', |
| }) |
| + api.gitiles.log('log source repo', 'B', n=6, add_change_id=True) |
| ) |
| |
| yield ( |
| api.status_check.test('no_change_ids') |
| + api.buildbucket.ci_build() |
| + api.gitiles.log( |
| 'get commit message', |
| 'A', |
| n=1, |
| extra_footers={ |
| ROLLED_REPO_FOOTER: 'https://foo.googlesource.com/bar-project', |
| ROLLED_COMMITS_FOOTER: '1000005..100000a', |
| }) |
| + api.gitiles.log('log source repo', 'B', n=1, add_change_id=False) |
| ) |
| |
| yield ( |
| api.status_check.test('no_footers') |
| + api.buildbucket.ci_build() |
| + api.gitiles.log('get commit message', 'A', n=1) |
| ) |
| |
| # If we get a Gerrit error commenting on the first CL, we should still try |
| # comment on the second one. |
| yield ( |
| api.status_check.test('failed_comment', status='failure') |
| + api.buildbucket.ci_build() |
| + api.gitiles.log( |
| 'get commit message', |
| 'A', |
| n=1, |
| extra_footers={ |
| ROLLED_REPO_FOOTER: 'https://foo.googlesource.com/bar-project', |
| ROLLED_COMMITS_FOOTER: '100000a..100000b', |
| }) |
| + api.step_data( |
| 'log source repo', |
| api.json.output([ |
| {'id': '100000a', 'message': 'Change-Id: Iabc'}, |
| {'id': '100000b', 'message': 'Change-Id: Ixyz'}, |
| ])) |
| + api.step_data('comment successful roll.comment on 100000a', retcode=1) |
| + api.post_process( |
| post_process.MustRun, 'comment successful roll.comment on 100000b') |
| ) |
| # yapf: enable |