1*90c8c64dSAndroid Build Coastguard Worker#!/usr/bin/env python3 2*90c8c64dSAndroid Build Coastguard Worker 3*90c8c64dSAndroid Build Coastguard Worker# 4*90c8c64dSAndroid Build Coastguard Worker# Copyright (C) 2018 The Android Open Source Project 5*90c8c64dSAndroid Build Coastguard Worker# 6*90c8c64dSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 7*90c8c64dSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 8*90c8c64dSAndroid Build Coastguard Worker# You may obtain a copy of the License at 9*90c8c64dSAndroid Build Coastguard Worker# 10*90c8c64dSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 11*90c8c64dSAndroid Build Coastguard Worker# 12*90c8c64dSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 13*90c8c64dSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 14*90c8c64dSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15*90c8c64dSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 16*90c8c64dSAndroid Build Coastguard Worker# limitations under the License. 17*90c8c64dSAndroid Build Coastguard Worker# 18*90c8c64dSAndroid Build Coastguard Worker 19*90c8c64dSAndroid Build Coastguard Worker"""Gerrit Restful API client library.""" 20*90c8c64dSAndroid Build Coastguard Worker 21*90c8c64dSAndroid Build Coastguard Workerfrom __future__ import print_function 22*90c8c64dSAndroid Build Coastguard Worker 23*90c8c64dSAndroid Build Coastguard Workerimport argparse 24*90c8c64dSAndroid Build Coastguard Workerimport base64 25*90c8c64dSAndroid Build Coastguard Workerimport json 26*90c8c64dSAndroid Build Coastguard Workerimport os 27*90c8c64dSAndroid Build Coastguard Workerimport sys 28*90c8c64dSAndroid Build Coastguard Workerimport xml.dom.minidom 29*90c8c64dSAndroid Build Coastguard Worker 30*90c8c64dSAndroid Build Coastguard Workertry: 31*90c8c64dSAndroid Build Coastguard Worker import ssl 32*90c8c64dSAndroid Build Coastguard Worker _HAS_SSL = True 33*90c8c64dSAndroid Build Coastguard Workerexcept ImportError: 34*90c8c64dSAndroid Build Coastguard Worker _HAS_SSL = False 35*90c8c64dSAndroid Build Coastguard Worker 36*90c8c64dSAndroid Build Coastguard Workertry: 37*90c8c64dSAndroid Build Coastguard Worker # PY3 38*90c8c64dSAndroid Build Coastguard Worker from urllib.error import HTTPError 39*90c8c64dSAndroid Build Coastguard Worker from urllib.parse import urlencode, urlparse 40*90c8c64dSAndroid Build Coastguard Worker from urllib.request import ( 41*90c8c64dSAndroid Build Coastguard Worker HTTPBasicAuthHandler, HTTPHandler, OpenerDirector, Request, 42*90c8c64dSAndroid Build Coastguard Worker build_opener 43*90c8c64dSAndroid Build Coastguard Worker ) 44*90c8c64dSAndroid Build Coastguard Worker if _HAS_SSL: 45*90c8c64dSAndroid Build Coastguard Worker from urllib.request import HTTPSHandler 46*90c8c64dSAndroid Build Coastguard Workerexcept ImportError: 47*90c8c64dSAndroid Build Coastguard Worker # PY2 48*90c8c64dSAndroid Build Coastguard Worker from urllib import urlencode 49*90c8c64dSAndroid Build Coastguard Worker from urllib2 import ( 50*90c8c64dSAndroid Build Coastguard Worker HTTPBasicAuthHandler, HTTPError, HTTPHandler, OpenerDirector, Request, 51*90c8c64dSAndroid Build Coastguard Worker build_opener 52*90c8c64dSAndroid Build Coastguard Worker ) 53*90c8c64dSAndroid Build Coastguard Worker if _HAS_SSL: 54*90c8c64dSAndroid Build Coastguard Worker from urllib2 import HTTPSHandler 55*90c8c64dSAndroid Build Coastguard Worker from urlparse import urlparse 56*90c8c64dSAndroid Build Coastguard Worker 57*90c8c64dSAndroid Build Coastguard Workertry: 58*90c8c64dSAndroid Build Coastguard Worker from http.client import HTTPResponse 59*90c8c64dSAndroid Build Coastguard Workerexcept ImportError: 60*90c8c64dSAndroid Build Coastguard Worker from httplib import HTTPResponse 61*90c8c64dSAndroid Build Coastguard Worker 62*90c8c64dSAndroid Build Coastguard Workertry: 63*90c8c64dSAndroid Build Coastguard Worker from urllib import addinfourl 64*90c8c64dSAndroid Build Coastguard Worker _HAS_ADD_INFO_URL = True 65*90c8c64dSAndroid Build Coastguard Workerexcept ImportError: 66*90c8c64dSAndroid Build Coastguard Worker _HAS_ADD_INFO_URL = False 67*90c8c64dSAndroid Build Coastguard Worker 68*90c8c64dSAndroid Build Coastguard Workertry: 69*90c8c64dSAndroid Build Coastguard Worker from io import BytesIO 70*90c8c64dSAndroid Build Coastguard Workerexcept ImportError: 71*90c8c64dSAndroid Build Coastguard Worker from StringIO import StringIO as BytesIO 72*90c8c64dSAndroid Build Coastguard Worker 73*90c8c64dSAndroid Build Coastguard Workertry: 74*90c8c64dSAndroid Build Coastguard Worker # PY3.5 75*90c8c64dSAndroid Build Coastguard Worker from subprocess import PIPE, run 76*90c8c64dSAndroid Build Coastguard Workerexcept ImportError: 77*90c8c64dSAndroid Build Coastguard Worker from subprocess import CalledProcessError, PIPE, Popen 78*90c8c64dSAndroid Build Coastguard Worker 79*90c8c64dSAndroid Build Coastguard Worker class CompletedProcess(object): 80*90c8c64dSAndroid Build Coastguard Worker """Process execution result returned by subprocess.run().""" 81*90c8c64dSAndroid Build Coastguard Worker # pylint: disable=too-few-public-methods 82*90c8c64dSAndroid Build Coastguard Worker 83*90c8c64dSAndroid Build Coastguard Worker def __init__(self, args, returncode, stdout, stderr): 84*90c8c64dSAndroid Build Coastguard Worker self.args = args 85*90c8c64dSAndroid Build Coastguard Worker self.returncode = returncode 86*90c8c64dSAndroid Build Coastguard Worker self.stdout = stdout 87*90c8c64dSAndroid Build Coastguard Worker self.stderr = stderr 88*90c8c64dSAndroid Build Coastguard Worker 89*90c8c64dSAndroid Build Coastguard Worker def run(*args, **kwargs): 90*90c8c64dSAndroid Build Coastguard Worker """Run a command with subprocess.Popen() and redirect input/output.""" 91*90c8c64dSAndroid Build Coastguard Worker 92*90c8c64dSAndroid Build Coastguard Worker check = kwargs.pop('check', False) 93*90c8c64dSAndroid Build Coastguard Worker 94*90c8c64dSAndroid Build Coastguard Worker try: 95*90c8c64dSAndroid Build Coastguard Worker stdin = kwargs.pop('input') 96*90c8c64dSAndroid Build Coastguard Worker assert 'stdin' not in kwargs 97*90c8c64dSAndroid Build Coastguard Worker kwargs['stdin'] = PIPE 98*90c8c64dSAndroid Build Coastguard Worker except KeyError: 99*90c8c64dSAndroid Build Coastguard Worker stdin = None 100*90c8c64dSAndroid Build Coastguard Worker 101*90c8c64dSAndroid Build Coastguard Worker proc = Popen(*args, **kwargs) 102*90c8c64dSAndroid Build Coastguard Worker try: 103*90c8c64dSAndroid Build Coastguard Worker stdout, stderr = proc.communicate(stdin) 104*90c8c64dSAndroid Build Coastguard Worker except: 105*90c8c64dSAndroid Build Coastguard Worker proc.kill() 106*90c8c64dSAndroid Build Coastguard Worker proc.wait() 107*90c8c64dSAndroid Build Coastguard Worker raise 108*90c8c64dSAndroid Build Coastguard Worker returncode = proc.wait() 109*90c8c64dSAndroid Build Coastguard Worker 110*90c8c64dSAndroid Build Coastguard Worker if check and returncode: 111*90c8c64dSAndroid Build Coastguard Worker raise CalledProcessError(returncode, args, stdout) 112*90c8c64dSAndroid Build Coastguard Worker return CompletedProcess(args, returncode, stdout, stderr) 113*90c8c64dSAndroid Build Coastguard Worker 114*90c8c64dSAndroid Build Coastguard Worker 115*90c8c64dSAndroid Build Coastguard Workerclass CurlSocket(object): 116*90c8c64dSAndroid Build Coastguard Worker """A mock socket object that loads the response from a curl output file.""" 117*90c8c64dSAndroid Build Coastguard Worker 118*90c8c64dSAndroid Build Coastguard Worker def __init__(self, file_obj): 119*90c8c64dSAndroid Build Coastguard Worker self._file_obj = file_obj 120*90c8c64dSAndroid Build Coastguard Worker 121*90c8c64dSAndroid Build Coastguard Worker def makefile(self, *args): 122*90c8c64dSAndroid Build Coastguard Worker return self._file_obj 123*90c8c64dSAndroid Build Coastguard Worker 124*90c8c64dSAndroid Build Coastguard Worker def close(self): 125*90c8c64dSAndroid Build Coastguard Worker self._file_obj = None 126*90c8c64dSAndroid Build Coastguard Worker 127*90c8c64dSAndroid Build Coastguard Worker 128*90c8c64dSAndroid Build Coastguard Workerdef _build_curl_command_for_request(curl_command_name, req): 129*90c8c64dSAndroid Build Coastguard Worker """Build the curl command line for an HTTP/HTTPS request.""" 130*90c8c64dSAndroid Build Coastguard Worker 131*90c8c64dSAndroid Build Coastguard Worker cmd = [curl_command_name] 132*90c8c64dSAndroid Build Coastguard Worker 133*90c8c64dSAndroid Build Coastguard Worker # Adds `--no-progress-meter` to hide the progress bar. 134*90c8c64dSAndroid Build Coastguard Worker cmd.append('--no-progress-meter') 135*90c8c64dSAndroid Build Coastguard Worker 136*90c8c64dSAndroid Build Coastguard Worker # Adds `-i` to print the HTTP response headers to stdout. 137*90c8c64dSAndroid Build Coastguard Worker cmd.append('-i') 138*90c8c64dSAndroid Build Coastguard Worker 139*90c8c64dSAndroid Build Coastguard Worker # Uses HTTP 1.1. The `http.client` module can only parse HTTP 1.1 headers. 140*90c8c64dSAndroid Build Coastguard Worker cmd.append('--http1.1') 141*90c8c64dSAndroid Build Coastguard Worker 142*90c8c64dSAndroid Build Coastguard Worker # Specifies the request method. 143*90c8c64dSAndroid Build Coastguard Worker cmd.append('-X') 144*90c8c64dSAndroid Build Coastguard Worker cmd.append(req.get_method()) 145*90c8c64dSAndroid Build Coastguard Worker 146*90c8c64dSAndroid Build Coastguard Worker # Adds the request headers. 147*90c8c64dSAndroid Build Coastguard Worker for name, value in req.headers.items(): 148*90c8c64dSAndroid Build Coastguard Worker cmd.append('-H') 149*90c8c64dSAndroid Build Coastguard Worker cmd.append(name + ': ' + value) 150*90c8c64dSAndroid Build Coastguard Worker 151*90c8c64dSAndroid Build Coastguard Worker # Adds the request data. 152*90c8c64dSAndroid Build Coastguard Worker if req.data: 153*90c8c64dSAndroid Build Coastguard Worker cmd.append('-d') 154*90c8c64dSAndroid Build Coastguard Worker cmd.append('@-') 155*90c8c64dSAndroid Build Coastguard Worker 156*90c8c64dSAndroid Build Coastguard Worker # Adds the request full URL. 157*90c8c64dSAndroid Build Coastguard Worker cmd.append(req.get_full_url()) 158*90c8c64dSAndroid Build Coastguard Worker return cmd 159*90c8c64dSAndroid Build Coastguard Worker 160*90c8c64dSAndroid Build Coastguard Worker 161*90c8c64dSAndroid Build Coastguard Workerdef _handle_open_with_curl(curl_command_name, req): 162*90c8c64dSAndroid Build Coastguard Worker """Send the HTTP request with CURL and return a response object that can be 163*90c8c64dSAndroid Build Coastguard Worker handled by urllib.""" 164*90c8c64dSAndroid Build Coastguard Worker 165*90c8c64dSAndroid Build Coastguard Worker # Runs the curl command. 166*90c8c64dSAndroid Build Coastguard Worker cmd = _build_curl_command_for_request(curl_command_name, req) 167*90c8c64dSAndroid Build Coastguard Worker proc = run(cmd, stdout=PIPE, input=req.data, check=True) 168*90c8c64dSAndroid Build Coastguard Worker 169*90c8c64dSAndroid Build Coastguard Worker # Wraps the curl output with a socket-like object. 170*90c8c64dSAndroid Build Coastguard Worker outfile = BytesIO(proc.stdout) 171*90c8c64dSAndroid Build Coastguard Worker socket = CurlSocket(outfile) 172*90c8c64dSAndroid Build Coastguard Worker 173*90c8c64dSAndroid Build Coastguard Worker response = HTTPResponse(socket) 174*90c8c64dSAndroid Build Coastguard Worker try: 175*90c8c64dSAndroid Build Coastguard Worker # Parses the response header. 176*90c8c64dSAndroid Build Coastguard Worker response.begin() 177*90c8c64dSAndroid Build Coastguard Worker except: 178*90c8c64dSAndroid Build Coastguard Worker response.close() 179*90c8c64dSAndroid Build Coastguard Worker raise 180*90c8c64dSAndroid Build Coastguard Worker 181*90c8c64dSAndroid Build Coastguard Worker # Overrides `Transfer-Encoding: chunked` because curl combines chunks. 182*90c8c64dSAndroid Build Coastguard Worker response.chunked = False 183*90c8c64dSAndroid Build Coastguard Worker response.chunk_left = None 184*90c8c64dSAndroid Build Coastguard Worker 185*90c8c64dSAndroid Build Coastguard Worker if _HAS_ADD_INFO_URL: 186*90c8c64dSAndroid Build Coastguard Worker # PY2 urllib2 expects a different return object. 187*90c8c64dSAndroid Build Coastguard Worker result = addinfourl(outfile, response.msg, req.get_full_url()) 188*90c8c64dSAndroid Build Coastguard Worker result.code = response.status 189*90c8c64dSAndroid Build Coastguard Worker result.msg = response.reason 190*90c8c64dSAndroid Build Coastguard Worker return result 191*90c8c64dSAndroid Build Coastguard Worker 192*90c8c64dSAndroid Build Coastguard Worker return response # PY3 193*90c8c64dSAndroid Build Coastguard Worker 194*90c8c64dSAndroid Build Coastguard Worker 195*90c8c64dSAndroid Build Coastguard Workerclass CurlHTTPHandler(HTTPHandler): 196*90c8c64dSAndroid Build Coastguard Worker """CURL HTTP handler.""" 197*90c8c64dSAndroid Build Coastguard Worker 198*90c8c64dSAndroid Build Coastguard Worker def __init__(self, curl_command_name): 199*90c8c64dSAndroid Build Coastguard Worker self._curl_command_name = curl_command_name 200*90c8c64dSAndroid Build Coastguard Worker 201*90c8c64dSAndroid Build Coastguard Worker def http_open(self, req): 202*90c8c64dSAndroid Build Coastguard Worker return _handle_open_with_curl(self._curl_command_name, req) 203*90c8c64dSAndroid Build Coastguard Worker 204*90c8c64dSAndroid Build Coastguard Worker 205*90c8c64dSAndroid Build Coastguard Workerif _HAS_SSL: 206*90c8c64dSAndroid Build Coastguard Worker class CurlHTTPSHandler(HTTPSHandler): 207*90c8c64dSAndroid Build Coastguard Worker """CURL HTTPS handler.""" 208*90c8c64dSAndroid Build Coastguard Worker 209*90c8c64dSAndroid Build Coastguard Worker def __init__(self, curl_command_name): 210*90c8c64dSAndroid Build Coastguard Worker self._curl_command_name = curl_command_name 211*90c8c64dSAndroid Build Coastguard Worker 212*90c8c64dSAndroid Build Coastguard Worker def https_open(self, req): 213*90c8c64dSAndroid Build Coastguard Worker return _handle_open_with_curl(self._curl_command_name, req) 214*90c8c64dSAndroid Build Coastguard Worker 215*90c8c64dSAndroid Build Coastguard Worker 216*90c8c64dSAndroid Build Coastguard Workerdef load_auth_credentials_from_file(cookie_file): 217*90c8c64dSAndroid Build Coastguard Worker """Load credentials from an opened .gitcookies file.""" 218*90c8c64dSAndroid Build Coastguard Worker credentials = {} 219*90c8c64dSAndroid Build Coastguard Worker for line in cookie_file: 220*90c8c64dSAndroid Build Coastguard Worker if line.startswith('#HttpOnly_'): 221*90c8c64dSAndroid Build Coastguard Worker line = line[len('#HttpOnly_'):] 222*90c8c64dSAndroid Build Coastguard Worker 223*90c8c64dSAndroid Build Coastguard Worker if not line or line[0] == '#': 224*90c8c64dSAndroid Build Coastguard Worker continue 225*90c8c64dSAndroid Build Coastguard Worker 226*90c8c64dSAndroid Build Coastguard Worker row = line.split('\t') 227*90c8c64dSAndroid Build Coastguard Worker if len(row) != 7: 228*90c8c64dSAndroid Build Coastguard Worker continue 229*90c8c64dSAndroid Build Coastguard Worker 230*90c8c64dSAndroid Build Coastguard Worker domain = row[0] 231*90c8c64dSAndroid Build Coastguard Worker cookie = row[6] 232*90c8c64dSAndroid Build Coastguard Worker 233*90c8c64dSAndroid Build Coastguard Worker sep = cookie.find('=') 234*90c8c64dSAndroid Build Coastguard Worker if sep == -1: 235*90c8c64dSAndroid Build Coastguard Worker continue 236*90c8c64dSAndroid Build Coastguard Worker username = cookie[0:sep] 237*90c8c64dSAndroid Build Coastguard Worker password = cookie[sep + 1:] 238*90c8c64dSAndroid Build Coastguard Worker 239*90c8c64dSAndroid Build Coastguard Worker credentials[domain] = (username, password) 240*90c8c64dSAndroid Build Coastguard Worker return credentials 241*90c8c64dSAndroid Build Coastguard Worker 242*90c8c64dSAndroid Build Coastguard Worker 243*90c8c64dSAndroid Build Coastguard Workerdef load_auth_credentials(cookie_file_path): 244*90c8c64dSAndroid Build Coastguard Worker """Load credentials from a .gitcookies file path.""" 245*90c8c64dSAndroid Build Coastguard Worker with open(cookie_file_path, 'r') as cookie_file: 246*90c8c64dSAndroid Build Coastguard Worker return load_auth_credentials_from_file(cookie_file) 247*90c8c64dSAndroid Build Coastguard Worker 248*90c8c64dSAndroid Build Coastguard Worker 249*90c8c64dSAndroid Build Coastguard Workerdef _domain_matches(domain_name, domain_pattern): 250*90c8c64dSAndroid Build Coastguard Worker """Returns whether `domain_name` matches `domain_pattern` under the 251*90c8c64dSAndroid Build Coastguard Worker definition of RFC 6265 (Section 4.1.2.3 and 5.1.3). 252*90c8c64dSAndroid Build Coastguard Worker 253*90c8c64dSAndroid Build Coastguard Worker Pattern matching rule defined by Section 5.1.3: 254*90c8c64dSAndroid Build Coastguard Worker 255*90c8c64dSAndroid Build Coastguard Worker >>> _domain_matches('example.com', 'example.com') 256*90c8c64dSAndroid Build Coastguard Worker True 257*90c8c64dSAndroid Build Coastguard Worker >>> _domain_matches('a.example.com', 'example.com') 258*90c8c64dSAndroid Build Coastguard Worker True 259*90c8c64dSAndroid Build Coastguard Worker >>> _domain_matches('aaaexample.com', 'example.com') 260*90c8c64dSAndroid Build Coastguard Worker False 261*90c8c64dSAndroid Build Coastguard Worker 262*90c8c64dSAndroid Build Coastguard Worker If the domain pattern starts with '.', '.' is ignored (Section 4.1.2.3): 263*90c8c64dSAndroid Build Coastguard Worker 264*90c8c64dSAndroid Build Coastguard Worker >>> _domain_matches('a.example.com', '.example.com') 265*90c8c64dSAndroid Build Coastguard Worker True 266*90c8c64dSAndroid Build Coastguard Worker >>> _domain_matches('example.com', '.example.com') 267*90c8c64dSAndroid Build Coastguard Worker True 268*90c8c64dSAndroid Build Coastguard Worker 269*90c8c64dSAndroid Build Coastguard Worker See also: 270*90c8c64dSAndroid Build Coastguard Worker https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.2.3 271*90c8c64dSAndroid Build Coastguard Worker https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.3 272*90c8c64dSAndroid Build Coastguard Worker """ 273*90c8c64dSAndroid Build Coastguard Worker domain_pattern = domain_pattern.removeprefix('.') 274*90c8c64dSAndroid Build Coastguard Worker return (domain_name == domain_pattern or 275*90c8c64dSAndroid Build Coastguard Worker (domain_name.endswith(domain_pattern) and 276*90c8c64dSAndroid Build Coastguard Worker domain_name[-len(domain_pattern) - 1] == '.')) 277*90c8c64dSAndroid Build Coastguard Worker 278*90c8c64dSAndroid Build Coastguard Worker 279*90c8c64dSAndroid Build Coastguard Workerdef _find_auth_credentials(credentials, domain): 280*90c8c64dSAndroid Build Coastguard Worker """Find the first set of login credentials (username, password) 281*90c8c64dSAndroid Build Coastguard Worker that `domain` matches. 282*90c8c64dSAndroid Build Coastguard Worker """ 283*90c8c64dSAndroid Build Coastguard Worker for domain_pattern, login in credentials.items(): 284*90c8c64dSAndroid Build Coastguard Worker if _domain_matches(domain, domain_pattern): 285*90c8c64dSAndroid Build Coastguard Worker return login 286*90c8c64dSAndroid Build Coastguard Worker raise KeyError('Domain {} not found'.format(domain)) 287*90c8c64dSAndroid Build Coastguard Worker 288*90c8c64dSAndroid Build Coastguard Worker 289*90c8c64dSAndroid Build Coastguard Workerdef create_url_opener(cookie_file_path, domain): 290*90c8c64dSAndroid Build Coastguard Worker """Load username and password from .gitcookies and return a URL opener with 291*90c8c64dSAndroid Build Coastguard Worker an authentication handler.""" 292*90c8c64dSAndroid Build Coastguard Worker 293*90c8c64dSAndroid Build Coastguard Worker # Load authentication credentials 294*90c8c64dSAndroid Build Coastguard Worker credentials = load_auth_credentials(cookie_file_path) 295*90c8c64dSAndroid Build Coastguard Worker username, password = _find_auth_credentials(credentials, domain) 296*90c8c64dSAndroid Build Coastguard Worker 297*90c8c64dSAndroid Build Coastguard Worker # Create URL opener with authentication handler 298*90c8c64dSAndroid Build Coastguard Worker auth_handler = HTTPBasicAuthHandler() 299*90c8c64dSAndroid Build Coastguard Worker auth_handler.add_password(domain, domain, username, password) 300*90c8c64dSAndroid Build Coastguard Worker return build_opener(auth_handler) 301*90c8c64dSAndroid Build Coastguard Worker 302*90c8c64dSAndroid Build Coastguard Worker 303*90c8c64dSAndroid Build Coastguard Workerdef create_url_opener_from_args(args): 304*90c8c64dSAndroid Build Coastguard Worker """Create URL opener from command line arguments.""" 305*90c8c64dSAndroid Build Coastguard Worker 306*90c8c64dSAndroid Build Coastguard Worker if args.use_curl: 307*90c8c64dSAndroid Build Coastguard Worker handlers = [] 308*90c8c64dSAndroid Build Coastguard Worker handlers.append(CurlHTTPHandler(args.use_curl)) 309*90c8c64dSAndroid Build Coastguard Worker if _HAS_SSL: 310*90c8c64dSAndroid Build Coastguard Worker handlers.append(CurlHTTPSHandler(args.use_curl)) 311*90c8c64dSAndroid Build Coastguard Worker 312*90c8c64dSAndroid Build Coastguard Worker opener = build_opener(*handlers) 313*90c8c64dSAndroid Build Coastguard Worker return opener 314*90c8c64dSAndroid Build Coastguard Worker 315*90c8c64dSAndroid Build Coastguard Worker domain = urlparse(args.gerrit).netloc 316*90c8c64dSAndroid Build Coastguard Worker 317*90c8c64dSAndroid Build Coastguard Worker try: 318*90c8c64dSAndroid Build Coastguard Worker return create_url_opener(args.gitcookies, domain) 319*90c8c64dSAndroid Build Coastguard Worker except KeyError: 320*90c8c64dSAndroid Build Coastguard Worker print('error: Cannot find the domain "{}" in "{}". ' 321*90c8c64dSAndroid Build Coastguard Worker .format(domain, args.gitcookies), file=sys.stderr) 322*90c8c64dSAndroid Build Coastguard Worker print('error: Please check the Gerrit Code Review URL or follow the ' 323*90c8c64dSAndroid Build Coastguard Worker 'instructions in ' 324*90c8c64dSAndroid Build Coastguard Worker 'https://android.googlesource.com/platform/development/' 325*90c8c64dSAndroid Build Coastguard Worker '+/master/tools/repo_pull#installation', file=sys.stderr) 326*90c8c64dSAndroid Build Coastguard Worker sys.exit(1) 327*90c8c64dSAndroid Build Coastguard Worker 328*90c8c64dSAndroid Build Coastguard Worker 329*90c8c64dSAndroid Build Coastguard Workerdef _decode_xssi_json(data): 330*90c8c64dSAndroid Build Coastguard Worker """Trim XSSI protector and decode JSON objects. 331*90c8c64dSAndroid Build Coastguard Worker 332*90c8c64dSAndroid Build Coastguard Worker Returns: 333*90c8c64dSAndroid Build Coastguard Worker An object returned by json.loads(). 334*90c8c64dSAndroid Build Coastguard Worker 335*90c8c64dSAndroid Build Coastguard Worker Raises: 336*90c8c64dSAndroid Build Coastguard Worker ValueError: If data doesn't start with a XSSI token. 337*90c8c64dSAndroid Build Coastguard Worker json.JSONDecodeError: If data failed to decode. 338*90c8c64dSAndroid Build Coastguard Worker """ 339*90c8c64dSAndroid Build Coastguard Worker 340*90c8c64dSAndroid Build Coastguard Worker # Decode UTF-8 341*90c8c64dSAndroid Build Coastguard Worker data = data.decode('utf-8') 342*90c8c64dSAndroid Build Coastguard Worker 343*90c8c64dSAndroid Build Coastguard Worker # Trim cross site script inclusion (XSSI) protector 344*90c8c64dSAndroid Build Coastguard Worker if data[0:4] != ')]}\'': 345*90c8c64dSAndroid Build Coastguard Worker raise ValueError('unexpected responsed content: ' + data) 346*90c8c64dSAndroid Build Coastguard Worker data = data[4:] 347*90c8c64dSAndroid Build Coastguard Worker 348*90c8c64dSAndroid Build Coastguard Worker # Parse JSON objects 349*90c8c64dSAndroid Build Coastguard Worker return json.loads(data) 350*90c8c64dSAndroid Build Coastguard Worker 351*90c8c64dSAndroid Build Coastguard Worker 352*90c8c64dSAndroid Build Coastguard Workerdef _query_change_lists(url_opener, gerrit, query_string, start, count): 353*90c8c64dSAndroid Build Coastguard Worker """Query change lists from the Gerrit server with a single request. 354*90c8c64dSAndroid Build Coastguard Worker 355*90c8c64dSAndroid Build Coastguard Worker This function performs a single query of the Gerrit server based on the 356*90c8c64dSAndroid Build Coastguard Worker input parameters for a list of changes. The server may return less than 357*90c8c64dSAndroid Build Coastguard Worker the number of changes requested. The caller should check the last record 358*90c8c64dSAndroid Build Coastguard Worker returned for the _more_changes attribute to determine if more changes are 359*90c8c64dSAndroid Build Coastguard Worker available and perform additional queries adjusting the start index. 360*90c8c64dSAndroid Build Coastguard Worker 361*90c8c64dSAndroid Build Coastguard Worker Args: 362*90c8c64dSAndroid Build Coastguard Worker url_opener: URL opener for request 363*90c8c64dSAndroid Build Coastguard Worker gerrit: Gerrit server URL 364*90c8c64dSAndroid Build Coastguard Worker query_string: Gerrit query string to select changes 365*90c8c64dSAndroid Build Coastguard Worker start: Number of changes to be skipped from the beginning 366*90c8c64dSAndroid Build Coastguard Worker count: Maximum number of changes to return 367*90c8c64dSAndroid Build Coastguard Worker 368*90c8c64dSAndroid Build Coastguard Worker Returns: 369*90c8c64dSAndroid Build Coastguard Worker List of changes 370*90c8c64dSAndroid Build Coastguard Worker """ 371*90c8c64dSAndroid Build Coastguard Worker data = [ 372*90c8c64dSAndroid Build Coastguard Worker ('q', query_string), 373*90c8c64dSAndroid Build Coastguard Worker ('o', 'CURRENT_REVISION'), 374*90c8c64dSAndroid Build Coastguard Worker ('o', 'CURRENT_COMMIT'), 375*90c8c64dSAndroid Build Coastguard Worker ('start', str(start)), 376*90c8c64dSAndroid Build Coastguard Worker ('n', str(count)), 377*90c8c64dSAndroid Build Coastguard Worker ] 378*90c8c64dSAndroid Build Coastguard Worker url = gerrit + '/a/changes/?' + urlencode(data) 379*90c8c64dSAndroid Build Coastguard Worker 380*90c8c64dSAndroid Build Coastguard Worker response_file = url_opener.open(url) 381*90c8c64dSAndroid Build Coastguard Worker try: 382*90c8c64dSAndroid Build Coastguard Worker return _decode_xssi_json(response_file.read()) 383*90c8c64dSAndroid Build Coastguard Worker finally: 384*90c8c64dSAndroid Build Coastguard Worker response_file.close() 385*90c8c64dSAndroid Build Coastguard Worker 386*90c8c64dSAndroid Build Coastguard Workerdef query_change_lists(url_opener, gerrit, query_string, start, count): 387*90c8c64dSAndroid Build Coastguard Worker """Query change lists from the Gerrit server. 388*90c8c64dSAndroid Build Coastguard Worker 389*90c8c64dSAndroid Build Coastguard Worker This function queries the Gerrit server based on the input parameters for a 390*90c8c64dSAndroid Build Coastguard Worker list of changes. This function handles querying the server multiple times 391*90c8c64dSAndroid Build Coastguard Worker if necessary and combining the results that are returned to the caller. 392*90c8c64dSAndroid Build Coastguard Worker 393*90c8c64dSAndroid Build Coastguard Worker Args: 394*90c8c64dSAndroid Build Coastguard Worker url_opener: URL opener for request 395*90c8c64dSAndroid Build Coastguard Worker gerrit: Gerrit server URL 396*90c8c64dSAndroid Build Coastguard Worker query_string: Gerrit query string to select changes 397*90c8c64dSAndroid Build Coastguard Worker start: Number of changes to be skipped from the beginning 398*90c8c64dSAndroid Build Coastguard Worker count: Maximum number of changes to return 399*90c8c64dSAndroid Build Coastguard Worker 400*90c8c64dSAndroid Build Coastguard Worker Returns: 401*90c8c64dSAndroid Build Coastguard Worker List of changes 402*90c8c64dSAndroid Build Coastguard Worker """ 403*90c8c64dSAndroid Build Coastguard Worker changes = [] 404*90c8c64dSAndroid Build Coastguard Worker while len(changes) < count: 405*90c8c64dSAndroid Build Coastguard Worker chunk = _query_change_lists(url_opener, gerrit, query_string, 406*90c8c64dSAndroid Build Coastguard Worker start + len(changes), count - len(changes)) 407*90c8c64dSAndroid Build Coastguard Worker if not chunk: 408*90c8c64dSAndroid Build Coastguard Worker break 409*90c8c64dSAndroid Build Coastguard Worker 410*90c8c64dSAndroid Build Coastguard Worker changes += chunk 411*90c8c64dSAndroid Build Coastguard Worker 412*90c8c64dSAndroid Build Coastguard Worker # The last change object contains a _more_changes attribute if the 413*90c8c64dSAndroid Build Coastguard Worker # number of changes exceeds the query parameter or the internal server 414*90c8c64dSAndroid Build Coastguard Worker # limit. Stop iteration if `_more_changes` attribute doesn't exist. 415*90c8c64dSAndroid Build Coastguard Worker if '_more_changes' not in chunk[-1]: 416*90c8c64dSAndroid Build Coastguard Worker break 417*90c8c64dSAndroid Build Coastguard Worker 418*90c8c64dSAndroid Build Coastguard Worker return changes 419*90c8c64dSAndroid Build Coastguard Worker 420*90c8c64dSAndroid Build Coastguard Worker 421*90c8c64dSAndroid Build Coastguard Workerdef _make_json_post_request(url_opener, url, data, method='POST'): 422*90c8c64dSAndroid Build Coastguard Worker """Open an URL request and decode its response. 423*90c8c64dSAndroid Build Coastguard Worker 424*90c8c64dSAndroid Build Coastguard Worker Returns a 3-tuple of (code, body, json). 425*90c8c64dSAndroid Build Coastguard Worker code: A numerical value, the HTTP status code of the response. 426*90c8c64dSAndroid Build Coastguard Worker body: A bytes, the response body. 427*90c8c64dSAndroid Build Coastguard Worker json: An object, the parsed JSON response. 428*90c8c64dSAndroid Build Coastguard Worker """ 429*90c8c64dSAndroid Build Coastguard Worker 430*90c8c64dSAndroid Build Coastguard Worker data = json.dumps(data).encode('utf-8') 431*90c8c64dSAndroid Build Coastguard Worker headers = { 432*90c8c64dSAndroid Build Coastguard Worker 'Content-Type': 'application/json; charset=UTF-8', 433*90c8c64dSAndroid Build Coastguard Worker } 434*90c8c64dSAndroid Build Coastguard Worker 435*90c8c64dSAndroid Build Coastguard Worker request = Request(url, data, headers) 436*90c8c64dSAndroid Build Coastguard Worker request.get_method = lambda: method 437*90c8c64dSAndroid Build Coastguard Worker 438*90c8c64dSAndroid Build Coastguard Worker try: 439*90c8c64dSAndroid Build Coastguard Worker response_file = url_opener.open(request) 440*90c8c64dSAndroid Build Coastguard Worker except HTTPError as error: 441*90c8c64dSAndroid Build Coastguard Worker response_file = error 442*90c8c64dSAndroid Build Coastguard Worker 443*90c8c64dSAndroid Build Coastguard Worker with response_file: 444*90c8c64dSAndroid Build Coastguard Worker res_code = response_file.getcode() 445*90c8c64dSAndroid Build Coastguard Worker res_body = response_file.read() 446*90c8c64dSAndroid Build Coastguard Worker try: 447*90c8c64dSAndroid Build Coastguard Worker res_json = _decode_xssi_json(res_body) 448*90c8c64dSAndroid Build Coastguard Worker except ValueError: 449*90c8c64dSAndroid Build Coastguard Worker # The response isn't JSON if it doesn't start with a XSSI token. 450*90c8c64dSAndroid Build Coastguard Worker # Possibly a plain text error message or empty body. 451*90c8c64dSAndroid Build Coastguard Worker res_json = None 452*90c8c64dSAndroid Build Coastguard Worker return (res_code, res_body, res_json) 453*90c8c64dSAndroid Build Coastguard Worker 454*90c8c64dSAndroid Build Coastguard Worker 455*90c8c64dSAndroid Build Coastguard Workerdef set_review(url_opener, gerrit_url, change_id, labels, message): 456*90c8c64dSAndroid Build Coastguard Worker """Set review votes to a change list.""" 457*90c8c64dSAndroid Build Coastguard Worker 458*90c8c64dSAndroid Build Coastguard Worker url = '{}/a/changes/{}/revisions/current/review'.format( 459*90c8c64dSAndroid Build Coastguard Worker gerrit_url, change_id) 460*90c8c64dSAndroid Build Coastguard Worker 461*90c8c64dSAndroid Build Coastguard Worker data = {} 462*90c8c64dSAndroid Build Coastguard Worker if labels: 463*90c8c64dSAndroid Build Coastguard Worker data['labels'] = labels 464*90c8c64dSAndroid Build Coastguard Worker if message: 465*90c8c64dSAndroid Build Coastguard Worker data['message'] = message 466*90c8c64dSAndroid Build Coastguard Worker 467*90c8c64dSAndroid Build Coastguard Worker return _make_json_post_request(url_opener, url, data) 468*90c8c64dSAndroid Build Coastguard Worker 469*90c8c64dSAndroid Build Coastguard Worker 470*90c8c64dSAndroid Build Coastguard Workerdef submit(url_opener, gerrit_url, change_id): 471*90c8c64dSAndroid Build Coastguard Worker """Submit a change list.""" 472*90c8c64dSAndroid Build Coastguard Worker 473*90c8c64dSAndroid Build Coastguard Worker url = '{}/a/changes/{}/submit'.format(gerrit_url, change_id) 474*90c8c64dSAndroid Build Coastguard Worker 475*90c8c64dSAndroid Build Coastguard Worker return _make_json_post_request(url_opener, url, {}) 476*90c8c64dSAndroid Build Coastguard Worker 477*90c8c64dSAndroid Build Coastguard Worker 478*90c8c64dSAndroid Build Coastguard Workerdef abandon(url_opener, gerrit_url, change_id, message): 479*90c8c64dSAndroid Build Coastguard Worker """Abandon a change list.""" 480*90c8c64dSAndroid Build Coastguard Worker 481*90c8c64dSAndroid Build Coastguard Worker url = '{}/a/changes/{}/abandon'.format(gerrit_url, change_id) 482*90c8c64dSAndroid Build Coastguard Worker 483*90c8c64dSAndroid Build Coastguard Worker data = {} 484*90c8c64dSAndroid Build Coastguard Worker if message: 485*90c8c64dSAndroid Build Coastguard Worker data['message'] = message 486*90c8c64dSAndroid Build Coastguard Worker 487*90c8c64dSAndroid Build Coastguard Worker return _make_json_post_request(url_opener, url, data) 488*90c8c64dSAndroid Build Coastguard Worker 489*90c8c64dSAndroid Build Coastguard Worker 490*90c8c64dSAndroid Build Coastguard Workerdef restore(url_opener, gerrit_url, change_id): 491*90c8c64dSAndroid Build Coastguard Worker """Restore a change list.""" 492*90c8c64dSAndroid Build Coastguard Worker 493*90c8c64dSAndroid Build Coastguard Worker url = '{}/a/changes/{}/restore'.format(gerrit_url, change_id) 494*90c8c64dSAndroid Build Coastguard Worker 495*90c8c64dSAndroid Build Coastguard Worker return _make_json_post_request(url_opener, url, {}) 496*90c8c64dSAndroid Build Coastguard Worker 497*90c8c64dSAndroid Build Coastguard Worker 498*90c8c64dSAndroid Build Coastguard Workerdef delete(url_opener, gerrit_url, change_id): 499*90c8c64dSAndroid Build Coastguard Worker """Delete a change list.""" 500*90c8c64dSAndroid Build Coastguard Worker 501*90c8c64dSAndroid Build Coastguard Worker url = '{}/a/changes/{}'.format(gerrit_url, change_id) 502*90c8c64dSAndroid Build Coastguard Worker 503*90c8c64dSAndroid Build Coastguard Worker return _make_json_post_request(url_opener, url, {}, method='DELETE') 504*90c8c64dSAndroid Build Coastguard Worker 505*90c8c64dSAndroid Build Coastguard Worker 506*90c8c64dSAndroid Build Coastguard Workerdef set_topic(url_opener, gerrit_url, change_id, name): 507*90c8c64dSAndroid Build Coastguard Worker """Set the topic name.""" 508*90c8c64dSAndroid Build Coastguard Worker 509*90c8c64dSAndroid Build Coastguard Worker url = '{}/a/changes/{}/topic'.format(gerrit_url, change_id) 510*90c8c64dSAndroid Build Coastguard Worker data = {'topic': name} 511*90c8c64dSAndroid Build Coastguard Worker return _make_json_post_request(url_opener, url, data, method='PUT') 512*90c8c64dSAndroid Build Coastguard Worker 513*90c8c64dSAndroid Build Coastguard Worker 514*90c8c64dSAndroid Build Coastguard Workerdef delete_topic(url_opener, gerrit_url, change_id): 515*90c8c64dSAndroid Build Coastguard Worker """Delete the topic name.""" 516*90c8c64dSAndroid Build Coastguard Worker 517*90c8c64dSAndroid Build Coastguard Worker url = '{}/a/changes/{}/topic'.format(gerrit_url, change_id) 518*90c8c64dSAndroid Build Coastguard Worker 519*90c8c64dSAndroid Build Coastguard Worker return _make_json_post_request(url_opener, url, {}, method='DELETE') 520*90c8c64dSAndroid Build Coastguard Worker 521*90c8c64dSAndroid Build Coastguard Worker 522*90c8c64dSAndroid Build Coastguard Workerdef set_hashtags(url_opener, gerrit_url, change_id, add_tags=None, 523*90c8c64dSAndroid Build Coastguard Worker remove_tags=None): 524*90c8c64dSAndroid Build Coastguard Worker """Add or remove hash tags.""" 525*90c8c64dSAndroid Build Coastguard Worker 526*90c8c64dSAndroid Build Coastguard Worker url = '{}/a/changes/{}/hashtags'.format(gerrit_url, change_id) 527*90c8c64dSAndroid Build Coastguard Worker 528*90c8c64dSAndroid Build Coastguard Worker data = {} 529*90c8c64dSAndroid Build Coastguard Worker if add_tags: 530*90c8c64dSAndroid Build Coastguard Worker data['add'] = add_tags 531*90c8c64dSAndroid Build Coastguard Worker if remove_tags: 532*90c8c64dSAndroid Build Coastguard Worker data['remove'] = remove_tags 533*90c8c64dSAndroid Build Coastguard Worker 534*90c8c64dSAndroid Build Coastguard Worker return _make_json_post_request(url_opener, url, data) 535*90c8c64dSAndroid Build Coastguard Worker 536*90c8c64dSAndroid Build Coastguard Worker 537*90c8c64dSAndroid Build Coastguard Workerdef add_reviewers(url_opener, gerrit_url, change_id, reviewers): 538*90c8c64dSAndroid Build Coastguard Worker """Add reviewers.""" 539*90c8c64dSAndroid Build Coastguard Worker 540*90c8c64dSAndroid Build Coastguard Worker url = '{}/a/changes/{}/revisions/current/review'.format( 541*90c8c64dSAndroid Build Coastguard Worker gerrit_url, change_id) 542*90c8c64dSAndroid Build Coastguard Worker 543*90c8c64dSAndroid Build Coastguard Worker data = {} 544*90c8c64dSAndroid Build Coastguard Worker if reviewers: 545*90c8c64dSAndroid Build Coastguard Worker data['reviewers'] = reviewers 546*90c8c64dSAndroid Build Coastguard Worker 547*90c8c64dSAndroid Build Coastguard Worker return _make_json_post_request(url_opener, url, data) 548*90c8c64dSAndroid Build Coastguard Worker 549*90c8c64dSAndroid Build Coastguard Worker 550*90c8c64dSAndroid Build Coastguard Workerdef delete_reviewer(url_opener, gerrit_url, change_id, name): 551*90c8c64dSAndroid Build Coastguard Worker """Delete reviewer.""" 552*90c8c64dSAndroid Build Coastguard Worker 553*90c8c64dSAndroid Build Coastguard Worker url = '{}/a/changes/{}/reviewers/{}/delete'.format( 554*90c8c64dSAndroid Build Coastguard Worker gerrit_url, change_id, name) 555*90c8c64dSAndroid Build Coastguard Worker 556*90c8c64dSAndroid Build Coastguard Worker return _make_json_post_request(url_opener, url, {}) 557*90c8c64dSAndroid Build Coastguard Worker 558*90c8c64dSAndroid Build Coastguard Worker 559*90c8c64dSAndroid Build Coastguard Workerdef get_patch(url_opener, gerrit_url, change_id, revision_id='current'): 560*90c8c64dSAndroid Build Coastguard Worker """Download the patch file.""" 561*90c8c64dSAndroid Build Coastguard Worker 562*90c8c64dSAndroid Build Coastguard Worker url = '{}/a/changes/{}/revisions/{}/patch'.format( 563*90c8c64dSAndroid Build Coastguard Worker gerrit_url, change_id, revision_id) 564*90c8c64dSAndroid Build Coastguard Worker 565*90c8c64dSAndroid Build Coastguard Worker response_file = url_opener.open(url) 566*90c8c64dSAndroid Build Coastguard Worker try: 567*90c8c64dSAndroid Build Coastguard Worker return base64.b64decode(response_file.read()) 568*90c8c64dSAndroid Build Coastguard Worker finally: 569*90c8c64dSAndroid Build Coastguard Worker response_file.close() 570*90c8c64dSAndroid Build Coastguard Worker 571*90c8c64dSAndroid Build Coastguard Workerdef find_gerrit_name(): 572*90c8c64dSAndroid Build Coastguard Worker """Find the gerrit instance specified in the default remote.""" 573*90c8c64dSAndroid Build Coastguard Worker manifest_cmd = ['repo', 'manifest'] 574*90c8c64dSAndroid Build Coastguard Worker raw_manifest_xml = run(manifest_cmd, stdout=PIPE, check=True).stdout 575*90c8c64dSAndroid Build Coastguard Worker 576*90c8c64dSAndroid Build Coastguard Worker manifest_xml = xml.dom.minidom.parseString(raw_manifest_xml) 577*90c8c64dSAndroid Build Coastguard Worker default_remote = manifest_xml.getElementsByTagName('default')[0] 578*90c8c64dSAndroid Build Coastguard Worker default_remote_name = default_remote.getAttribute('remote') 579*90c8c64dSAndroid Build Coastguard Worker for remote in manifest_xml.getElementsByTagName('remote'): 580*90c8c64dSAndroid Build Coastguard Worker name = remote.getAttribute('name') 581*90c8c64dSAndroid Build Coastguard Worker review = remote.getAttribute('review') 582*90c8c64dSAndroid Build Coastguard Worker if review and name == default_remote_name: 583*90c8c64dSAndroid Build Coastguard Worker return review.rstrip('/') 584*90c8c64dSAndroid Build Coastguard Worker 585*90c8c64dSAndroid Build Coastguard Worker raise ValueError('cannot find gerrit URL from manifest') 586*90c8c64dSAndroid Build Coastguard Worker 587*90c8c64dSAndroid Build Coastguard Workerdef normalize_gerrit_name(gerrit): 588*90c8c64dSAndroid Build Coastguard Worker """Strip the trailing slashes because Gerrit will return 404 when there are 589*90c8c64dSAndroid Build Coastguard Worker redundant trailing slashes.""" 590*90c8c64dSAndroid Build Coastguard Worker return gerrit.rstrip('/') 591*90c8c64dSAndroid Build Coastguard Worker 592*90c8c64dSAndroid Build Coastguard Workerdef add_common_parse_args(parser): 593*90c8c64dSAndroid Build Coastguard Worker parser.add_argument('query', help='Change list query string') 594*90c8c64dSAndroid Build Coastguard Worker parser.add_argument('-g', '--gerrit', help='Gerrit review URL') 595*90c8c64dSAndroid Build Coastguard Worker parser.add_argument('--gitcookies', 596*90c8c64dSAndroid Build Coastguard Worker default=os.path.expanduser('~/.gitcookies'), 597*90c8c64dSAndroid Build Coastguard Worker help='Gerrit cookie file') 598*90c8c64dSAndroid Build Coastguard Worker parser.add_argument('--limits', default=1000, type=int, 599*90c8c64dSAndroid Build Coastguard Worker help='Max number of change lists') 600*90c8c64dSAndroid Build Coastguard Worker parser.add_argument('--start', default=0, type=int, 601*90c8c64dSAndroid Build Coastguard Worker help='Skip first N changes in query') 602*90c8c64dSAndroid Build Coastguard Worker parser.add_argument( 603*90c8c64dSAndroid Build Coastguard Worker '--use-curl', 604*90c8c64dSAndroid Build Coastguard Worker help='Send requests with the specified curl command (e.g. `curl`)') 605*90c8c64dSAndroid Build Coastguard Worker 606*90c8c64dSAndroid Build Coastguard Workerdef _parse_args(): 607*90c8c64dSAndroid Build Coastguard Worker """Parse command line options.""" 608*90c8c64dSAndroid Build Coastguard Worker parser = argparse.ArgumentParser() 609*90c8c64dSAndroid Build Coastguard Worker add_common_parse_args(parser) 610*90c8c64dSAndroid Build Coastguard Worker parser.add_argument('--format', default='json', 611*90c8c64dSAndroid Build Coastguard Worker choices=['json', 'oneline', 'porcelain'], 612*90c8c64dSAndroid Build Coastguard Worker help='Print format') 613*90c8c64dSAndroid Build Coastguard Worker return parser.parse_args() 614*90c8c64dSAndroid Build Coastguard Worker 615*90c8c64dSAndroid Build Coastguard Workerdef main(): 616*90c8c64dSAndroid Build Coastguard Worker """Main function""" 617*90c8c64dSAndroid Build Coastguard Worker args = _parse_args() 618*90c8c64dSAndroid Build Coastguard Worker 619*90c8c64dSAndroid Build Coastguard Worker if args.gerrit: 620*90c8c64dSAndroid Build Coastguard Worker args.gerrit = normalize_gerrit_name(args.gerrit) 621*90c8c64dSAndroid Build Coastguard Worker else: 622*90c8c64dSAndroid Build Coastguard Worker try: 623*90c8c64dSAndroid Build Coastguard Worker args.gerrit = find_gerrit_name() 624*90c8c64dSAndroid Build Coastguard Worker # pylint: disable=bare-except 625*90c8c64dSAndroid Build Coastguard Worker except: 626*90c8c64dSAndroid Build Coastguard Worker print('gerrit instance not found, use [-g GERRIT]') 627*90c8c64dSAndroid Build Coastguard Worker sys.exit(1) 628*90c8c64dSAndroid Build Coastguard Worker 629*90c8c64dSAndroid Build Coastguard Worker # Query change lists 630*90c8c64dSAndroid Build Coastguard Worker url_opener = create_url_opener_from_args(args) 631*90c8c64dSAndroid Build Coastguard Worker change_lists = query_change_lists( 632*90c8c64dSAndroid Build Coastguard Worker url_opener, args.gerrit, args.query, args.start, args.limits) 633*90c8c64dSAndroid Build Coastguard Worker 634*90c8c64dSAndroid Build Coastguard Worker # Print the result 635*90c8c64dSAndroid Build Coastguard Worker if args.format == 'json': 636*90c8c64dSAndroid Build Coastguard Worker json.dump(change_lists, sys.stdout, indent=4, separators=(', ', ': ')) 637*90c8c64dSAndroid Build Coastguard Worker print() # Print the end-of-line 638*90c8c64dSAndroid Build Coastguard Worker else: 639*90c8c64dSAndroid Build Coastguard Worker if args.format == 'oneline': 640*90c8c64dSAndroid Build Coastguard Worker format_str = ('{i:<8} {number:<16} {status:<20} ' 641*90c8c64dSAndroid Build Coastguard Worker '{change_id:<60} {project:<120} ' 642*90c8c64dSAndroid Build Coastguard Worker '{subject}') 643*90c8c64dSAndroid Build Coastguard Worker else: 644*90c8c64dSAndroid Build Coastguard Worker format_str = ('{i}\t{number}\t{status}\t' 645*90c8c64dSAndroid Build Coastguard Worker '{change_id}\t{project}\t{subject}') 646*90c8c64dSAndroid Build Coastguard Worker 647*90c8c64dSAndroid Build Coastguard Worker for i, change in enumerate(change_lists): 648*90c8c64dSAndroid Build Coastguard Worker print(format_str.format(i=i, 649*90c8c64dSAndroid Build Coastguard Worker project=change['project'], 650*90c8c64dSAndroid Build Coastguard Worker change_id=change['change_id'], 651*90c8c64dSAndroid Build Coastguard Worker status=change['status'], 652*90c8c64dSAndroid Build Coastguard Worker number=change['_number'], 653*90c8c64dSAndroid Build Coastguard Worker subject=change['subject'])) 654*90c8c64dSAndroid Build Coastguard Worker 655*90c8c64dSAndroid Build Coastguard Worker 656*90c8c64dSAndroid Build Coastguard Workerif __name__ == '__main__': 657*90c8c64dSAndroid Build Coastguard Worker main() 658