def _ExecuteHookViaImport(self, data, context, **kwargs): """Execute the hook code in |data| directly. Args: data: The code of the hook to execute. context: Basic Python context to execute the hook inside. kwargs: Arbitrary arguments to pass to the hook script. Raises: HookError: When the hooks failed for any reason. """ # Exec, storing global context in the context dict. We catch exceptions # and convert to a HookError w/ just the failing traceback. try: exec(compile(data, self._script_fullpath, 'exec'), context) except Exception: raise HookError( '%s\nFailed to import %s hook; see traceback above.' % (traceback.format_exc(), self._hook_type)) # Running the script should have defined a main() function. if 'main' not in context: raise HookError('Missing main() in: "%s"' % self._script_fullpath) # Call the main function in the hook. If the hook should cause the # build to fail, it will raise an Exception. We'll catch that convert # to a HookError w/ just the failing traceback. try: context['main'](**kwargs) except Exception: raise HookError( '%s\nFailed to run main() for %s hook; see traceback ' 'above.' % (traceback.format_exc(), self._hook_type))
def Run(self, user_allows_all_hooks, **kwargs): """Run the hook. If the hook doesn't exist (because there is no hooks project or because this particular hook is not enabled), this is a no-op. Args: user_allows_all_hooks: If True, we will never prompt about running the hook--we'll just assume it's OK to run it. kwargs: Keyword arguments to pass to the hook. These are often specific to the hook type. For instance, pre-upload hooks will contain a project_list. Raises: HookError: If there was a problem finding the hook or the user declined to run a required hook (from _CheckForHookApproval). """ # No-op if there is no hooks project or if hook is disabled. if ((not self._hooks_project) or (self._hook_type not in self._hooks_project.enabled_repo_hooks)): return # Bail with a nice error if we can't find the hook. if not os.path.isfile(self._script_fullpath): raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath) # Make sure the user is OK with running the hook. if (not user_allows_all_hooks) and (not self._CheckForHookApproval()): return # Run the hook with the same version of python we're using. self._ExecuteHook(**kwargs)
def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt, changed_prompt): """Check for approval for a particular attribute and hook. Args: subkey: The git config key under [repo.hooks.<hook_type>] to store the last approved string. new_val: The new value to compare against the last approved one. main_prompt: Message to display to the user to ask for approval. changed_prompt: Message explaining why we're re-asking for approval. Returns: True if this hook is approved to run; False otherwise. Raises: HookError: Raised if the user doesn't approve and abort_if_user_denies was passed to the consturctor. """ hooks_config = self._hooks_project.config git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey) # Get the last value that the user approved for this hook; may be None. old_val = hooks_config.GetString(git_approval_key) if old_val is not None: # User previously approved hook and asked not to be prompted again. if new_val == old_val: # Approval matched. We're done. return True else: # Give the user a reason why we're prompting, since they last told # us to "never ask again". prompt = 'WARNING: %s\n\n' % (changed_prompt, ) else: prompt = '' # Prompt the user if we're not on a tty; on a tty we'll assume "no". if sys.stdout.isatty(): prompt += main_prompt + ' (yes/always/NO)? ' response = input(prompt).lower() print() # User is doing a one-time approval. if response in ('y', 'yes'): return True elif response == 'always': hooks_config.SetString(git_approval_key, new_val) return True # For anything else, we'll assume no approval. if self._abort_if_user_denies: raise HookError('You must allow the %s hook or use --no-verify.' % self._hook_type) return False
def run_hook(hook, hargs, cwd=None, dryrun=False, *args, **kws): if hook: if os.path.exists(hook): cli = list([hook]) if hargs: cli.extend(hargs) if args: cli.extend(args) cmd = Command(cwd=cwd, dryrun=dryrun) cmd.new_args(*cli) ret = cmd.wait(**kws) if ret != 0: raise HookError('Failed to run %s' % hook) return 0 else: SubCommand.get_logger().debug("Error: %s not existed", hook)
def _ExecuteHookViaReexec(self, interp, context, **kwargs): """Execute the hook script through |interp|. Note: Support for this feature should be dropped ~Jun 2021. Args: interp: The Python program to run. context: Basic Python context to execute the hook inside. kwargs: Arbitrary arguments to pass to the hook script. Raises: HookError: When the hooks failed for any reason. """ # This logic needs to be kept in sync with _ExecuteHookViaImport below. script = """ import json, os, sys path = '''%(path)s''' kwargs = json.loads('''%(kwargs)s''') context = json.loads('''%(context)s''') sys.path.insert(0, os.path.dirname(path)) data = open(path).read() exec(compile(data, path, 'exec'), context) context['main'](**kwargs) """ % { 'path': self._script_fullpath, 'kwargs': json.dumps(kwargs), 'context': json.dumps(context), } # We pass the script via stdin to avoid OS argv limits. It also makes # unhandled exception tracebacks less verbose/confusing for users. cmd = [interp, '-c', 'import sys; exec(sys.stdin.read())'] proc = subprocess.Popen(cmd, stdin=subprocess.PIPE) proc.communicate(input=script.encode('utf-8')) if proc.returncode: raise HookError('Failed to run %s hook.' % (self._hook_type, ))
def _CheckHook(self): # Bail with a nice error if we can't find the hook. if not os.path.isfile(self._script_fullpath): raise HookError('Couldn\'t find repo hook: %s' % self._script_fullpath)