1*d68f33bcSAndroid Build Coastguard Worker#!/usr/bin/env python3 2*d68f33bcSAndroid Build Coastguard Worker# Copyright 2016 The Android Open Source Project 3*d68f33bcSAndroid Build Coastguard Worker# 4*d68f33bcSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 5*d68f33bcSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 6*d68f33bcSAndroid Build Coastguard Worker# You may obtain a copy of the License at 7*d68f33bcSAndroid Build Coastguard Worker# 8*d68f33bcSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 9*d68f33bcSAndroid Build Coastguard Worker# 10*d68f33bcSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 11*d68f33bcSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 12*d68f33bcSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*d68f33bcSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 14*d68f33bcSAndroid Build Coastguard Worker# limitations under the License. 15*d68f33bcSAndroid Build Coastguard Worker 16*d68f33bcSAndroid Build Coastguard Worker"""Repo pre-upload hook. 17*d68f33bcSAndroid Build Coastguard Worker 18*d68f33bcSAndroid Build Coastguard WorkerNormally this is loaded indirectly by repo itself, but it can be run directly 19*d68f33bcSAndroid Build Coastguard Workerwhen developing. 20*d68f33bcSAndroid Build Coastguard Worker""" 21*d68f33bcSAndroid Build Coastguard Worker 22*d68f33bcSAndroid Build Coastguard Workerimport argparse 23*d68f33bcSAndroid Build Coastguard Workerimport concurrent.futures 24*d68f33bcSAndroid Build Coastguard Workerimport datetime 25*d68f33bcSAndroid Build Coastguard Workerimport os 26*d68f33bcSAndroid Build Coastguard Workerimport signal 27*d68f33bcSAndroid Build Coastguard Workerimport sys 28*d68f33bcSAndroid Build Coastguard Workerfrom typing import List, Optional 29*d68f33bcSAndroid Build Coastguard Worker 30*d68f33bcSAndroid Build Coastguard Worker 31*d68f33bcSAndroid Build Coastguard Worker# Assert some minimum Python versions as we don't test or support any others. 32*d68f33bcSAndroid Build Coastguard Workerif sys.version_info < (3, 6): 33*d68f33bcSAndroid Build Coastguard Worker print('repohooks: error: Python-3.6+ is required', file=sys.stderr) 34*d68f33bcSAndroid Build Coastguard Worker sys.exit(1) 35*d68f33bcSAndroid Build Coastguard Worker 36*d68f33bcSAndroid Build Coastguard Worker 37*d68f33bcSAndroid Build Coastguard Worker_path = os.path.dirname(os.path.realpath(__file__)) 38*d68f33bcSAndroid Build Coastguard Workerif sys.path[0] != _path: 39*d68f33bcSAndroid Build Coastguard Worker sys.path.insert(0, _path) 40*d68f33bcSAndroid Build Coastguard Workerdel _path 41*d68f33bcSAndroid Build Coastguard Worker 42*d68f33bcSAndroid Build Coastguard Worker# We have to import our local modules after the sys.path tweak. We can't use 43*d68f33bcSAndroid Build Coastguard Worker# relative imports because this is an executable program, not a module. 44*d68f33bcSAndroid Build Coastguard Worker# pylint: disable=wrong-import-position 45*d68f33bcSAndroid Build Coastguard Workerimport rh 46*d68f33bcSAndroid Build Coastguard Workerimport rh.results 47*d68f33bcSAndroid Build Coastguard Workerimport rh.config 48*d68f33bcSAndroid Build Coastguard Workerimport rh.git 49*d68f33bcSAndroid Build Coastguard Workerimport rh.hooks 50*d68f33bcSAndroid Build Coastguard Workerimport rh.terminal 51*d68f33bcSAndroid Build Coastguard Workerimport rh.utils 52*d68f33bcSAndroid Build Coastguard Worker 53*d68f33bcSAndroid Build Coastguard Worker 54*d68f33bcSAndroid Build Coastguard Worker# Repohooks homepage. 55*d68f33bcSAndroid Build Coastguard WorkerREPOHOOKS_URL = 'https://android.googlesource.com/platform/tools/repohooks/' 56*d68f33bcSAndroid Build Coastguard Worker 57*d68f33bcSAndroid Build Coastguard Worker 58*d68f33bcSAndroid Build Coastguard Workerclass Output(object): 59*d68f33bcSAndroid Build Coastguard Worker """Class for reporting hook status.""" 60*d68f33bcSAndroid Build Coastguard Worker 61*d68f33bcSAndroid Build Coastguard Worker COLOR = rh.terminal.Color() 62*d68f33bcSAndroid Build Coastguard Worker COMMIT = COLOR.color(COLOR.CYAN, 'COMMIT') 63*d68f33bcSAndroid Build Coastguard Worker RUNNING = COLOR.color(COLOR.YELLOW, 'RUNNING') 64*d68f33bcSAndroid Build Coastguard Worker PASSED = COLOR.color(COLOR.GREEN, 'PASSED') 65*d68f33bcSAndroid Build Coastguard Worker FAILED = COLOR.color(COLOR.RED, 'FAILED') 66*d68f33bcSAndroid Build Coastguard Worker WARNING = COLOR.color(COLOR.YELLOW, 'WARNING') 67*d68f33bcSAndroid Build Coastguard Worker FIXUP = COLOR.color(COLOR.MAGENTA, 'FIXUP') 68*d68f33bcSAndroid Build Coastguard Worker 69*d68f33bcSAndroid Build Coastguard Worker # How long a hook is allowed to run before we warn that it is "too slow". 70*d68f33bcSAndroid Build Coastguard Worker _SLOW_HOOK_DURATION = datetime.timedelta(seconds=30) 71*d68f33bcSAndroid Build Coastguard Worker 72*d68f33bcSAndroid Build Coastguard Worker def __init__(self, project_name): 73*d68f33bcSAndroid Build Coastguard Worker """Create a new Output object for a specified project. 74*d68f33bcSAndroid Build Coastguard Worker 75*d68f33bcSAndroid Build Coastguard Worker Args: 76*d68f33bcSAndroid Build Coastguard Worker project_name: name of project. 77*d68f33bcSAndroid Build Coastguard Worker """ 78*d68f33bcSAndroid Build Coastguard Worker self.project_name = project_name 79*d68f33bcSAndroid Build Coastguard Worker self.hooks = None 80*d68f33bcSAndroid Build Coastguard Worker self.num_hooks = None 81*d68f33bcSAndroid Build Coastguard Worker self.num_commits = None 82*d68f33bcSAndroid Build Coastguard Worker self.commit_index = 0 83*d68f33bcSAndroid Build Coastguard Worker self.success = True 84*d68f33bcSAndroid Build Coastguard Worker self.start_time = datetime.datetime.now() 85*d68f33bcSAndroid Build Coastguard Worker self.hook_start_time = None 86*d68f33bcSAndroid Build Coastguard Worker # Cache number of invisible characters in our banner. 87*d68f33bcSAndroid Build Coastguard Worker self._banner_esc_chars = len(self.COLOR.color(self.COLOR.YELLOW, '')) 88*d68f33bcSAndroid Build Coastguard Worker 89*d68f33bcSAndroid Build Coastguard Worker def set_num_commits(self, num_commits: int) -> None: 90*d68f33bcSAndroid Build Coastguard Worker """Keep track of how many commits we'll be running. 91*d68f33bcSAndroid Build Coastguard Worker 92*d68f33bcSAndroid Build Coastguard Worker Args: 93*d68f33bcSAndroid Build Coastguard Worker num_commits: Number of commits to be run. 94*d68f33bcSAndroid Build Coastguard Worker """ 95*d68f33bcSAndroid Build Coastguard Worker self.num_commits = num_commits 96*d68f33bcSAndroid Build Coastguard Worker self.commit_index = 1 97*d68f33bcSAndroid Build Coastguard Worker 98*d68f33bcSAndroid Build Coastguard Worker def commit_start(self, hooks, commit, commit_summary): 99*d68f33bcSAndroid Build Coastguard Worker """Emit status for new commit. 100*d68f33bcSAndroid Build Coastguard Worker 101*d68f33bcSAndroid Build Coastguard Worker Args: 102*d68f33bcSAndroid Build Coastguard Worker hooks: All the hooks to be run for this commit. 103*d68f33bcSAndroid Build Coastguard Worker commit: commit hash. 104*d68f33bcSAndroid Build Coastguard Worker commit_summary: commit summary. 105*d68f33bcSAndroid Build Coastguard Worker """ 106*d68f33bcSAndroid Build Coastguard Worker status_line = ( 107*d68f33bcSAndroid Build Coastguard Worker f'[{self.COMMIT} ' 108*d68f33bcSAndroid Build Coastguard Worker f'{self.commit_index}/{self.num_commits} ' 109*d68f33bcSAndroid Build Coastguard Worker f'{commit[0:12]}] {commit_summary}' 110*d68f33bcSAndroid Build Coastguard Worker ) 111*d68f33bcSAndroid Build Coastguard Worker rh.terminal.print_status_line(status_line, print_newline=True) 112*d68f33bcSAndroid Build Coastguard Worker self.commit_index += 1 113*d68f33bcSAndroid Build Coastguard Worker 114*d68f33bcSAndroid Build Coastguard Worker # Initialize the pending hooks line too. 115*d68f33bcSAndroid Build Coastguard Worker self.hooks = set(hooks) 116*d68f33bcSAndroid Build Coastguard Worker self.num_hooks = len(hooks) 117*d68f33bcSAndroid Build Coastguard Worker self.hook_banner() 118*d68f33bcSAndroid Build Coastguard Worker 119*d68f33bcSAndroid Build Coastguard Worker def hook_banner(self): 120*d68f33bcSAndroid Build Coastguard Worker """Display the banner for current set of hooks.""" 121*d68f33bcSAndroid Build Coastguard Worker pending = ', '.join(x.name for x in self.hooks) 122*d68f33bcSAndroid Build Coastguard Worker status_line = ( 123*d68f33bcSAndroid Build Coastguard Worker f'[{self.RUNNING} ' 124*d68f33bcSAndroid Build Coastguard Worker f'{self.num_hooks - len(self.hooks)}/{self.num_hooks}] ' 125*d68f33bcSAndroid Build Coastguard Worker f'{pending}' 126*d68f33bcSAndroid Build Coastguard Worker ) 127*d68f33bcSAndroid Build Coastguard Worker if self._banner_esc_chars and sys.stderr.isatty(): 128*d68f33bcSAndroid Build Coastguard Worker cols = os.get_terminal_size(sys.stderr.fileno()).columns 129*d68f33bcSAndroid Build Coastguard Worker status_line = status_line[0:cols + self._banner_esc_chars] 130*d68f33bcSAndroid Build Coastguard Worker rh.terminal.print_status_line(status_line) 131*d68f33bcSAndroid Build Coastguard Worker 132*d68f33bcSAndroid Build Coastguard Worker def hook_finish(self, hook, duration): 133*d68f33bcSAndroid Build Coastguard Worker """Finish processing any per-hook state.""" 134*d68f33bcSAndroid Build Coastguard Worker self.hooks.remove(hook) 135*d68f33bcSAndroid Build Coastguard Worker if duration >= self._SLOW_HOOK_DURATION: 136*d68f33bcSAndroid Build Coastguard Worker d = rh.utils.timedelta_str(duration) 137*d68f33bcSAndroid Build Coastguard Worker self.hook_warning( 138*d68f33bcSAndroid Build Coastguard Worker hook, 139*d68f33bcSAndroid Build Coastguard Worker f'This hook took {d} to finish which is fairly slow for ' 140*d68f33bcSAndroid Build Coastguard Worker 'developers.\nPlease consider moving the check to the ' 141*d68f33bcSAndroid Build Coastguard Worker 'server/CI system instead.') 142*d68f33bcSAndroid Build Coastguard Worker 143*d68f33bcSAndroid Build Coastguard Worker # Show any hooks still pending. 144*d68f33bcSAndroid Build Coastguard Worker if self.hooks: 145*d68f33bcSAndroid Build Coastguard Worker self.hook_banner() 146*d68f33bcSAndroid Build Coastguard Worker 147*d68f33bcSAndroid Build Coastguard Worker def hook_error(self, hook, error): 148*d68f33bcSAndroid Build Coastguard Worker """Print an error for a single hook. 149*d68f33bcSAndroid Build Coastguard Worker 150*d68f33bcSAndroid Build Coastguard Worker Args: 151*d68f33bcSAndroid Build Coastguard Worker hook: The hook that generated the output. 152*d68f33bcSAndroid Build Coastguard Worker error: error string. 153*d68f33bcSAndroid Build Coastguard Worker """ 154*d68f33bcSAndroid Build Coastguard Worker self.error(f'{hook.name} hook', error) 155*d68f33bcSAndroid Build Coastguard Worker 156*d68f33bcSAndroid Build Coastguard Worker def hook_warning(self, hook, warning): 157*d68f33bcSAndroid Build Coastguard Worker """Print a warning for a single hook. 158*d68f33bcSAndroid Build Coastguard Worker 159*d68f33bcSAndroid Build Coastguard Worker Args: 160*d68f33bcSAndroid Build Coastguard Worker hook: The hook that generated the output. 161*d68f33bcSAndroid Build Coastguard Worker warning: warning string. 162*d68f33bcSAndroid Build Coastguard Worker """ 163*d68f33bcSAndroid Build Coastguard Worker status_line = f'[{self.WARNING}] {hook.name}' 164*d68f33bcSAndroid Build Coastguard Worker rh.terminal.print_status_line(status_line, print_newline=True) 165*d68f33bcSAndroid Build Coastguard Worker print(warning, file=sys.stderr) 166*d68f33bcSAndroid Build Coastguard Worker 167*d68f33bcSAndroid Build Coastguard Worker def error(self, header, error): 168*d68f33bcSAndroid Build Coastguard Worker """Print a general error. 169*d68f33bcSAndroid Build Coastguard Worker 170*d68f33bcSAndroid Build Coastguard Worker Args: 171*d68f33bcSAndroid Build Coastguard Worker header: A unique identifier for the source of this error. 172*d68f33bcSAndroid Build Coastguard Worker error: error string. 173*d68f33bcSAndroid Build Coastguard Worker """ 174*d68f33bcSAndroid Build Coastguard Worker status_line = f'[{self.FAILED}] {header}' 175*d68f33bcSAndroid Build Coastguard Worker rh.terminal.print_status_line(status_line, print_newline=True) 176*d68f33bcSAndroid Build Coastguard Worker print(error, file=sys.stderr) 177*d68f33bcSAndroid Build Coastguard Worker self.success = False 178*d68f33bcSAndroid Build Coastguard Worker 179*d68f33bcSAndroid Build Coastguard Worker def hook_fixups( 180*d68f33bcSAndroid Build Coastguard Worker self, 181*d68f33bcSAndroid Build Coastguard Worker project_results: rh.results.ProjectResults, 182*d68f33bcSAndroid Build Coastguard Worker hook_results: List[rh.results.HookResult], 183*d68f33bcSAndroid Build Coastguard Worker ) -> None: 184*d68f33bcSAndroid Build Coastguard Worker """Display summary of possible fixups for a single hook.""" 185*d68f33bcSAndroid Build Coastguard Worker for result in (x for x in hook_results if x.fixup_cmd): 186*d68f33bcSAndroid Build Coastguard Worker cmd = result.fixup_cmd + list(result.files) 187*d68f33bcSAndroid Build Coastguard Worker for line in ( 188*d68f33bcSAndroid Build Coastguard Worker f'[{self.FIXUP}] {result.hook} has automated fixups available', 189*d68f33bcSAndroid Build Coastguard Worker f' cd {rh.shell.quote(project_results.workdir)} && \\', 190*d68f33bcSAndroid Build Coastguard Worker f' {rh.shell.cmd_to_str(cmd)}', 191*d68f33bcSAndroid Build Coastguard Worker ): 192*d68f33bcSAndroid Build Coastguard Worker rh.terminal.print_status_line(line, print_newline=True) 193*d68f33bcSAndroid Build Coastguard Worker 194*d68f33bcSAndroid Build Coastguard Worker def finish(self): 195*d68f33bcSAndroid Build Coastguard Worker """Print summary for all the hooks.""" 196*d68f33bcSAndroid Build Coastguard Worker header = self.PASSED if self.success else self.FAILED 197*d68f33bcSAndroid Build Coastguard Worker status = 'passed' if self.success else 'failed' 198*d68f33bcSAndroid Build Coastguard Worker d = rh.utils.timedelta_str(datetime.datetime.now() - self.start_time) 199*d68f33bcSAndroid Build Coastguard Worker rh.terminal.print_status_line( 200*d68f33bcSAndroid Build Coastguard Worker f'[{header}] repohooks for {self.project_name} {status} in {d}', 201*d68f33bcSAndroid Build Coastguard Worker print_newline=True) 202*d68f33bcSAndroid Build Coastguard Worker 203*d68f33bcSAndroid Build Coastguard Worker 204*d68f33bcSAndroid Build Coastguard Workerdef _process_hook_results(results): 205*d68f33bcSAndroid Build Coastguard Worker """Returns an error string if an error occurred. 206*d68f33bcSAndroid Build Coastguard Worker 207*d68f33bcSAndroid Build Coastguard Worker Args: 208*d68f33bcSAndroid Build Coastguard Worker results: A list of HookResult objects, or None. 209*d68f33bcSAndroid Build Coastguard Worker 210*d68f33bcSAndroid Build Coastguard Worker Returns: 211*d68f33bcSAndroid Build Coastguard Worker error output if an error occurred, otherwise None 212*d68f33bcSAndroid Build Coastguard Worker warning output if an error occurred, otherwise None 213*d68f33bcSAndroid Build Coastguard Worker """ 214*d68f33bcSAndroid Build Coastguard Worker if not results: 215*d68f33bcSAndroid Build Coastguard Worker return (None, None) 216*d68f33bcSAndroid Build Coastguard Worker 217*d68f33bcSAndroid Build Coastguard Worker # We track these as dedicated fields in case a hook doesn't output anything. 218*d68f33bcSAndroid Build Coastguard Worker # We want to treat silent non-zero exits as failures too. 219*d68f33bcSAndroid Build Coastguard Worker has_error = False 220*d68f33bcSAndroid Build Coastguard Worker has_warning = False 221*d68f33bcSAndroid Build Coastguard Worker 222*d68f33bcSAndroid Build Coastguard Worker error_ret = '' 223*d68f33bcSAndroid Build Coastguard Worker warning_ret = '' 224*d68f33bcSAndroid Build Coastguard Worker for result in results: 225*d68f33bcSAndroid Build Coastguard Worker if result or result.is_warning(): 226*d68f33bcSAndroid Build Coastguard Worker ret = '' 227*d68f33bcSAndroid Build Coastguard Worker if result.files: 228*d68f33bcSAndroid Build Coastguard Worker ret += f' FILES: {rh.shell.cmd_to_str(result.files)}\n' 229*d68f33bcSAndroid Build Coastguard Worker lines = result.error.splitlines() 230*d68f33bcSAndroid Build Coastguard Worker ret += '\n'.join(f' {x}' for x in lines) 231*d68f33bcSAndroid Build Coastguard Worker if result.is_warning(): 232*d68f33bcSAndroid Build Coastguard Worker has_warning = True 233*d68f33bcSAndroid Build Coastguard Worker warning_ret += ret 234*d68f33bcSAndroid Build Coastguard Worker else: 235*d68f33bcSAndroid Build Coastguard Worker has_error = True 236*d68f33bcSAndroid Build Coastguard Worker error_ret += ret 237*d68f33bcSAndroid Build Coastguard Worker 238*d68f33bcSAndroid Build Coastguard Worker return (error_ret if has_error else None, 239*d68f33bcSAndroid Build Coastguard Worker warning_ret if has_warning else None) 240*d68f33bcSAndroid Build Coastguard Worker 241*d68f33bcSAndroid Build Coastguard Worker 242*d68f33bcSAndroid Build Coastguard Workerdef _get_project_config(from_git=False): 243*d68f33bcSAndroid Build Coastguard Worker """Returns the configuration for a project. 244*d68f33bcSAndroid Build Coastguard Worker 245*d68f33bcSAndroid Build Coastguard Worker Args: 246*d68f33bcSAndroid Build Coastguard Worker from_git: If true, we are called from git directly and repo should not be 247*d68f33bcSAndroid Build Coastguard Worker used. 248*d68f33bcSAndroid Build Coastguard Worker Expects to be called from within the project root. 249*d68f33bcSAndroid Build Coastguard Worker """ 250*d68f33bcSAndroid Build Coastguard Worker if from_git: 251*d68f33bcSAndroid Build Coastguard Worker global_paths = (rh.git.find_repo_root(),) 252*d68f33bcSAndroid Build Coastguard Worker else: 253*d68f33bcSAndroid Build Coastguard Worker global_paths = ( 254*d68f33bcSAndroid Build Coastguard Worker # Load the global config found in the manifest repo. 255*d68f33bcSAndroid Build Coastguard Worker (os.path.join(rh.git.find_repo_root(), '.repo', 'manifests')), 256*d68f33bcSAndroid Build Coastguard Worker # Load the global config found in the root of the repo checkout. 257*d68f33bcSAndroid Build Coastguard Worker rh.git.find_repo_root(), 258*d68f33bcSAndroid Build Coastguard Worker ) 259*d68f33bcSAndroid Build Coastguard Worker 260*d68f33bcSAndroid Build Coastguard Worker paths = ( 261*d68f33bcSAndroid Build Coastguard Worker # Load the config for this git repo. 262*d68f33bcSAndroid Build Coastguard Worker '.', 263*d68f33bcSAndroid Build Coastguard Worker ) 264*d68f33bcSAndroid Build Coastguard Worker return rh.config.PreUploadSettings(paths=paths, global_paths=global_paths) 265*d68f33bcSAndroid Build Coastguard Worker 266*d68f33bcSAndroid Build Coastguard Worker 267*d68f33bcSAndroid Build Coastguard Workerdef _attempt_fixes(projects_results: List[rh.results.ProjectResults]) -> None: 268*d68f33bcSAndroid Build Coastguard Worker """Attempts to fix fixable results.""" 269*d68f33bcSAndroid Build Coastguard Worker # Filter out any result that has a fixup. 270*d68f33bcSAndroid Build Coastguard Worker fixups = [] 271*d68f33bcSAndroid Build Coastguard Worker for project_results in projects_results: 272*d68f33bcSAndroid Build Coastguard Worker fixups.extend((project_results.workdir, x) 273*d68f33bcSAndroid Build Coastguard Worker for x in project_results.fixups) 274*d68f33bcSAndroid Build Coastguard Worker if not fixups: 275*d68f33bcSAndroid Build Coastguard Worker return 276*d68f33bcSAndroid Build Coastguard Worker 277*d68f33bcSAndroid Build Coastguard Worker if len(fixups) > 1: 278*d68f33bcSAndroid Build Coastguard Worker banner = f'Multiple fixups ({len(fixups)}) are available.' 279*d68f33bcSAndroid Build Coastguard Worker else: 280*d68f33bcSAndroid Build Coastguard Worker banner = 'Automated fixups are available.' 281*d68f33bcSAndroid Build Coastguard Worker print(Output.COLOR.color(Output.COLOR.MAGENTA, banner), file=sys.stderr) 282*d68f33bcSAndroid Build Coastguard Worker 283*d68f33bcSAndroid Build Coastguard Worker # If there's more than one fixup available, ask if they want to blindly run 284*d68f33bcSAndroid Build Coastguard Worker # them all, or prompt for them one-by-one. 285*d68f33bcSAndroid Build Coastguard Worker mode = 'some' 286*d68f33bcSAndroid Build Coastguard Worker if len(fixups) > 1: 287*d68f33bcSAndroid Build Coastguard Worker while True: 288*d68f33bcSAndroid Build Coastguard Worker response = rh.terminal.str_prompt( 289*d68f33bcSAndroid Build Coastguard Worker 'What would you like to do', 290*d68f33bcSAndroid Build Coastguard Worker ('Run (A)ll', 'Run (S)ome', '(D)ry-run', '(N)othing [default]')) 291*d68f33bcSAndroid Build Coastguard Worker if not response: 292*d68f33bcSAndroid Build Coastguard Worker print('', file=sys.stderr) 293*d68f33bcSAndroid Build Coastguard Worker return 294*d68f33bcSAndroid Build Coastguard Worker if response.startswith('a') or response.startswith('y'): 295*d68f33bcSAndroid Build Coastguard Worker mode = 'all' 296*d68f33bcSAndroid Build Coastguard Worker break 297*d68f33bcSAndroid Build Coastguard Worker elif response.startswith('s'): 298*d68f33bcSAndroid Build Coastguard Worker mode = 'some' 299*d68f33bcSAndroid Build Coastguard Worker break 300*d68f33bcSAndroid Build Coastguard Worker elif response.startswith('d'): 301*d68f33bcSAndroid Build Coastguard Worker mode = 'dry-run' 302*d68f33bcSAndroid Build Coastguard Worker break 303*d68f33bcSAndroid Build Coastguard Worker elif response.startswith('n'): 304*d68f33bcSAndroid Build Coastguard Worker print('', file=sys.stderr) 305*d68f33bcSAndroid Build Coastguard Worker return 306*d68f33bcSAndroid Build Coastguard Worker 307*d68f33bcSAndroid Build Coastguard Worker # Walk all the fixups and run them one-by-one. 308*d68f33bcSAndroid Build Coastguard Worker for workdir, result in fixups: 309*d68f33bcSAndroid Build Coastguard Worker if mode == 'some': 310*d68f33bcSAndroid Build Coastguard Worker if not rh.terminal.boolean_prompt( 311*d68f33bcSAndroid Build Coastguard Worker f'Run {result.hook} fixup for {result.commit}' 312*d68f33bcSAndroid Build Coastguard Worker ): 313*d68f33bcSAndroid Build Coastguard Worker continue 314*d68f33bcSAndroid Build Coastguard Worker 315*d68f33bcSAndroid Build Coastguard Worker cmd = tuple(result.fixup_cmd) + tuple(result.files) 316*d68f33bcSAndroid Build Coastguard Worker print( 317*d68f33bcSAndroid Build Coastguard Worker f'\n[{Output.RUNNING}] cd {rh.shell.quote(workdir)} && ' 318*d68f33bcSAndroid Build Coastguard Worker f'{rh.shell.cmd_to_str(cmd)}', file=sys.stderr) 319*d68f33bcSAndroid Build Coastguard Worker if mode == 'dry-run': 320*d68f33bcSAndroid Build Coastguard Worker continue 321*d68f33bcSAndroid Build Coastguard Worker 322*d68f33bcSAndroid Build Coastguard Worker cmd_result = rh.utils.run(cmd, cwd=workdir, check=False) 323*d68f33bcSAndroid Build Coastguard Worker if cmd_result.returncode: 324*d68f33bcSAndroid Build Coastguard Worker print(f'[{Output.WARNING}] command exited {cmd_result.returncode}', 325*d68f33bcSAndroid Build Coastguard Worker file=sys.stderr) 326*d68f33bcSAndroid Build Coastguard Worker else: 327*d68f33bcSAndroid Build Coastguard Worker print(f'[{Output.PASSED}] great success', file=sys.stderr) 328*d68f33bcSAndroid Build Coastguard Worker 329*d68f33bcSAndroid Build Coastguard Worker print(f'\n[{Output.FIXUP}] Please amend & rebase your tree before ' 330*d68f33bcSAndroid Build Coastguard Worker 'attempting to upload again.\n', file=sys.stderr) 331*d68f33bcSAndroid Build Coastguard Worker 332*d68f33bcSAndroid Build Coastguard Workerdef _run_project_hooks_in_cwd( 333*d68f33bcSAndroid Build Coastguard Worker project_name: str, 334*d68f33bcSAndroid Build Coastguard Worker proj_dir: str, 335*d68f33bcSAndroid Build Coastguard Worker output: Output, 336*d68f33bcSAndroid Build Coastguard Worker jobs: Optional[int] = None, 337*d68f33bcSAndroid Build Coastguard Worker from_git: bool = False, 338*d68f33bcSAndroid Build Coastguard Worker commit_list: Optional[List[str]] = None, 339*d68f33bcSAndroid Build Coastguard Worker) -> rh.results.ProjectResults: 340*d68f33bcSAndroid Build Coastguard Worker """Run the project-specific hooks in the cwd. 341*d68f33bcSAndroid Build Coastguard Worker 342*d68f33bcSAndroid Build Coastguard Worker Args: 343*d68f33bcSAndroid Build Coastguard Worker project_name: The name of this project. 344*d68f33bcSAndroid Build Coastguard Worker proj_dir: The directory for this project (for passing on in metadata). 345*d68f33bcSAndroid Build Coastguard Worker output: Helper for summarizing output/errors to the user. 346*d68f33bcSAndroid Build Coastguard Worker jobs: How many hooks to run in parallel. 347*d68f33bcSAndroid Build Coastguard Worker from_git: If true, we are called from git directly and repo should not be 348*d68f33bcSAndroid Build Coastguard Worker used. 349*d68f33bcSAndroid Build Coastguard Worker commit_list: A list of commits to run hooks against. If None or empty 350*d68f33bcSAndroid Build Coastguard Worker list then we'll automatically get the list of commits that would be 351*d68f33bcSAndroid Build Coastguard Worker uploaded. 352*d68f33bcSAndroid Build Coastguard Worker 353*d68f33bcSAndroid Build Coastguard Worker Returns: 354*d68f33bcSAndroid Build Coastguard Worker All the results for this project. 355*d68f33bcSAndroid Build Coastguard Worker """ 356*d68f33bcSAndroid Build Coastguard Worker ret = rh.results.ProjectResults(project_name, proj_dir) 357*d68f33bcSAndroid Build Coastguard Worker 358*d68f33bcSAndroid Build Coastguard Worker try: 359*d68f33bcSAndroid Build Coastguard Worker config = _get_project_config(from_git) 360*d68f33bcSAndroid Build Coastguard Worker except rh.config.ValidationError as e: 361*d68f33bcSAndroid Build Coastguard Worker output.error('Loading config files', str(e)) 362*d68f33bcSAndroid Build Coastguard Worker return ret._replace(internal_failure=True) 363*d68f33bcSAndroid Build Coastguard Worker 364*d68f33bcSAndroid Build Coastguard Worker builtin_hooks = list(config.callable_builtin_hooks()) 365*d68f33bcSAndroid Build Coastguard Worker custom_hooks = list(config.callable_custom_hooks()) 366*d68f33bcSAndroid Build Coastguard Worker 367*d68f33bcSAndroid Build Coastguard Worker # If the repo has no pre-upload hooks enabled, then just return. 368*d68f33bcSAndroid Build Coastguard Worker if not builtin_hooks and not custom_hooks: 369*d68f33bcSAndroid Build Coastguard Worker return ret 370*d68f33bcSAndroid Build Coastguard Worker 371*d68f33bcSAndroid Build Coastguard Worker # Set up the environment like repo would with the forall command. 372*d68f33bcSAndroid Build Coastguard Worker try: 373*d68f33bcSAndroid Build Coastguard Worker remote = rh.git.get_upstream_remote() 374*d68f33bcSAndroid Build Coastguard Worker upstream_branch = rh.git.get_upstream_branch() 375*d68f33bcSAndroid Build Coastguard Worker except rh.utils.CalledProcessError as e: 376*d68f33bcSAndroid Build Coastguard Worker output.error('Upstream remote/tracking branch lookup', 377*d68f33bcSAndroid Build Coastguard Worker f'{e}\nDid you run repo start? Is your HEAD detached?') 378*d68f33bcSAndroid Build Coastguard Worker return ret._replace(internal_failure=True) 379*d68f33bcSAndroid Build Coastguard Worker 380*d68f33bcSAndroid Build Coastguard Worker project = rh.Project(name=project_name, dir=proj_dir) 381*d68f33bcSAndroid Build Coastguard Worker rel_proj_dir = os.path.relpath(proj_dir, rh.git.find_repo_root()) 382*d68f33bcSAndroid Build Coastguard Worker 383*d68f33bcSAndroid Build Coastguard Worker # Filter out the hooks to process. 384*d68f33bcSAndroid Build Coastguard Worker builtin_hooks = [x for x in builtin_hooks if rel_proj_dir not in x.scope] 385*d68f33bcSAndroid Build Coastguard Worker custom_hooks = [x for x in custom_hooks if rel_proj_dir not in x.scope] 386*d68f33bcSAndroid Build Coastguard Worker 387*d68f33bcSAndroid Build Coastguard Worker if not builtin_hooks and not custom_hooks: 388*d68f33bcSAndroid Build Coastguard Worker return ret 389*d68f33bcSAndroid Build Coastguard Worker 390*d68f33bcSAndroid Build Coastguard Worker os.environ.update({ 391*d68f33bcSAndroid Build Coastguard Worker 'REPO_LREV': rh.git.get_commit_for_ref(upstream_branch), 392*d68f33bcSAndroid Build Coastguard Worker 'REPO_PATH': rel_proj_dir, 393*d68f33bcSAndroid Build Coastguard Worker 'REPO_PROJECT': project_name, 394*d68f33bcSAndroid Build Coastguard Worker 'REPO_REMOTE': remote, 395*d68f33bcSAndroid Build Coastguard Worker 'REPO_RREV': rh.git.get_remote_revision(upstream_branch, remote), 396*d68f33bcSAndroid Build Coastguard Worker }) 397*d68f33bcSAndroid Build Coastguard Worker 398*d68f33bcSAndroid Build Coastguard Worker if not commit_list: 399*d68f33bcSAndroid Build Coastguard Worker commit_list = rh.git.get_commits( 400*d68f33bcSAndroid Build Coastguard Worker ignore_merged_commits=config.ignore_merged_commits) 401*d68f33bcSAndroid Build Coastguard Worker output.set_num_commits(len(commit_list)) 402*d68f33bcSAndroid Build Coastguard Worker 403*d68f33bcSAndroid Build Coastguard Worker def _run_hook(hook, project, commit, desc, diff): 404*d68f33bcSAndroid Build Coastguard Worker """Run a hook, gather stats, and process its results.""" 405*d68f33bcSAndroid Build Coastguard Worker start = datetime.datetime.now() 406*d68f33bcSAndroid Build Coastguard Worker results = hook.hook(project, commit, desc, diff) 407*d68f33bcSAndroid Build Coastguard Worker (error, warning) = _process_hook_results(results) 408*d68f33bcSAndroid Build Coastguard Worker duration = datetime.datetime.now() - start 409*d68f33bcSAndroid Build Coastguard Worker return (hook, results, error, warning, duration) 410*d68f33bcSAndroid Build Coastguard Worker 411*d68f33bcSAndroid Build Coastguard Worker with concurrent.futures.ThreadPoolExecutor(max_workers=jobs) as executor: 412*d68f33bcSAndroid Build Coastguard Worker for commit in commit_list: 413*d68f33bcSAndroid Build Coastguard Worker # Mix in some settings for our hooks. 414*d68f33bcSAndroid Build Coastguard Worker os.environ['PREUPLOAD_COMMIT'] = commit 415*d68f33bcSAndroid Build Coastguard Worker diff = rh.git.get_affected_files(commit) 416*d68f33bcSAndroid Build Coastguard Worker desc = rh.git.get_commit_desc(commit) 417*d68f33bcSAndroid Build Coastguard Worker os.environ['PREUPLOAD_COMMIT_MESSAGE'] = desc 418*d68f33bcSAndroid Build Coastguard Worker 419*d68f33bcSAndroid Build Coastguard Worker commit_summary = desc.split('\n', 1)[0] 420*d68f33bcSAndroid Build Coastguard Worker output.commit_start(builtin_hooks + custom_hooks, commit, commit_summary) 421*d68f33bcSAndroid Build Coastguard Worker 422*d68f33bcSAndroid Build Coastguard Worker def run_hooks(hooks): 423*d68f33bcSAndroid Build Coastguard Worker futures = ( 424*d68f33bcSAndroid Build Coastguard Worker executor.submit(_run_hook, hook, project, commit, desc, diff) 425*d68f33bcSAndroid Build Coastguard Worker for hook in hooks 426*d68f33bcSAndroid Build Coastguard Worker ) 427*d68f33bcSAndroid Build Coastguard Worker future_results = ( 428*d68f33bcSAndroid Build Coastguard Worker x.result() for x in concurrent.futures.as_completed(futures) 429*d68f33bcSAndroid Build Coastguard Worker ) 430*d68f33bcSAndroid Build Coastguard Worker for hook, hook_results, error, warning, duration in future_results: 431*d68f33bcSAndroid Build Coastguard Worker ret.add_results(hook_results) 432*d68f33bcSAndroid Build Coastguard Worker if error is not None or warning is not None: 433*d68f33bcSAndroid Build Coastguard Worker if warning is not None: 434*d68f33bcSAndroid Build Coastguard Worker output.hook_warning(hook, warning) 435*d68f33bcSAndroid Build Coastguard Worker if error is not None: 436*d68f33bcSAndroid Build Coastguard Worker output.hook_error(hook, error) 437*d68f33bcSAndroid Build Coastguard Worker output.hook_fixups(ret, hook_results) 438*d68f33bcSAndroid Build Coastguard Worker output.hook_finish(hook, duration) 439*d68f33bcSAndroid Build Coastguard Worker 440*d68f33bcSAndroid Build Coastguard Worker run_hooks(builtin_hooks) 441*d68f33bcSAndroid Build Coastguard Worker run_hooks(custom_hooks) 442*d68f33bcSAndroid Build Coastguard Worker 443*d68f33bcSAndroid Build Coastguard Worker return ret 444*d68f33bcSAndroid Build Coastguard Worker 445*d68f33bcSAndroid Build Coastguard Worker 446*d68f33bcSAndroid Build Coastguard Workerdef _run_project_hooks( 447*d68f33bcSAndroid Build Coastguard Worker project_name: str, 448*d68f33bcSAndroid Build Coastguard Worker proj_dir: Optional[str] = None, 449*d68f33bcSAndroid Build Coastguard Worker jobs: Optional[int] = None, 450*d68f33bcSAndroid Build Coastguard Worker from_git: bool = False, 451*d68f33bcSAndroid Build Coastguard Worker commit_list: Optional[List[str]] = None, 452*d68f33bcSAndroid Build Coastguard Worker) -> rh.results.ProjectResults: 453*d68f33bcSAndroid Build Coastguard Worker """Run the project-specific hooks in |proj_dir|. 454*d68f33bcSAndroid Build Coastguard Worker 455*d68f33bcSAndroid Build Coastguard Worker Args: 456*d68f33bcSAndroid Build Coastguard Worker project_name: The name of project to run hooks for. 457*d68f33bcSAndroid Build Coastguard Worker proj_dir: If non-None, this is the directory the project is in. If None, 458*d68f33bcSAndroid Build Coastguard Worker we'll ask repo. 459*d68f33bcSAndroid Build Coastguard Worker jobs: How many hooks to run in parallel. 460*d68f33bcSAndroid Build Coastguard Worker from_git: If true, we are called from git directly and repo should not be 461*d68f33bcSAndroid Build Coastguard Worker used. 462*d68f33bcSAndroid Build Coastguard Worker commit_list: A list of commits to run hooks against. If None or empty 463*d68f33bcSAndroid Build Coastguard Worker list then we'll automatically get the list of commits that would be 464*d68f33bcSAndroid Build Coastguard Worker uploaded. 465*d68f33bcSAndroid Build Coastguard Worker 466*d68f33bcSAndroid Build Coastguard Worker Returns: 467*d68f33bcSAndroid Build Coastguard Worker All the results for this project. 468*d68f33bcSAndroid Build Coastguard Worker """ 469*d68f33bcSAndroid Build Coastguard Worker output = Output(project_name) 470*d68f33bcSAndroid Build Coastguard Worker 471*d68f33bcSAndroid Build Coastguard Worker if proj_dir is None: 472*d68f33bcSAndroid Build Coastguard Worker cmd = ['repo', 'forall', project_name, '-c', 'pwd'] 473*d68f33bcSAndroid Build Coastguard Worker result = rh.utils.run(cmd, capture_output=True) 474*d68f33bcSAndroid Build Coastguard Worker proj_dirs = result.stdout.split() 475*d68f33bcSAndroid Build Coastguard Worker if not proj_dirs: 476*d68f33bcSAndroid Build Coastguard Worker print(f'{project_name} cannot be found.', file=sys.stderr) 477*d68f33bcSAndroid Build Coastguard Worker print('Please specify a valid project.', file=sys.stderr) 478*d68f33bcSAndroid Build Coastguard Worker return False 479*d68f33bcSAndroid Build Coastguard Worker if len(proj_dirs) > 1: 480*d68f33bcSAndroid Build Coastguard Worker print(f'{project_name} is associated with multiple directories.', 481*d68f33bcSAndroid Build Coastguard Worker file=sys.stderr) 482*d68f33bcSAndroid Build Coastguard Worker print('Please specify a directory to help disambiguate.', 483*d68f33bcSAndroid Build Coastguard Worker file=sys.stderr) 484*d68f33bcSAndroid Build Coastguard Worker return False 485*d68f33bcSAndroid Build Coastguard Worker proj_dir = proj_dirs[0] 486*d68f33bcSAndroid Build Coastguard Worker 487*d68f33bcSAndroid Build Coastguard Worker pwd = os.getcwd() 488*d68f33bcSAndroid Build Coastguard Worker try: 489*d68f33bcSAndroid Build Coastguard Worker # Hooks assume they are run from the root of the project. 490*d68f33bcSAndroid Build Coastguard Worker os.chdir(proj_dir) 491*d68f33bcSAndroid Build Coastguard Worker return _run_project_hooks_in_cwd( 492*d68f33bcSAndroid Build Coastguard Worker project_name, proj_dir, output, jobs=jobs, from_git=from_git, 493*d68f33bcSAndroid Build Coastguard Worker commit_list=commit_list) 494*d68f33bcSAndroid Build Coastguard Worker finally: 495*d68f33bcSAndroid Build Coastguard Worker output.finish() 496*d68f33bcSAndroid Build Coastguard Worker os.chdir(pwd) 497*d68f33bcSAndroid Build Coastguard Worker 498*d68f33bcSAndroid Build Coastguard Worker 499*d68f33bcSAndroid Build Coastguard Workerdef _run_projects_hooks( 500*d68f33bcSAndroid Build Coastguard Worker project_list: List[str], 501*d68f33bcSAndroid Build Coastguard Worker worktree_list: List[Optional[str]], 502*d68f33bcSAndroid Build Coastguard Worker jobs: Optional[int] = None, 503*d68f33bcSAndroid Build Coastguard Worker from_git: bool = False, 504*d68f33bcSAndroid Build Coastguard Worker commit_list: Optional[List[str]] = None, 505*d68f33bcSAndroid Build Coastguard Worker) -> bool: 506*d68f33bcSAndroid Build Coastguard Worker """Run all the hooks 507*d68f33bcSAndroid Build Coastguard Worker 508*d68f33bcSAndroid Build Coastguard Worker Args: 509*d68f33bcSAndroid Build Coastguard Worker project_list: List of project names. 510*d68f33bcSAndroid Build Coastguard Worker worktree_list: List of project checkouts. 511*d68f33bcSAndroid Build Coastguard Worker jobs: How many hooks to run in parallel. 512*d68f33bcSAndroid Build Coastguard Worker from_git: If true, we are called from git directly and repo should not be 513*d68f33bcSAndroid Build Coastguard Worker used. 514*d68f33bcSAndroid Build Coastguard Worker commit_list: A list of commits to run hooks against. If None or empty 515*d68f33bcSAndroid Build Coastguard Worker list then we'll automatically get the list of commits that would be 516*d68f33bcSAndroid Build Coastguard Worker uploaded. 517*d68f33bcSAndroid Build Coastguard Worker 518*d68f33bcSAndroid Build Coastguard Worker Returns: 519*d68f33bcSAndroid Build Coastguard Worker True if everything passed, else False. 520*d68f33bcSAndroid Build Coastguard Worker """ 521*d68f33bcSAndroid Build Coastguard Worker results = [] 522*d68f33bcSAndroid Build Coastguard Worker for project, worktree in zip(project_list, worktree_list): 523*d68f33bcSAndroid Build Coastguard Worker result = _run_project_hooks( 524*d68f33bcSAndroid Build Coastguard Worker project, 525*d68f33bcSAndroid Build Coastguard Worker proj_dir=worktree, 526*d68f33bcSAndroid Build Coastguard Worker jobs=jobs, 527*d68f33bcSAndroid Build Coastguard Worker from_git=from_git, 528*d68f33bcSAndroid Build Coastguard Worker commit_list=commit_list, 529*d68f33bcSAndroid Build Coastguard Worker ) 530*d68f33bcSAndroid Build Coastguard Worker results.append(result) 531*d68f33bcSAndroid Build Coastguard Worker if result: 532*d68f33bcSAndroid Build Coastguard Worker # If a repo had failures, add a blank line to help break up the 533*d68f33bcSAndroid Build Coastguard Worker # output. If there were no failures, then the output should be 534*d68f33bcSAndroid Build Coastguard Worker # very minimal, so we don't add it then. 535*d68f33bcSAndroid Build Coastguard Worker print('', file=sys.stderr) 536*d68f33bcSAndroid Build Coastguard Worker 537*d68f33bcSAndroid Build Coastguard Worker _attempt_fixes(results) 538*d68f33bcSAndroid Build Coastguard Worker return not any(results) 539*d68f33bcSAndroid Build Coastguard Worker 540*d68f33bcSAndroid Build Coastguard Worker 541*d68f33bcSAndroid Build Coastguard Workerdef main(project_list, worktree_list=None, **_kwargs): 542*d68f33bcSAndroid Build Coastguard Worker """Main function invoked directly by repo. 543*d68f33bcSAndroid Build Coastguard Worker 544*d68f33bcSAndroid Build Coastguard Worker We must use the name "main" as that is what repo requires. 545*d68f33bcSAndroid Build Coastguard Worker 546*d68f33bcSAndroid Build Coastguard Worker This function will exit directly upon error so that repo doesn't print some 547*d68f33bcSAndroid Build Coastguard Worker obscure error message. 548*d68f33bcSAndroid Build Coastguard Worker 549*d68f33bcSAndroid Build Coastguard Worker Args: 550*d68f33bcSAndroid Build Coastguard Worker project_list: List of projects to run on. 551*d68f33bcSAndroid Build Coastguard Worker worktree_list: A list of directories. It should be the same length as 552*d68f33bcSAndroid Build Coastguard Worker project_list, so that each entry in project_list matches with a 553*d68f33bcSAndroid Build Coastguard Worker directory in worktree_list. If None, we will attempt to calculate 554*d68f33bcSAndroid Build Coastguard Worker the directories automatically. 555*d68f33bcSAndroid Build Coastguard Worker kwargs: Leave this here for forward-compatibility. 556*d68f33bcSAndroid Build Coastguard Worker """ 557*d68f33bcSAndroid Build Coastguard Worker if not worktree_list: 558*d68f33bcSAndroid Build Coastguard Worker worktree_list = [None] * len(project_list) 559*d68f33bcSAndroid Build Coastguard Worker if not _run_projects_hooks(project_list, worktree_list): 560*d68f33bcSAndroid Build Coastguard Worker color = rh.terminal.Color() 561*d68f33bcSAndroid Build Coastguard Worker print(color.color(color.RED, 'FATAL') + 562*d68f33bcSAndroid Build Coastguard Worker ': Preupload failed due to above error(s).\n' 563*d68f33bcSAndroid Build Coastguard Worker f'For more info, see: {REPOHOOKS_URL}', 564*d68f33bcSAndroid Build Coastguard Worker file=sys.stderr) 565*d68f33bcSAndroid Build Coastguard Worker sys.exit(1) 566*d68f33bcSAndroid Build Coastguard Worker 567*d68f33bcSAndroid Build Coastguard Worker 568*d68f33bcSAndroid Build Coastguard Workerdef _identify_project(path, from_git=False): 569*d68f33bcSAndroid Build Coastguard Worker """Identify the repo project associated with the given path. 570*d68f33bcSAndroid Build Coastguard Worker 571*d68f33bcSAndroid Build Coastguard Worker Returns: 572*d68f33bcSAndroid Build Coastguard Worker A string indicating what project is associated with the path passed in or 573*d68f33bcSAndroid Build Coastguard Worker a blank string upon failure. 574*d68f33bcSAndroid Build Coastguard Worker """ 575*d68f33bcSAndroid Build Coastguard Worker if from_git: 576*d68f33bcSAndroid Build Coastguard Worker cmd = ['git', 'rev-parse', '--show-toplevel'] 577*d68f33bcSAndroid Build Coastguard Worker project_path = rh.utils.run(cmd, capture_output=True).stdout.strip() 578*d68f33bcSAndroid Build Coastguard Worker cmd = ['git', 'rev-parse', '--show-superproject-working-tree'] 579*d68f33bcSAndroid Build Coastguard Worker superproject_path = rh.utils.run( 580*d68f33bcSAndroid Build Coastguard Worker cmd, capture_output=True).stdout.strip() 581*d68f33bcSAndroid Build Coastguard Worker module_path = project_path[len(superproject_path) + 1:] 582*d68f33bcSAndroid Build Coastguard Worker cmd = ['git', 'config', '-f', '.gitmodules', 583*d68f33bcSAndroid Build Coastguard Worker '--name-only', '--get-regexp', r'^submodule\..*\.path$', 584*d68f33bcSAndroid Build Coastguard Worker f"^{module_path}$"] 585*d68f33bcSAndroid Build Coastguard Worker module_name = rh.utils.run(cmd, cwd=superproject_path, 586*d68f33bcSAndroid Build Coastguard Worker capture_output=True).stdout.strip() 587*d68f33bcSAndroid Build Coastguard Worker return module_name[len('submodule.'):-len(".path")] 588*d68f33bcSAndroid Build Coastguard Worker else: 589*d68f33bcSAndroid Build Coastguard Worker cmd = ['repo', 'forall', '.', '-c', 'echo ${REPO_PROJECT}'] 590*d68f33bcSAndroid Build Coastguard Worker return rh.utils.run(cmd, capture_output=True, cwd=path).stdout.strip() 591*d68f33bcSAndroid Build Coastguard Worker 592*d68f33bcSAndroid Build Coastguard Worker 593*d68f33bcSAndroid Build Coastguard Workerdef direct_main(argv): 594*d68f33bcSAndroid Build Coastguard Worker """Run hooks directly (outside of the context of repo). 595*d68f33bcSAndroid Build Coastguard Worker 596*d68f33bcSAndroid Build Coastguard Worker Args: 597*d68f33bcSAndroid Build Coastguard Worker argv: The command line args to process. 598*d68f33bcSAndroid Build Coastguard Worker 599*d68f33bcSAndroid Build Coastguard Worker Returns: 600*d68f33bcSAndroid Build Coastguard Worker 0 if no pre-upload failures, 1 if failures. 601*d68f33bcSAndroid Build Coastguard Worker 602*d68f33bcSAndroid Build Coastguard Worker Raises: 603*d68f33bcSAndroid Build Coastguard Worker BadInvocation: On some types of invocation errors. 604*d68f33bcSAndroid Build Coastguard Worker """ 605*d68f33bcSAndroid Build Coastguard Worker parser = argparse.ArgumentParser(description=__doc__) 606*d68f33bcSAndroid Build Coastguard Worker parser.add_argument('--git', action='store_true', 607*d68f33bcSAndroid Build Coastguard Worker help='This hook is called from git instead of repo') 608*d68f33bcSAndroid Build Coastguard Worker parser.add_argument('--dir', default=None, 609*d68f33bcSAndroid Build Coastguard Worker help='The directory that the project lives in. If not ' 610*d68f33bcSAndroid Build Coastguard Worker 'specified, use the git project root based on the cwd.') 611*d68f33bcSAndroid Build Coastguard Worker parser.add_argument('--project', default=None, 612*d68f33bcSAndroid Build Coastguard Worker help='The project repo path; this can affect how the ' 613*d68f33bcSAndroid Build Coastguard Worker 'hooks get run, since some hooks are project-specific.' 614*d68f33bcSAndroid Build Coastguard Worker 'If not specified, `repo` will be used to figure this ' 615*d68f33bcSAndroid Build Coastguard Worker 'out based on the dir.') 616*d68f33bcSAndroid Build Coastguard Worker parser.add_argument('-j', '--jobs', type=int, 617*d68f33bcSAndroid Build Coastguard Worker help='Run up to this many hooks in parallel. Setting ' 618*d68f33bcSAndroid Build Coastguard Worker 'to 1 forces serial execution, and the default ' 619*d68f33bcSAndroid Build Coastguard Worker 'automatically chooses an appropriate number for the ' 620*d68f33bcSAndroid Build Coastguard Worker 'current system.') 621*d68f33bcSAndroid Build Coastguard Worker parser.add_argument('commits', nargs='*', 622*d68f33bcSAndroid Build Coastguard Worker help='Check specific commits') 623*d68f33bcSAndroid Build Coastguard Worker opts = parser.parse_args(argv) 624*d68f33bcSAndroid Build Coastguard Worker 625*d68f33bcSAndroid Build Coastguard Worker # Check/normalize git dir; if unspecified, we'll use the root of the git 626*d68f33bcSAndroid Build Coastguard Worker # project from CWD. 627*d68f33bcSAndroid Build Coastguard Worker if opts.dir is None: 628*d68f33bcSAndroid Build Coastguard Worker cmd = ['git', 'rev-parse', '--git-dir'] 629*d68f33bcSAndroid Build Coastguard Worker git_dir = rh.utils.run(cmd, capture_output=True).stdout.strip() 630*d68f33bcSAndroid Build Coastguard Worker if not git_dir: 631*d68f33bcSAndroid Build Coastguard Worker parser.error('The current directory is not part of a git project.') 632*d68f33bcSAndroid Build Coastguard Worker opts.dir = os.path.dirname(os.path.abspath(git_dir)) 633*d68f33bcSAndroid Build Coastguard Worker elif not os.path.isdir(opts.dir): 634*d68f33bcSAndroid Build Coastguard Worker parser.error(f'Invalid dir: {opts.dir}') 635*d68f33bcSAndroid Build Coastguard Worker elif not rh.git.is_git_repository(opts.dir): 636*d68f33bcSAndroid Build Coastguard Worker parser.error(f'Not a git repository: {opts.dir}') 637*d68f33bcSAndroid Build Coastguard Worker 638*d68f33bcSAndroid Build Coastguard Worker # Identify the project if it wasn't specified; this _requires_ the repo 639*d68f33bcSAndroid Build Coastguard Worker # tool to be installed and for the project to be part of a repo checkout. 640*d68f33bcSAndroid Build Coastguard Worker if not opts.project: 641*d68f33bcSAndroid Build Coastguard Worker opts.project = _identify_project(opts.dir, opts.git) 642*d68f33bcSAndroid Build Coastguard Worker if not opts.project: 643*d68f33bcSAndroid Build Coastguard Worker parser.error(f"Couldn't identify the project of {opts.dir}") 644*d68f33bcSAndroid Build Coastguard Worker 645*d68f33bcSAndroid Build Coastguard Worker try: 646*d68f33bcSAndroid Build Coastguard Worker if _run_projects_hooks([opts.project], [opts.dir], jobs=opts.jobs, 647*d68f33bcSAndroid Build Coastguard Worker from_git=opts.git, commit_list=opts.commits): 648*d68f33bcSAndroid Build Coastguard Worker return 0 649*d68f33bcSAndroid Build Coastguard Worker except KeyboardInterrupt: 650*d68f33bcSAndroid Build Coastguard Worker print('Aborting execution early due to user interrupt', file=sys.stderr) 651*d68f33bcSAndroid Build Coastguard Worker return 128 + signal.SIGINT 652*d68f33bcSAndroid Build Coastguard Worker return 1 653*d68f33bcSAndroid Build Coastguard Worker 654*d68f33bcSAndroid Build Coastguard Worker 655*d68f33bcSAndroid Build Coastguard Workerif __name__ == '__main__': 656*d68f33bcSAndroid Build Coastguard Worker sys.exit(direct_main(sys.argv[1:])) 657