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"""A command line utility to pull multiple change lists from Gerrit.""" 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 collections 25*90c8c64dSAndroid Build Coastguard Workerimport itertools 26*90c8c64dSAndroid Build Coastguard Workerimport json 27*90c8c64dSAndroid Build Coastguard Workerimport multiprocessing 28*90c8c64dSAndroid Build Coastguard Workerimport os 29*90c8c64dSAndroid Build Coastguard Workerimport os.path 30*90c8c64dSAndroid Build Coastguard Workerimport re 31*90c8c64dSAndroid Build Coastguard Workerimport sys 32*90c8c64dSAndroid Build Coastguard Workerimport xml.dom.minidom 33*90c8c64dSAndroid Build Coastguard Worker 34*90c8c64dSAndroid Build Coastguard Workerfrom gerrit import ( 35*90c8c64dSAndroid Build Coastguard Worker add_common_parse_args, create_url_opener_from_args, find_gerrit_name, 36*90c8c64dSAndroid Build Coastguard Worker normalize_gerrit_name, query_change_lists, run 37*90c8c64dSAndroid Build Coastguard Worker) 38*90c8c64dSAndroid Build Coastguard Workerfrom subprocess import PIPE 39*90c8c64dSAndroid Build Coastguard Worker 40*90c8c64dSAndroid Build Coastguard Workertry: 41*90c8c64dSAndroid Build Coastguard Worker # pylint: disable=redefined-builtin 42*90c8c64dSAndroid Build Coastguard Worker from __builtin__ import raw_input as input # PY2 43*90c8c64dSAndroid Build Coastguard Workerexcept ImportError: 44*90c8c64dSAndroid Build Coastguard Worker pass 45*90c8c64dSAndroid Build Coastguard Worker 46*90c8c64dSAndroid Build Coastguard Workertry: 47*90c8c64dSAndroid Build Coastguard Worker from shlex import quote as _sh_quote # PY3.3 48*90c8c64dSAndroid Build Coastguard Workerexcept ImportError: 49*90c8c64dSAndroid Build Coastguard Worker # Shell language simple string pattern. If a string matches this pattern, 50*90c8c64dSAndroid Build Coastguard Worker # it doesn't have to be quoted. 51*90c8c64dSAndroid Build Coastguard Worker _SHELL_SIMPLE_PATTERN = re.compile('^[a-zA-Z90-9_./-]+$') 52*90c8c64dSAndroid Build Coastguard Worker 53*90c8c64dSAndroid Build Coastguard Worker def _sh_quote(txt): 54*90c8c64dSAndroid Build Coastguard Worker """Quote a string if it contains special characters.""" 55*90c8c64dSAndroid Build Coastguard Worker return txt if _SHELL_SIMPLE_PATTERN.match(txt) else json.dumps(txt) 56*90c8c64dSAndroid Build Coastguard Worker 57*90c8c64dSAndroid Build Coastguard Worker 58*90c8c64dSAndroid Build Coastguard Workerif bytes is str: 59*90c8c64dSAndroid Build Coastguard Worker def write_bytes(data, file): # PY2 60*90c8c64dSAndroid Build Coastguard Worker """Write bytes to a file.""" 61*90c8c64dSAndroid Build Coastguard Worker # pylint: disable=redefined-builtin 62*90c8c64dSAndroid Build Coastguard Worker file.write(data) 63*90c8c64dSAndroid Build Coastguard Workerelse: 64*90c8c64dSAndroid Build Coastguard Worker def write_bytes(data, file): # PY3 65*90c8c64dSAndroid Build Coastguard Worker """Write bytes to a file.""" 66*90c8c64dSAndroid Build Coastguard Worker # pylint: disable=redefined-builtin 67*90c8c64dSAndroid Build Coastguard Worker file.buffer.write(data) 68*90c8c64dSAndroid Build Coastguard Worker 69*90c8c64dSAndroid Build Coastguard Worker 70*90c8c64dSAndroid Build Coastguard Workerdef _confirm(question, default, file=sys.stderr): 71*90c8c64dSAndroid Build Coastguard Worker """Prompt a yes/no question and convert the answer to a boolean value.""" 72*90c8c64dSAndroid Build Coastguard Worker # pylint: disable=redefined-builtin 73*90c8c64dSAndroid Build Coastguard Worker answers = {'': default, 'y': True, 'yes': True, 'n': False, 'no': False} 74*90c8c64dSAndroid Build Coastguard Worker suffix = '[Y/n] ' if default else ' [y/N] ' 75*90c8c64dSAndroid Build Coastguard Worker while True: 76*90c8c64dSAndroid Build Coastguard Worker file.write(question + suffix) 77*90c8c64dSAndroid Build Coastguard Worker file.flush() 78*90c8c64dSAndroid Build Coastguard Worker ans = answers.get(input().lower()) 79*90c8c64dSAndroid Build Coastguard Worker if ans is not None: 80*90c8c64dSAndroid Build Coastguard Worker return ans 81*90c8c64dSAndroid Build Coastguard Worker 82*90c8c64dSAndroid Build Coastguard Worker 83*90c8c64dSAndroid Build Coastguard Workerclass ChangeList(object): 84*90c8c64dSAndroid Build Coastguard Worker """A ChangeList to be checked out.""" 85*90c8c64dSAndroid Build Coastguard Worker # pylint: disable=too-few-public-methods,too-many-instance-attributes 86*90c8c64dSAndroid Build Coastguard Worker 87*90c8c64dSAndroid Build Coastguard Worker def __init__(self, project, fetch, commit_sha1, commit, change_list): 88*90c8c64dSAndroid Build Coastguard Worker """Initialize a ChangeList instance.""" 89*90c8c64dSAndroid Build Coastguard Worker # pylint: disable=too-many-arguments 90*90c8c64dSAndroid Build Coastguard Worker 91*90c8c64dSAndroid Build Coastguard Worker self.project = project 92*90c8c64dSAndroid Build Coastguard Worker self.number = change_list['_number'] 93*90c8c64dSAndroid Build Coastguard Worker self.branch = change_list['branch'] 94*90c8c64dSAndroid Build Coastguard Worker 95*90c8c64dSAndroid Build Coastguard Worker self.fetch = fetch 96*90c8c64dSAndroid Build Coastguard Worker 97*90c8c64dSAndroid Build Coastguard Worker fetch_git = None 98*90c8c64dSAndroid Build Coastguard Worker for protocol in ('http', 'sso', 'rpc', 'ssh'): 99*90c8c64dSAndroid Build Coastguard Worker fetch_git = fetch.get(protocol) 100*90c8c64dSAndroid Build Coastguard Worker if fetch_git: 101*90c8c64dSAndroid Build Coastguard Worker break 102*90c8c64dSAndroid Build Coastguard Worker 103*90c8c64dSAndroid Build Coastguard Worker if not fetch_git: 104*90c8c64dSAndroid Build Coastguard Worker raise ValueError( 105*90c8c64dSAndroid Build Coastguard Worker 'unknown fetch protocols: ' + str(list(fetch.keys()))) 106*90c8c64dSAndroid Build Coastguard Worker 107*90c8c64dSAndroid Build Coastguard Worker self.fetch_url = fetch_git['url'] 108*90c8c64dSAndroid Build Coastguard Worker self.fetch_ref = fetch_git['ref'] 109*90c8c64dSAndroid Build Coastguard Worker 110*90c8c64dSAndroid Build Coastguard Worker self.commit_sha1 = commit_sha1 111*90c8c64dSAndroid Build Coastguard Worker self.commit = commit 112*90c8c64dSAndroid Build Coastguard Worker self.parents = commit['parents'] 113*90c8c64dSAndroid Build Coastguard Worker 114*90c8c64dSAndroid Build Coastguard Worker self.change_list = change_list 115*90c8c64dSAndroid Build Coastguard Worker 116*90c8c64dSAndroid Build Coastguard Worker 117*90c8c64dSAndroid Build Coastguard Worker def is_merge(self): 118*90c8c64dSAndroid Build Coastguard Worker """Check whether this change list a merge commit.""" 119*90c8c64dSAndroid Build Coastguard Worker return len(self.parents) > 1 120*90c8c64dSAndroid Build Coastguard Worker 121*90c8c64dSAndroid Build Coastguard Worker 122*90c8c64dSAndroid Build Coastguard Workerdef find_repo_top(curdir): 123*90c8c64dSAndroid Build Coastguard Worker """Find the top directory for this git-repo source tree.""" 124*90c8c64dSAndroid Build Coastguard Worker olddir = None 125*90c8c64dSAndroid Build Coastguard Worker while curdir != olddir: 126*90c8c64dSAndroid Build Coastguard Worker if os.path.exists(os.path.join(curdir, '.repo')): 127*90c8c64dSAndroid Build Coastguard Worker return curdir 128*90c8c64dSAndroid Build Coastguard Worker olddir = curdir 129*90c8c64dSAndroid Build Coastguard Worker curdir = os.path.dirname(curdir) 130*90c8c64dSAndroid Build Coastguard Worker raise ValueError('.repo dir not found') 131*90c8c64dSAndroid Build Coastguard Worker 132*90c8c64dSAndroid Build Coastguard Worker 133*90c8c64dSAndroid Build Coastguard Workerclass ProjectNameDirDict: 134*90c8c64dSAndroid Build Coastguard Worker """A dict which maps project name and revision to the source path.""" 135*90c8c64dSAndroid Build Coastguard Worker def __init__(self): 136*90c8c64dSAndroid Build Coastguard Worker self._dirs = dict() 137*90c8c64dSAndroid Build Coastguard Worker 138*90c8c64dSAndroid Build Coastguard Worker 139*90c8c64dSAndroid Build Coastguard Worker def add_directory(self, name, revision, path): 140*90c8c64dSAndroid Build Coastguard Worker """Maps project name and revision to path.""" 141*90c8c64dSAndroid Build Coastguard Worker self._dirs[name] = path or name 142*90c8c64dSAndroid Build Coastguard Worker if revision: 143*90c8c64dSAndroid Build Coastguard Worker self._dirs[(name, revision)] = path or name 144*90c8c64dSAndroid Build Coastguard Worker 145*90c8c64dSAndroid Build Coastguard Worker 146*90c8c64dSAndroid Build Coastguard Worker def find_directory(self, name, revision, default_result=None): 147*90c8c64dSAndroid Build Coastguard Worker """Finds corresponding path of project name and revision.""" 148*90c8c64dSAndroid Build Coastguard Worker if (name, revision) in self._dirs: 149*90c8c64dSAndroid Build Coastguard Worker return self._dirs[(name, revision)] 150*90c8c64dSAndroid Build Coastguard Worker if default_result is None: 151*90c8c64dSAndroid Build Coastguard Worker return self._dirs[name] 152*90c8c64dSAndroid Build Coastguard Worker return self._dirs.get(name, default_result) 153*90c8c64dSAndroid Build Coastguard Worker 154*90c8c64dSAndroid Build Coastguard Worker 155*90c8c64dSAndroid Build Coastguard Workerdef build_project_name_dir_dict(manifest_name): 156*90c8c64dSAndroid Build Coastguard Worker """Build the mapping from Gerrit project name to source tree project 157*90c8c64dSAndroid Build Coastguard Worker directory path.""" 158*90c8c64dSAndroid Build Coastguard Worker manifest_cmd = ['repo', 'manifest'] 159*90c8c64dSAndroid Build Coastguard Worker if manifest_name: 160*90c8c64dSAndroid Build Coastguard Worker manifest_cmd.extend(['-m', manifest_name]) 161*90c8c64dSAndroid Build Coastguard Worker raw_manifest_xml = run(manifest_cmd, stdout=PIPE, check=True).stdout 162*90c8c64dSAndroid Build Coastguard Worker 163*90c8c64dSAndroid Build Coastguard Worker manifest_xml = xml.dom.minidom.parseString(raw_manifest_xml) 164*90c8c64dSAndroid Build Coastguard Worker project_dirs = ProjectNameDirDict() 165*90c8c64dSAndroid Build Coastguard Worker for project in manifest_xml.getElementsByTagName('project'): 166*90c8c64dSAndroid Build Coastguard Worker name = project.getAttribute('name') 167*90c8c64dSAndroid Build Coastguard Worker path = project.getAttribute('path') 168*90c8c64dSAndroid Build Coastguard Worker revision = project.getAttribute('revision') 169*90c8c64dSAndroid Build Coastguard Worker project_dirs.add_directory(name, revision, path) 170*90c8c64dSAndroid Build Coastguard Worker 171*90c8c64dSAndroid Build Coastguard Worker return project_dirs 172*90c8c64dSAndroid Build Coastguard Worker 173*90c8c64dSAndroid Build Coastguard Worker 174*90c8c64dSAndroid Build Coastguard Workerdef group_and_sort_change_lists(change_lists): 175*90c8c64dSAndroid Build Coastguard Worker """Build a dict that maps projects to a list of topologically sorted change 176*90c8c64dSAndroid Build Coastguard Worker lists.""" 177*90c8c64dSAndroid Build Coastguard Worker 178*90c8c64dSAndroid Build Coastguard Worker # Build a dict that map projects to dicts that map commits to changes. 179*90c8c64dSAndroid Build Coastguard Worker projects = collections.defaultdict(dict) 180*90c8c64dSAndroid Build Coastguard Worker for change_list in change_lists: 181*90c8c64dSAndroid Build Coastguard Worker commit_sha1 = None 182*90c8c64dSAndroid Build Coastguard Worker for commit_sha1, value in change_list['revisions'].items(): 183*90c8c64dSAndroid Build Coastguard Worker fetch = value['fetch'] 184*90c8c64dSAndroid Build Coastguard Worker commit = value['commit'] 185*90c8c64dSAndroid Build Coastguard Worker 186*90c8c64dSAndroid Build Coastguard Worker if not commit_sha1: 187*90c8c64dSAndroid Build Coastguard Worker raise ValueError('bad revision') 188*90c8c64dSAndroid Build Coastguard Worker 189*90c8c64dSAndroid Build Coastguard Worker project = change_list['project'] 190*90c8c64dSAndroid Build Coastguard Worker 191*90c8c64dSAndroid Build Coastguard Worker project_changes = projects[project] 192*90c8c64dSAndroid Build Coastguard Worker if commit_sha1 in project_changes: 193*90c8c64dSAndroid Build Coastguard Worker raise KeyError('repeated commit sha1 "{}" in project "{}"'.format( 194*90c8c64dSAndroid Build Coastguard Worker commit_sha1, project)) 195*90c8c64dSAndroid Build Coastguard Worker 196*90c8c64dSAndroid Build Coastguard Worker project_changes[commit_sha1] = ChangeList( 197*90c8c64dSAndroid Build Coastguard Worker project, fetch, commit_sha1, commit, change_list) 198*90c8c64dSAndroid Build Coastguard Worker 199*90c8c64dSAndroid Build Coastguard Worker # Sort all change lists in a project in post ordering. 200*90c8c64dSAndroid Build Coastguard Worker def _sort_project_change_lists(changes): 201*90c8c64dSAndroid Build Coastguard Worker visited_changes = set() 202*90c8c64dSAndroid Build Coastguard Worker sorted_changes = [] 203*90c8c64dSAndroid Build Coastguard Worker 204*90c8c64dSAndroid Build Coastguard Worker def _post_order_traverse(change): 205*90c8c64dSAndroid Build Coastguard Worker visited_changes.add(change) 206*90c8c64dSAndroid Build Coastguard Worker for parent in change.parents: 207*90c8c64dSAndroid Build Coastguard Worker parent_change = changes.get(parent['commit']) 208*90c8c64dSAndroid Build Coastguard Worker if parent_change and parent_change not in visited_changes: 209*90c8c64dSAndroid Build Coastguard Worker _post_order_traverse(parent_change) 210*90c8c64dSAndroid Build Coastguard Worker sorted_changes.append(change) 211*90c8c64dSAndroid Build Coastguard Worker 212*90c8c64dSAndroid Build Coastguard Worker for change in sorted(changes.values(), key=lambda x: x.number): 213*90c8c64dSAndroid Build Coastguard Worker if change not in visited_changes: 214*90c8c64dSAndroid Build Coastguard Worker _post_order_traverse(change) 215*90c8c64dSAndroid Build Coastguard Worker 216*90c8c64dSAndroid Build Coastguard Worker return sorted_changes 217*90c8c64dSAndroid Build Coastguard Worker 218*90c8c64dSAndroid Build Coastguard Worker # Sort changes in each projects 219*90c8c64dSAndroid Build Coastguard Worker sorted_changes = [] 220*90c8c64dSAndroid Build Coastguard Worker for project in sorted(projects.keys()): 221*90c8c64dSAndroid Build Coastguard Worker sorted_changes.append(_sort_project_change_lists(projects[project])) 222*90c8c64dSAndroid Build Coastguard Worker 223*90c8c64dSAndroid Build Coastguard Worker return sorted_changes 224*90c8c64dSAndroid Build Coastguard Worker 225*90c8c64dSAndroid Build Coastguard Worker 226*90c8c64dSAndroid Build Coastguard Workerdef _main_json(args): 227*90c8c64dSAndroid Build Coastguard Worker """Print the change lists in JSON format.""" 228*90c8c64dSAndroid Build Coastguard Worker change_lists = _get_change_lists_from_args(args) 229*90c8c64dSAndroid Build Coastguard Worker json.dump(change_lists, sys.stdout, indent=4, separators=(', ', ': ')) 230*90c8c64dSAndroid Build Coastguard Worker print() # Print the end-of-line 231*90c8c64dSAndroid Build Coastguard Worker 232*90c8c64dSAndroid Build Coastguard Worker 233*90c8c64dSAndroid Build Coastguard Worker# Git commands for merge commits 234*90c8c64dSAndroid Build Coastguard Worker_MERGE_COMMANDS = { 235*90c8c64dSAndroid Build Coastguard Worker 'merge': ['git', 'merge', '--no-edit'], 236*90c8c64dSAndroid Build Coastguard Worker 'merge-ff-only': ['git', 'merge', '--no-edit', '--ff-only'], 237*90c8c64dSAndroid Build Coastguard Worker 'merge-no-ff': ['git', 'merge', '--no-edit', '--no-ff'], 238*90c8c64dSAndroid Build Coastguard Worker 'reset': ['git', 'reset', '--hard'], 239*90c8c64dSAndroid Build Coastguard Worker 'checkout': ['git', 'checkout'], 240*90c8c64dSAndroid Build Coastguard Worker} 241*90c8c64dSAndroid Build Coastguard Worker 242*90c8c64dSAndroid Build Coastguard Worker 243*90c8c64dSAndroid Build Coastguard Worker# Git commands for non-merge commits 244*90c8c64dSAndroid Build Coastguard Worker_PICK_COMMANDS = { 245*90c8c64dSAndroid Build Coastguard Worker 'pick': ['git', 'cherry-pick', '--allow-empty'], 246*90c8c64dSAndroid Build Coastguard Worker 'merge': ['git', 'merge', '--no-edit'], 247*90c8c64dSAndroid Build Coastguard Worker 'merge-ff-only': ['git', 'merge', '--no-edit', '--ff-only'], 248*90c8c64dSAndroid Build Coastguard Worker 'merge-no-ff': ['git', 'merge', '--no-edit', '--no-ff'], 249*90c8c64dSAndroid Build Coastguard Worker 'reset': ['git', 'reset', '--hard'], 250*90c8c64dSAndroid Build Coastguard Worker 'checkout': ['git', 'checkout'], 251*90c8c64dSAndroid Build Coastguard Worker} 252*90c8c64dSAndroid Build Coastguard Worker 253*90c8c64dSAndroid Build Coastguard Worker 254*90c8c64dSAndroid Build Coastguard Workerdef build_pull_commands(change, branch_name, merge_opt, pick_opt): 255*90c8c64dSAndroid Build Coastguard Worker """Build command lines for each change. The command lines will be passed 256*90c8c64dSAndroid Build Coastguard Worker to subprocess.run().""" 257*90c8c64dSAndroid Build Coastguard Worker 258*90c8c64dSAndroid Build Coastguard Worker cmds = [] 259*90c8c64dSAndroid Build Coastguard Worker if branch_name is not None: 260*90c8c64dSAndroid Build Coastguard Worker cmds.append(['repo', 'start', branch_name]) 261*90c8c64dSAndroid Build Coastguard Worker cmds.append(['git', 'fetch', change.fetch_url, change.fetch_ref]) 262*90c8c64dSAndroid Build Coastguard Worker if change.is_merge(): 263*90c8c64dSAndroid Build Coastguard Worker cmds.append(_MERGE_COMMANDS[merge_opt] + ['FETCH_HEAD']) 264*90c8c64dSAndroid Build Coastguard Worker else: 265*90c8c64dSAndroid Build Coastguard Worker cmds.append(_PICK_COMMANDS[pick_opt] + ['FETCH_HEAD']) 266*90c8c64dSAndroid Build Coastguard Worker return cmds 267*90c8c64dSAndroid Build Coastguard Worker 268*90c8c64dSAndroid Build Coastguard Worker 269*90c8c64dSAndroid Build Coastguard Workerdef _sh_quote_command(cmd): 270*90c8c64dSAndroid Build Coastguard Worker """Convert a command (an argument to subprocess.run()) to a shell command 271*90c8c64dSAndroid Build Coastguard Worker string.""" 272*90c8c64dSAndroid Build Coastguard Worker return ' '.join(_sh_quote(x) for x in cmd) 273*90c8c64dSAndroid Build Coastguard Worker 274*90c8c64dSAndroid Build Coastguard Worker 275*90c8c64dSAndroid Build Coastguard Workerdef _sh_quote_commands(cmds): 276*90c8c64dSAndroid Build Coastguard Worker """Convert multiple commands (arguments to subprocess.run()) to shell 277*90c8c64dSAndroid Build Coastguard Worker command strings.""" 278*90c8c64dSAndroid Build Coastguard Worker return ' && '.join(_sh_quote_command(cmd) for cmd in cmds) 279*90c8c64dSAndroid Build Coastguard Worker 280*90c8c64dSAndroid Build Coastguard Worker 281*90c8c64dSAndroid Build Coastguard Workerdef _main_bash(args): 282*90c8c64dSAndroid Build Coastguard Worker """Print the bash command to pull the change lists.""" 283*90c8c64dSAndroid Build Coastguard Worker repo_top = find_repo_top(os.getcwd()) 284*90c8c64dSAndroid Build Coastguard Worker project_dirs = build_project_name_dir_dict(args.manifest) 285*90c8c64dSAndroid Build Coastguard Worker branch_name = _get_local_branch_name_from_args(args) 286*90c8c64dSAndroid Build Coastguard Worker 287*90c8c64dSAndroid Build Coastguard Worker change_lists = _get_change_lists_from_args(args) 288*90c8c64dSAndroid Build Coastguard Worker change_list_groups = group_and_sort_change_lists(change_lists) 289*90c8c64dSAndroid Build Coastguard Worker 290*90c8c64dSAndroid Build Coastguard Worker print(_sh_quote_command(['pushd', repo_top])) 291*90c8c64dSAndroid Build Coastguard Worker for changes in change_list_groups: 292*90c8c64dSAndroid Build Coastguard Worker for change in changes: 293*90c8c64dSAndroid Build Coastguard Worker project_dir = project_dirs.find_directory( 294*90c8c64dSAndroid Build Coastguard Worker change.project, change.branch, change.project) 295*90c8c64dSAndroid Build Coastguard Worker cmds = [] 296*90c8c64dSAndroid Build Coastguard Worker cmds.append(['pushd', project_dir]) 297*90c8c64dSAndroid Build Coastguard Worker cmds.extend(build_pull_commands( 298*90c8c64dSAndroid Build Coastguard Worker change, branch_name, args.merge, args.pick)) 299*90c8c64dSAndroid Build Coastguard Worker cmds.append(['popd']) 300*90c8c64dSAndroid Build Coastguard Worker print(_sh_quote_commands(cmds)) 301*90c8c64dSAndroid Build Coastguard Worker print(_sh_quote_command(['popd'])) 302*90c8c64dSAndroid Build Coastguard Worker 303*90c8c64dSAndroid Build Coastguard Worker 304*90c8c64dSAndroid Build Coastguard Workerdef _do_pull_change_lists_for_project(task, ignore_unknown_changes): 305*90c8c64dSAndroid Build Coastguard Worker """Pick a list of changes (usually under a project directory).""" 306*90c8c64dSAndroid Build Coastguard Worker changes, task_opts = task 307*90c8c64dSAndroid Build Coastguard Worker 308*90c8c64dSAndroid Build Coastguard Worker branch_name = task_opts['branch_name'] 309*90c8c64dSAndroid Build Coastguard Worker merge_opt = task_opts['merge_opt'] 310*90c8c64dSAndroid Build Coastguard Worker pick_opt = task_opts['pick_opt'] 311*90c8c64dSAndroid Build Coastguard Worker project_dirs = task_opts['project_dirs'] 312*90c8c64dSAndroid Build Coastguard Worker repo_top = task_opts['repo_top'] 313*90c8c64dSAndroid Build Coastguard Worker 314*90c8c64dSAndroid Build Coastguard Worker for i, change in enumerate(changes): 315*90c8c64dSAndroid Build Coastguard Worker try: 316*90c8c64dSAndroid Build Coastguard Worker cwd = project_dirs.find_directory(change.project, change.branch) 317*90c8c64dSAndroid Build Coastguard Worker except KeyError: 318*90c8c64dSAndroid Build Coastguard Worker err_msg = 'error: project "{}" cannot be found in manifest.xml\n' 319*90c8c64dSAndroid Build Coastguard Worker err_msg = err_msg.format(change.project).encode('utf-8') 320*90c8c64dSAndroid Build Coastguard Worker if ignore_unknown_changes: 321*90c8c64dSAndroid Build Coastguard Worker print(err_msg) 322*90c8c64dSAndroid Build Coastguard Worker continue 323*90c8c64dSAndroid Build Coastguard Worker return (change, changes[i + 1:], [], err_msg) 324*90c8c64dSAndroid Build Coastguard Worker 325*90c8c64dSAndroid Build Coastguard Worker print(change.commit_sha1[0:10], i + 1, cwd) 326*90c8c64dSAndroid Build Coastguard Worker cmds = build_pull_commands(change, branch_name, merge_opt, pick_opt) 327*90c8c64dSAndroid Build Coastguard Worker for cmd in cmds: 328*90c8c64dSAndroid Build Coastguard Worker proc = run(cmd, cwd=os.path.join(repo_top, cwd), stderr=PIPE) 329*90c8c64dSAndroid Build Coastguard Worker if proc.returncode != 0: 330*90c8c64dSAndroid Build Coastguard Worker return (change, changes[i + 1:], cmd, proc.stderr) 331*90c8c64dSAndroid Build Coastguard Worker return None 332*90c8c64dSAndroid Build Coastguard Worker 333*90c8c64dSAndroid Build Coastguard Worker 334*90c8c64dSAndroid Build Coastguard Workerdef _print_pull_failures(failures, file=sys.stderr): 335*90c8c64dSAndroid Build Coastguard Worker """Print pull failures and tracebacks.""" 336*90c8c64dSAndroid Build Coastguard Worker # pylint: disable=redefined-builtin 337*90c8c64dSAndroid Build Coastguard Worker 338*90c8c64dSAndroid Build Coastguard Worker separator = '=' * 78 339*90c8c64dSAndroid Build Coastguard Worker separator_sub = '-' * 78 340*90c8c64dSAndroid Build Coastguard Worker 341*90c8c64dSAndroid Build Coastguard Worker print(separator, file=file) 342*90c8c64dSAndroid Build Coastguard Worker for failed_change, skipped_changes, cmd, errors in failures: 343*90c8c64dSAndroid Build Coastguard Worker print('PROJECT:', failed_change.project, file=file) 344*90c8c64dSAndroid Build Coastguard Worker print('FAILED COMMIT:', failed_change.commit_sha1, file=file) 345*90c8c64dSAndroid Build Coastguard Worker for change in skipped_changes: 346*90c8c64dSAndroid Build Coastguard Worker print('PENDING COMMIT:', change.commit_sha1, file=file) 347*90c8c64dSAndroid Build Coastguard Worker print(separator_sub, file=sys.stderr) 348*90c8c64dSAndroid Build Coastguard Worker print('FAILED COMMAND:', _sh_quote_command(cmd), file=file) 349*90c8c64dSAndroid Build Coastguard Worker write_bytes(errors, file=sys.stderr) 350*90c8c64dSAndroid Build Coastguard Worker print(separator, file=sys.stderr) 351*90c8c64dSAndroid Build Coastguard Worker 352*90c8c64dSAndroid Build Coastguard Worker 353*90c8c64dSAndroid Build Coastguard Workerdef _main_pull(args): 354*90c8c64dSAndroid Build Coastguard Worker """Pull the change lists.""" 355*90c8c64dSAndroid Build Coastguard Worker repo_top = find_repo_top(os.getcwd()) 356*90c8c64dSAndroid Build Coastguard Worker project_dirs = build_project_name_dir_dict(args.manifest) 357*90c8c64dSAndroid Build Coastguard Worker branch_name = _get_local_branch_name_from_args(args) 358*90c8c64dSAndroid Build Coastguard Worker 359*90c8c64dSAndroid Build Coastguard Worker # Collect change lists 360*90c8c64dSAndroid Build Coastguard Worker change_lists = _get_change_lists_from_args(args) 361*90c8c64dSAndroid Build Coastguard Worker change_list_groups = group_and_sort_change_lists(change_lists) 362*90c8c64dSAndroid Build Coastguard Worker 363*90c8c64dSAndroid Build Coastguard Worker # Build the options list for tasks 364*90c8c64dSAndroid Build Coastguard Worker task_opts = { 365*90c8c64dSAndroid Build Coastguard Worker 'branch_name': branch_name, 366*90c8c64dSAndroid Build Coastguard Worker 'merge_opt': args.merge, 367*90c8c64dSAndroid Build Coastguard Worker 'pick_opt': args.pick, 368*90c8c64dSAndroid Build Coastguard Worker 'project_dirs': project_dirs, 369*90c8c64dSAndroid Build Coastguard Worker 'repo_top': repo_top, 370*90c8c64dSAndroid Build Coastguard Worker } 371*90c8c64dSAndroid Build Coastguard Worker 372*90c8c64dSAndroid Build Coastguard Worker # Run the commands to pull the change lists 373*90c8c64dSAndroid Build Coastguard Worker if args.parallel <= 1: 374*90c8c64dSAndroid Build Coastguard Worker results = [_do_pull_change_lists_for_project( 375*90c8c64dSAndroid Build Coastguard Worker (changes, task_opts), args.ignore_unknown_changes) 376*90c8c64dSAndroid Build Coastguard Worker for changes in change_list_groups] 377*90c8c64dSAndroid Build Coastguard Worker else: 378*90c8c64dSAndroid Build Coastguard Worker pool = multiprocessing.Pool(processes=args.parallel) 379*90c8c64dSAndroid Build Coastguard Worker results = pool.map(_do_pull_change_lists_for_project, 380*90c8c64dSAndroid Build Coastguard Worker zip(change_list_groups, itertools.repeat(task_opts)), 381*90c8c64dSAndroid Build Coastguard Worker args.ignore_unknown_changes) 382*90c8c64dSAndroid Build Coastguard Worker 383*90c8c64dSAndroid Build Coastguard Worker # Print failures and tracebacks 384*90c8c64dSAndroid Build Coastguard Worker failures = [result for result in results if result] 385*90c8c64dSAndroid Build Coastguard Worker if failures: 386*90c8c64dSAndroid Build Coastguard Worker _print_pull_failures(failures) 387*90c8c64dSAndroid Build Coastguard Worker sys.exit(1) 388*90c8c64dSAndroid Build Coastguard Worker 389*90c8c64dSAndroid Build Coastguard Worker 390*90c8c64dSAndroid Build Coastguard Workerdef _parse_args(): 391*90c8c64dSAndroid Build Coastguard Worker """Parse command line options.""" 392*90c8c64dSAndroid Build Coastguard Worker parser = argparse.ArgumentParser() 393*90c8c64dSAndroid Build Coastguard Worker 394*90c8c64dSAndroid Build Coastguard Worker parser.add_argument('command', choices=['pull', 'bash', 'json'], 395*90c8c64dSAndroid Build Coastguard Worker help='Commands') 396*90c8c64dSAndroid Build Coastguard Worker add_common_parse_args(parser) 397*90c8c64dSAndroid Build Coastguard Worker 398*90c8c64dSAndroid Build Coastguard Worker 399*90c8c64dSAndroid Build Coastguard Worker parser.add_argument('--manifest', help='Manifest') 400*90c8c64dSAndroid Build Coastguard Worker 401*90c8c64dSAndroid Build Coastguard Worker parser.add_argument('-m', '--merge', 402*90c8c64dSAndroid Build Coastguard Worker choices=sorted(_MERGE_COMMANDS.keys()), 403*90c8c64dSAndroid Build Coastguard Worker default='merge-ff-only', 404*90c8c64dSAndroid Build Coastguard Worker help='Method to pull merge commits') 405*90c8c64dSAndroid Build Coastguard Worker 406*90c8c64dSAndroid Build Coastguard Worker parser.add_argument('-p', '--pick', 407*90c8c64dSAndroid Build Coastguard Worker choices=sorted(_PICK_COMMANDS.keys()), 408*90c8c64dSAndroid Build Coastguard Worker default='pick', 409*90c8c64dSAndroid Build Coastguard Worker help='Method to pull merge commits') 410*90c8c64dSAndroid Build Coastguard Worker 411*90c8c64dSAndroid Build Coastguard Worker parser.add_argument('-b', '--branch', 412*90c8c64dSAndroid Build Coastguard Worker help='Local branch name for `repo start`') 413*90c8c64dSAndroid Build Coastguard Worker 414*90c8c64dSAndroid Build Coastguard Worker parser.add_argument('-j', '--parallel', default=1, type=int, 415*90c8c64dSAndroid Build Coastguard Worker help='Number of parallel running commands') 416*90c8c64dSAndroid Build Coastguard Worker 417*90c8c64dSAndroid Build Coastguard Worker parser.add_argument('--current-branch', action='store_true', 418*90c8c64dSAndroid Build Coastguard Worker help='Pull commits to the current branch') 419*90c8c64dSAndroid Build Coastguard Worker 420*90c8c64dSAndroid Build Coastguard Worker parser.add_argument('--ignore-unknown-changes', action='store_true', 421*90c8c64dSAndroid Build Coastguard Worker help='Ignore changes whose repo is not in the manifest') 422*90c8c64dSAndroid Build Coastguard Worker 423*90c8c64dSAndroid Build Coastguard Worker return parser.parse_args() 424*90c8c64dSAndroid Build Coastguard Worker 425*90c8c64dSAndroid Build Coastguard Worker 426*90c8c64dSAndroid Build Coastguard Workerdef _get_change_lists_from_args(args): 427*90c8c64dSAndroid Build Coastguard Worker """Query the change lists by args.""" 428*90c8c64dSAndroid Build Coastguard Worker url_opener = create_url_opener_from_args(args) 429*90c8c64dSAndroid Build Coastguard Worker return query_change_lists(url_opener, args.gerrit, args.query, args.start, 430*90c8c64dSAndroid Build Coastguard Worker args.limits) 431*90c8c64dSAndroid Build Coastguard Worker 432*90c8c64dSAndroid Build Coastguard Worker 433*90c8c64dSAndroid Build Coastguard Workerdef _get_local_branch_name_from_args(args): 434*90c8c64dSAndroid Build Coastguard Worker """Get the local branch name from args.""" 435*90c8c64dSAndroid Build Coastguard Worker if not args.branch and not args.current_branch and not _confirm( 436*90c8c64dSAndroid Build Coastguard Worker 'Do you want to continue without local branch name?', False): 437*90c8c64dSAndroid Build Coastguard Worker print('error: `-b` or `--branch` must be specified', file=sys.stderr) 438*90c8c64dSAndroid Build Coastguard Worker sys.exit(1) 439*90c8c64dSAndroid Build Coastguard Worker return args.branch 440*90c8c64dSAndroid Build Coastguard Worker 441*90c8c64dSAndroid Build Coastguard Worker 442*90c8c64dSAndroid Build Coastguard Workerdef main(): 443*90c8c64dSAndroid Build Coastguard Worker """Main function""" 444*90c8c64dSAndroid Build Coastguard Worker args = _parse_args() 445*90c8c64dSAndroid Build Coastguard Worker 446*90c8c64dSAndroid Build Coastguard Worker if args.gerrit: 447*90c8c64dSAndroid Build Coastguard Worker args.gerrit = normalize_gerrit_name(args.gerrit) 448*90c8c64dSAndroid Build Coastguard Worker else: 449*90c8c64dSAndroid Build Coastguard Worker try: 450*90c8c64dSAndroid Build Coastguard Worker args.gerrit = find_gerrit_name() 451*90c8c64dSAndroid Build Coastguard Worker # pylint: disable=bare-except 452*90c8c64dSAndroid Build Coastguard Worker except: 453*90c8c64dSAndroid Build Coastguard Worker print('gerrit instance not found, use [-g GERRIT]') 454*90c8c64dSAndroid Build Coastguard Worker sys.exit(1) 455*90c8c64dSAndroid Build Coastguard Worker 456*90c8c64dSAndroid Build Coastguard Worker if args.command == 'json': 457*90c8c64dSAndroid Build Coastguard Worker _main_json(args) 458*90c8c64dSAndroid Build Coastguard Worker elif args.command == 'bash': 459*90c8c64dSAndroid Build Coastguard Worker _main_bash(args) 460*90c8c64dSAndroid Build Coastguard Worker elif args.command == 'pull': 461*90c8c64dSAndroid Build Coastguard Worker _main_pull(args) 462*90c8c64dSAndroid Build Coastguard Worker else: 463*90c8c64dSAndroid Build Coastguard Worker raise KeyError('unknown command') 464*90c8c64dSAndroid Build Coastguard Worker 465*90c8c64dSAndroid Build Coastguard Workerif __name__ == '__main__': 466*90c8c64dSAndroid Build Coastguard Worker main() 467