blob: 7f40c92a2190ea0ec3b55af75b14a5122ea7e3d5 [file] [log] [blame]
Hirthanan Subenderanb8402a12020-02-05 14:11:00 -08001#!/usr/bin/env python3
2# -*- coding: utf-8 -*-"
3#
4# Copyright 2020 The Chromium OS Authors. All rights reserved.
5# Use of this source code is governed by a BSD-style license that can be
6# found in the LICENSE file.
7
8"""Module containing methods interfacing with gerrit.
9
10i.e Create new bugfix change tickets, and reading metadata about a specific change.
Hirthanan Subenderan40368002020-03-10 15:36:48 -070011
12Example CURL command that creates CL:
13curl -b /home/chromeos_patches/.git-credential-cache/cookie \
14 --header "Content-Type: application/json" \
15 --data \
16 '{"project":"chromiumos/third_party/kernel",\
17 "subject":"test",\
18 "branch":"chromeos-4.19",\
19 "topic":"test_topic"}' https://chromium-review.googlesource.com/a/changes/
Hirthanan Subenderanb8402a12020-02-05 14:11:00 -080020"""
21
22from __future__ import print_function
Guenter Roeck9c7f67b2020-05-12 12:48:54 -070023import logging
Hirthanan Subenderanb8402a12020-02-05 14:11:00 -080024import json
Hirthanan Subenderan40368002020-03-10 15:36:48 -070025import http
Hirthanan Subenderan3f029112020-03-11 12:33:05 -070026import os
Hirthanan Subenderandc721602020-03-13 15:48:10 -070027import re
Hirthanan Subenderanc44a0b32020-03-11 22:34:39 -070028import requests
Hirthanan Subenderanb8402a12020-02-05 14:11:00 -080029
Hirthanan Subenderanc44a0b32020-03-11 22:34:39 -070030import common
Hirthanan Subenderanb1866552020-03-20 14:01:14 -070031import git_interface
Hirthanan Subenderan40368002020-03-10 15:36:48 -070032
33
34def get_auth_cookie():
35 """Load cookies in order to authenticate requests with gerrit/googlesource."""
Hirthanan Subenderan00544542020-04-10 18:11:51 -070036 # This cookie should exist in order to perform GAIA authenticated requests
37 try:
38 gerrit_credentials_cookies = \
39 http.cookiejar.MozillaCookieJar(common.GCE_GIT_COOKIE_PATH, None, None)
40 gerrit_credentials_cookies.load()
41 return gerrit_credentials_cookies
42 except FileNotFoundError:
43 try:
44 gerrit_credentials_cookies = \
45 http.cookiejar.MozillaCookieJar(common.LOCAL_GIT_COOKIE_PATH, None, None)
46 gerrit_credentials_cookies.load()
47 return gerrit_credentials_cookies
48 except FileNotFoundError:
Guenter Roeck9c7f67b2020-05-12 12:48:54 -070049 logging.error('Could not locate gitcookies file. Generate cookie file and try again')
50 logging.error('If running locally, ensure gitcookies file is located at ~/.gitcookies')
51 logging.error('Learn more by visiting go/gob-dev#testing-user-authentication')
Hirthanan Subenderan00544542020-04-10 18:11:51 -070052 raise
Hirthanan Subenderan40368002020-03-10 15:36:48 -070053
Hirthanan Subenderana43fd4d2020-03-30 13:01:45 -070054
Hirthanan Subenderan40368002020-03-10 15:36:48 -070055def retrieve_and_parse_endpoint(endpoint_url):
56 """Retrieves Gerrit endpoint response and removes XSSI prefix )]}'"""
Hirthanan Subenderan40368002020-03-10 15:36:48 -070057 try:
Hirthanan Subenderan3f029112020-03-11 12:33:05 -070058 resp = requests.get(endpoint_url, cookies=get_auth_cookie())
59 resp.raise_for_status()
Hirthanan Subenderan40368002020-03-10 15:36:48 -070060 resp_json = json.loads(resp.text[5:])
Hirthanan Subenderan3f029112020-03-11 12:33:05 -070061 except requests.exceptions.HTTPError as e:
62 raise type(e)('Endpoint %s should have HTTP response 200' % endpoint_url) from e
Hirthanan Subenderan40368002020-03-10 15:36:48 -070063 except json.decoder.JSONDecodeError as e:
Hirthanan Subenderan3f029112020-03-11 12:33:05 -070064 raise ValueError('Response should contain json )]} prefix to prevent XSSI attacks') from e
Hirthanan Subenderan40368002020-03-10 15:36:48 -070065
66 return resp_json
Hirthanan Subenderanb8402a12020-02-05 14:11:00 -080067
Hirthanan Subenderana43fd4d2020-03-30 13:01:45 -070068
69def set_and_parse_endpoint(endpoint_url, payload=None):
Hirthanan Subenderan3f029112020-03-11 12:33:05 -070070 """POST request to gerrit endpoint with specified payload."""
71 try:
72 resp = requests.post(endpoint_url, json=payload, cookies=get_auth_cookie())
73 resp.raise_for_status()
74 resp_json = json.loads(resp.text[5:])
Hirthanan Subenderan3f029112020-03-11 12:33:05 -070075 except json.decoder.JSONDecodeError as e:
76 raise ValueError('Response should contain json )]} prefix to prevent XSSI attacks') from e
77
78 return resp_json
Hirthanan Subenderanb8402a12020-02-05 14:11:00 -080079
Hirthanan Subenderanc44a0b32020-03-11 22:34:39 -070080
Hirthanan Subenderan53bf3d52020-04-08 09:56:49 -070081def get_full_changeid(changeid, branch):
82 """Returns the changeid with url-encoding in project~branch~changeid format."""
83 project = 'chromiumos%2Fthird_party%2Fkernel'
84 chromeos_branch = common.chromeos_branch(branch)
85 return '{project}~{branch}~{changeid}'.format(project=project,
86 branch=chromeos_branch,
87 changeid=changeid)
88
89
90def get_reviewers(changeid, branch):
Hirthanan Subenderan3f029112020-03-11 12:33:05 -070091 """Retrieves list of reviewer emails from gerrit given a chromeos changeid."""
Hirthanan Subenderan53bf3d52020-04-08 09:56:49 -070092 unique_changeid = get_full_changeid(changeid, branch)
Hirthanan Subenderanc44a0b32020-03-11 22:34:39 -070093 list_reviewers_endpoint = os.path.join(common.CHROMIUM_REVIEW_BASEURL, 'changes',
Hirthanan Subenderan53bf3d52020-04-08 09:56:49 -070094 unique_changeid, 'reviewers')
Hirthanan Subenderan3f029112020-03-11 12:33:05 -070095
96 resp = retrieve_and_parse_endpoint(list_reviewers_endpoint)
97
98 try:
99 return [reviewer_resp['email'] for reviewer_resp in resp]
100 except KeyError as e:
101 raise type(e)('Gerrit API endpoint to list reviewers should contain key email') from e
102
Hirthanan Subenderana43fd4d2020-03-30 13:01:45 -0700103
Hirthanan Subenderan53bf3d52020-04-08 09:56:49 -0700104def abandon_change(changeid, branch, reason=None):
Hirthanan Subenderana43fd4d2020-03-30 13:01:45 -0700105 """Abandons a change."""
Hirthanan Subenderan53bf3d52020-04-08 09:56:49 -0700106 unique_changeid = get_full_changeid(changeid, branch)
Hirthanan Subenderana43fd4d2020-03-30 13:01:45 -0700107 abandon_change_endpoint = os.path.join(common.CHROMIUM_REVIEW_BASEURL, 'changes',
Hirthanan Subenderan53bf3d52020-04-08 09:56:49 -0700108 unique_changeid, 'abandon')
Hirthanan Subenderand9d1b842020-04-09 12:15:14 -0700109
110 abandon_payload = {'message': reason} if reason else None
111
112 try:
113 set_and_parse_endpoint(abandon_change_endpoint, abandon_payload)
Guenter Roeck9c7f67b2020-05-12 12:48:54 -0700114 logging.info('Abandoned changeid %s on Gerrit', changeid)
Hirthanan Subenderand9d1b842020-04-09 12:15:14 -0700115 except requests.exceptions.HTTPError as e:
Hirthanan Subenderanc5e6c402020-04-10 14:31:08 -0700116 if e.response.status_code == http.HTTPStatus.CONFLICT:
Guenter Roeck95f04822021-01-20 09:17:24 -0800117 logging.info('Change %s for branch %s has already been abandoned', changeid, branch)
Hirthanan Subenderand9d1b842020-04-09 12:15:14 -0700118 else:
119 raise
Hirthanan Subenderana43fd4d2020-03-30 13:01:45 -0700120
121
Hirthanan Subenderanc5e6c402020-04-10 14:31:08 -0700122def restore_change(changeid, branch, reason=None):
Hirthanan Subenderana43fd4d2020-03-30 13:01:45 -0700123 """Restores an abandoned change."""
Hirthanan Subenderan53bf3d52020-04-08 09:56:49 -0700124 unique_changeid = get_full_changeid(changeid, branch)
Hirthanan Subenderana43fd4d2020-03-30 13:01:45 -0700125 restore_change_endpoint = os.path.join(common.CHROMIUM_REVIEW_BASEURL, 'changes',
Hirthanan Subenderan53bf3d52020-04-08 09:56:49 -0700126 unique_changeid, 'restore')
Hirthanan Subenderanc5e6c402020-04-10 14:31:08 -0700127
128 restore_payload = {'message': reason} if reason else None
129
130 try:
131 set_and_parse_endpoint(restore_change_endpoint, restore_payload)
Guenter Roeck9c7f67b2020-05-12 12:48:54 -0700132 logging.info('Restored changeid %s on Gerrit', changeid)
Hirthanan Subenderanc5e6c402020-04-10 14:31:08 -0700133 except requests.exceptions.HTTPError as e:
134 if e.response.status_code == http.HTTPStatus.CONFLICT:
Guenter Roeck95f04822021-01-20 09:17:24 -0800135 logging.info('Change %s for branch %s has already been restored', changeid, branch)
Hirthanan Subenderanc5e6c402020-04-10 14:31:08 -0700136 else:
137 raise
Hirthanan Subenderana43fd4d2020-03-30 13:01:45 -0700138
139
Hirthanan Subenderan53bf3d52020-04-08 09:56:49 -0700140def get_change(changeid, branch):
Hirthanan Subenderanb8402a12020-02-05 14:11:00 -0800141 """Retrieves ChangeInfo from gerrit using its changeid"""
Hirthanan Subenderan53bf3d52020-04-08 09:56:49 -0700142 unique_changeid = get_full_changeid(changeid, branch)
Hirthanan Subenderanc44a0b32020-03-11 22:34:39 -0700143 get_change_endpoint = os.path.join(common.CHROMIUM_REVIEW_BASEURL, 'changes',
Hirthanan Subenderan53bf3d52020-04-08 09:56:49 -0700144 unique_changeid)
Hirthanan Subenderan40368002020-03-10 15:36:48 -0700145 return retrieve_and_parse_endpoint(get_change_endpoint)
Hirthanan Subenderanb8402a12020-02-05 14:11:00 -0800146
Hirthanan Subenderana43fd4d2020-03-30 13:01:45 -0700147
Hirthanan Subenderan53bf3d52020-04-08 09:56:49 -0700148def set_hashtag(changeid, branch):
Hirthanan Subenderanc44a0b32020-03-11 22:34:39 -0700149 """Set hashtag to be autogenerated indicating a robot generated CL."""
Hirthanan Subenderan53bf3d52020-04-08 09:56:49 -0700150 unique_changeid = get_full_changeid(changeid, branch)
Hirthanan Subenderanc44a0b32020-03-11 22:34:39 -0700151 set_hashtag_endpoint = os.path.join(common.CHROMIUM_REVIEW_BASEURL, 'changes',
Hirthanan Subenderan53bf3d52020-04-08 09:56:49 -0700152 unique_changeid, 'hashtags')
Hirthanan Subenderanc44a0b32020-03-11 22:34:39 -0700153 hashtag_input_payload = {'add' : ['autogenerated']}
154 set_and_parse_endpoint(set_hashtag_endpoint, hashtag_input_payload)
Hirthanan Subenderanb8402a12020-02-05 14:11:00 -0800155
Hirthanan Subenderan53bf3d52020-04-08 09:56:49 -0700156def get_status(changeid, branch):
Hirthanan Subenderan30be90b2020-04-02 10:03:25 -0700157 """Retrieves the latest status of a changeid by checking gerrit."""
Hirthanan Subenderan53bf3d52020-04-08 09:56:49 -0700158 change_info = get_change(changeid, branch)
Hirthanan Subenderan30be90b2020-04-02 10:03:25 -0700159 return change_info['status']
Hirthanan Subenderanc44a0b32020-03-11 22:34:39 -0700160
Hirthanan Subenderand6922c32020-03-23 14:17:40 -0700161def get_bug_test_line(chrome_sha):
162 """Retrieve BUG and TEST lines from the chrome sha."""
Hirthanan Subenderandc721602020-03-13 15:48:10 -0700163 # stable fixes don't have a fixee changeid
164 bug_test_line = 'BUG=%s\nTEST=%s'
165 bug = test = None
Hirthanan Subenderand6922c32020-03-23 14:17:40 -0700166 if not chrome_sha:
Hirthanan Subenderandc721602020-03-13 15:48:10 -0700167 return bug_test_line % (bug, test)
168
Hirthanan Subenderand6922c32020-03-23 14:17:40 -0700169 chrome_commit_msg = git_interface.get_chrome_commit_message(chrome_sha)
Hirthanan Subenderandc721602020-03-13 15:48:10 -0700170
171 bug_matches = re.findall('^BUG=(.*)$', chrome_commit_msg, re.M)
172 test_matches = re.findall('^TEST=(.*)$', chrome_commit_msg, re.M)
173
Guenter Roeck7f094f02020-05-05 10:55:13 -0700174 if bug_matches:
175 bug = bug_matches[-1]
176 if bug is None or bug == 'None':
177 bug = 'None (see commit %s)' % chrome_sha
178 if test_matches:
179 test = test_matches[-1]
Hirthanan Subenderandc721602020-03-13 15:48:10 -0700180
181 return bug_test_line % (bug, test)
182
Hirthanan Subenderana43fd4d2020-03-30 13:01:45 -0700183
Hirthanan Subenderandc721602020-03-13 15:48:10 -0700184def generate_fix_message(fixer_upstream_sha, bug_test_line):
Hirthanan Subenderanb8402a12020-02-05 14:11:00 -0800185 """Generates new commit message for a fix change.
186
187 Use script ./contrib/from_upstream.py to generate new commit msg
188 Commit message should include essential information:
189 i.e:
190 FROMGIT, FROMLIST, ANDROID, CHROMIUM, etc.
191 commit message indiciating what is happening
192 BUG=...
193 TEST=...
194 tag for Fixes: <upstream-sha>
195 """
Hirthanan Subenderanb1866552020-03-20 14:01:14 -0700196 fix_upstream_commit_msg = git_interface.get_upstream_commit_message(fixer_upstream_sha)
Hirthanan Subenderandc721602020-03-13 15:48:10 -0700197
Hirthanan Subenderanb1866552020-03-20 14:01:14 -0700198 upstream_full_sha = git_interface.get_upstream_fullsha(fixer_upstream_sha)
Hirthanan Subenderandc721602020-03-13 15:48:10 -0700199 cherry_picked = '(cherry picked from commit %s)\n\n'% upstream_full_sha
200
201
202 commit_message = ('UPSTREAM: {fix_commit_msg}'
203 '{cherry_picked}'
204 '{bug_test_line}').format(fix_commit_msg=fix_upstream_commit_msg,
205 cherry_picked=cherry_picked, bug_test_line=bug_test_line)
206
Hirthanan Subenderanc44a0b32020-03-11 22:34:39 -0700207 return commit_message
208
209
Guenter Roeckbdf658c2020-12-29 09:56:04 -0800210def create_change(fixee_kernel_sha, fixer_upstream_sha, branch, is_chromeos, fixer_changeid=None):
Hirthanan Subenderandc721602020-03-13 15:48:10 -0700211 """Creates a Patch in gerrit given a ChangeInput object.
Hirthanan Subenderanc44a0b32020-03-11 22:34:39 -0700212
Hirthanan Subenderandc721602020-03-13 15:48:10 -0700213 Determines whether a change for a fix has already been created,
214 and avoids duplicate creations.
215 """
Hirthanan Subenderan5f94c602020-03-18 16:03:05 -0700216 cwd = os.getcwd()
Hirthanan Subenderandc721602020-03-13 15:48:10 -0700217 chromeos_branch = common.chromeos_branch(branch)
Hirthanan Subenderanc44a0b32020-03-11 22:34:39 -0700218
Guenter Roeckbfda1242020-12-29 15:24:18 -0800219 if is_chromeos:
220 fixee_changeid = git_interface.get_commit_changeid_linux_chrome(fixee_kernel_sha)
221 chrome_kernel_sha = fixee_kernel_sha
Hirthanan Subenderanb2106a92020-03-19 19:12:55 -0700222
Guenter Roeckbfda1242020-12-29 15:24:18 -0800223 if not fixee_changeid:
224 # This may be a merge. Try to find its merge commit.
225 merge_sha = git_interface.get_merge_sha(chromeos_branch, fixee_kernel_sha)
226 if merge_sha:
227 chrome_kernel_sha = merge_sha
228 fixee_changeid = git_interface.get_commit_changeid_linux_chrome(merge_sha)
229 else:
230 fixee_changeid = None
231 chrome_kernel_sha = None
Hirthanan Subenderand6922c32020-03-23 14:17:40 -0700232
233 bug_test_line = get_bug_test_line(chrome_kernel_sha)
Hirthanan Subenderandc721602020-03-13 15:48:10 -0700234 fix_commit_message = generate_fix_message(fixer_upstream_sha, bug_test_line)
Hirthanan Subenderanc44a0b32020-03-11 22:34:39 -0700235
Hirthanan Subenderand6922c32020-03-23 14:17:40 -0700236 # TODO(hirthanan): find relevant mailing list/reviewers
237 # For now we will assign it to a default user like Guenter?
238 # This is for stable bug fix patches that don't have a direct fixee changeid
239 # since groups of stable commits get merged as one changeid
240 reviewers = ['groeck@chromium.org']
241 try:
242 if fixee_changeid:
Hirthanan Subenderan53bf3d52020-04-08 09:56:49 -0700243 cl_reviewers = get_reviewers(fixee_changeid, branch)
Guenter Roeck81ed0572020-05-13 11:33:24 -0700244 if cl_reviewers:
245 reviewers = cl_reviewers
Hirthanan Subenderand6922c32020-03-23 14:17:40 -0700246 except requests.exceptions.HTTPError:
Guenter Roeck0632c112020-05-12 12:41:41 -0700247 # There is a Change-Id in the commit log, but Gerrit does not have a
248 # matching entry. Fall back to list of e-mails found in tags after
249 # the last "cherry picked" message.
Guenter Roeck9c7f67b2020-05-12 12:48:54 -0700250 logging.warning('Failed to get reviewer(s) from gerrit for Change-Id %s', fixee_changeid)
Guenter Roeck0632c112020-05-12 12:41:41 -0700251 emails = git_interface.get_tag_emails_linux_chrome(fixee_kernel_sha)
252 if emails:
253 reviewers = emails
Hirthanan Subenderanc44a0b32020-03-11 22:34:39 -0700254
Hirthanan Subenderan13e19352020-04-10 16:21:08 -0700255 try:
256 # Cherry pick changes and generate commit message indicating fix from upstream
Guenter Roeck32b23dc2021-01-17 13:29:06 -0800257 handler = git_interface.commitHandler(common.Kernel.linux_chrome, branch)
258 fixer_changeid = handler.cherry_pick_and_push(fixer_upstream_sha, fixer_changeid,
259 fix_commit_message, reviewers)
Hirthanan Subenderan13e19352020-04-10 16:21:08 -0700260 except ValueError:
261 # Error cherry-picking and pushing fix patch
262 return None
Hirthanan Subenderanb8402a12020-02-05 14:11:00 -0800263
Hirthanan Subenderan5f94c602020-03-18 16:03:05 -0700264 os.chdir(cwd)
Hirthanan Subenderandc721602020-03-13 15:48:10 -0700265 return fixer_changeid