blob: 4150d9eb75a6aa05ef17e08d836a8cf7921ada87 [file] [log] [blame]
# 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