blob: 5fceffd1eeefdcf7a10add7c8548787a572151cd [file] [log] [blame]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-"
#
# Copyright 2020 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Module containing methods interfacing with gerrit.
i.e Create new bugfix change tickets, and reading metadata about a specific change.
Example CURL command that creates CL:
curl -b /home/chromeos_patches/.git-credential-cache/cookie \
--header "Content-Type: application/json" \
--data \
'{"project":"chromiumos/third_party/kernel",\
"subject":"test",\
"branch":"chromeos-4.19",\
"topic":"test_topic"}' https://chromium-review.googlesource.com/a/changes/
"""
from __future__ import print_function
import json
import http
import os
import re
import requests
import common
import git_interface
def get_auth_cookie():
"""Load cookies in order to authenticate requests with gerrit/googlesource."""
# This cookie should exist on GCE in order to perform GAIA authenticated requests
gerrit_credentials_cookies = http.cookiejar.MozillaCookieJar(common.GIT_COOKIE_PATH, None, None)
gerrit_credentials_cookies.load()
return gerrit_credentials_cookies
def retrieve_and_parse_endpoint(endpoint_url):
"""Retrieves Gerrit endpoint response and removes XSSI prefix )]}'"""
try:
resp = requests.get(endpoint_url, cookies=get_auth_cookie())
resp.raise_for_status()
resp_json = json.loads(resp.text[5:])
except requests.exceptions.HTTPError as e:
raise type(e)('Endpoint %s should have HTTP response 200' % endpoint_url) from e
except json.decoder.JSONDecodeError as e:
raise ValueError('Response should contain json )]} prefix to prevent XSSI attacks') from e
return resp_json
def set_and_parse_endpoint(endpoint_url, payload):
"""POST request to gerrit endpoint with specified payload."""
try:
resp = requests.post(endpoint_url, json=payload, cookies=get_auth_cookie())
resp.raise_for_status()
resp_json = json.loads(resp.text[5:])
except requests.exceptions.HTTPError as e:
raise type(e)('Endpoint %s should have HTTP response 200' % endpoint_url) from e
except json.decoder.JSONDecodeError as e:
raise ValueError('Response should contain json )]} prefix to prevent XSSI attacks') from e
return resp_json
def get_reviewers(changeid):
"""Retrieves list of reviewer emails from gerrit given a chromeos changeid."""
list_reviewers_endpoint = os.path.join(common.CHROMIUM_REVIEW_BASEURL, 'changes',
changeid, 'reviewers')
resp = retrieve_and_parse_endpoint(list_reviewers_endpoint)
try:
return [reviewer_resp['email'] for reviewer_resp in resp]
except KeyError as e:
raise type(e)('Gerrit API endpoint to list reviewers should contain key email') from e
def set_reviewers(changeid, reviewer_emails):
"""Adds reviewers to a Gerrit CL."""
add_reviewer_endpoint = os.path.join(common.CHROMIUM_REVIEW_BASEURL, 'changes',
changeid, 'reviewers')
for email in reviewer_emails:
payload = {'reviewer': email}
set_and_parse_endpoint(add_reviewer_endpoint, payload)
def get_change(changeid):
"""Retrieves ChangeInfo from gerrit using its changeid"""
get_change_endpoint = os.path.join(common.CHROMIUM_REVIEW_BASEURL, 'changes',
changeid)
return retrieve_and_parse_endpoint(get_change_endpoint)
def set_hashtag(changeid):
"""Set hashtag to be autogenerated indicating a robot generated CL."""
set_hashtag_endpoint = os.path.join(common.CHROMIUM_REVIEW_BASEURL, 'changes',
changeid, 'hashtags')
hashtag_input_payload = {'add' : ['autogenerated']}
set_and_parse_endpoint(set_hashtag_endpoint, hashtag_input_payload)
def get_bug_test_line(chrome_sha):
"""Retrieve BUG and TEST lines from the chrome sha."""
# stable fixes don't have a fixee changeid
bug_test_line = 'BUG=%s\nTEST=%s'
bug = test = None
if not chrome_sha:
return bug_test_line % (bug, test)
chrome_commit_msg = git_interface.get_chrome_commit_message(chrome_sha)
bug_matches = re.findall('^BUG=(.*)$', chrome_commit_msg, re.M)
test_matches = re.findall('^TEST=(.*)$', chrome_commit_msg, re.M)
bug = bug_matches[0] if bug_matches else None
test = test_matches[0] if test_matches else None
return bug_test_line % (bug, test)
def generate_fix_message(fixer_upstream_sha, bug_test_line):
"""Generates new commit message for a fix change.
Use script ./contrib/from_upstream.py to generate new commit msg
Commit message should include essential information:
i.e:
FROMGIT, FROMLIST, ANDROID, CHROMIUM, etc.
commit message indiciating what is happening
BUG=...
TEST=...
tag for Fixes: <upstream-sha>
"""
fix_upstream_commit_msg = git_interface.get_upstream_commit_message(fixer_upstream_sha)
upstream_full_sha = git_interface.get_upstream_fullsha(fixer_upstream_sha)
cherry_picked = '(cherry picked from commit %s)\n\n'% upstream_full_sha
commit_message = ('UPSTREAM: {fix_commit_msg}'
'{cherry_picked}'
'{bug_test_line}').format(fix_commit_msg=fix_upstream_commit_msg,
cherry_picked=cherry_picked, bug_test_line=bug_test_line)
return commit_message
# Note: Stable patches won't have a fixee_change_id since they come into chromeos as merges
def create_change(fixee_kernel_sha, fixer_upstream_sha, branch):
"""Creates a Patch in gerrit given a ChangeInput object.
Determines whether a change for a fix has already been created,
and avoids duplicate creations.
"""
cwd = os.getcwd()
chromeos_branch = common.chromeos_branch(branch)
# fixee_changeid will be None for stable fixee_kernel_sha's
fixee_changeid = git_interface.get_commit_changeid_linux_chrome(fixee_kernel_sha)
# if fixee_changeid is set, the fixee_kernel_sha represents a chrome sha
chrome_kernel_sha = fixee_kernel_sha if fixee_changeid else None
bug_test_line = get_bug_test_line(chrome_kernel_sha)
fix_commit_message = generate_fix_message(fixer_upstream_sha, bug_test_line)
try:
# Cherry pick changes and generate commit message indicating fix from upstream
fixer_changeid = git_interface.cherry_pick_and_push_fix(fixer_upstream_sha,
chromeos_branch, fix_commit_message)
except ValueError:
print('Failed to create gerrit ticket for [fixee_kernel_sha, fixer_upstream_sha]',
(fixee_kernel_sha, fixer_upstream_sha))
raise
# TODO(hirthanan): find relevant mailing list/reviewers
# For now we will assign it to a default user like Guenter?
# This is for stable bug fix patches that don't have a direct fixee changeid
# since groups of stable commits get merged as one changeid
reviewers = ['groeck@chromium.org']
try:
if fixee_changeid:
cl_reviewers = get_reviewers(fixee_changeid)
reviewers = cl_reviewers if cl_reviewers else reviewers
except requests.exceptions.HTTPError:
print('Error getting reviewers from gerrit for fixee_changeid', fixee_changeid)
set_reviewers(fixer_changeid, reviewers)
os.chdir(cwd)
return fixer_changeid