| #!/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 |