blob: 7eb11b8a5ffc8df12449fe60ff35088d3c82290a [file] [log] [blame]
Mike Frysinger76987782020-07-15 00:45:14 -04001#!/usr/bin/env python3
Mike Frysinger2e65c542016-03-08 16:17:00 -05002# Copyright 2016 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16"""Repo pre-upload hook.
17
18Normally this is loaded indirectly by repo itself, but it can be run directly
19when developing.
20"""
21
Mike Frysinger2e65c542016-03-08 16:17:00 -050022import argparse
Mike Frysinger579111e2019-12-04 21:36:01 -050023import datetime
Mike Frysinger2e65c542016-03-08 16:17:00 -050024import os
25import sys
26
Mike Frysinger7b30f4d2019-10-15 01:18:38 -040027
28# Assert some minimum Python versions as we don't test or support any others.
Mike Frysinger2729e112020-07-15 00:42:44 -040029if sys.version_info < (3, 6):
30 print('repohooks: error: Python-3.6+ is required', file=sys.stderr)
Mike Frysinger7b30f4d2019-10-15 01:18:38 -040031 sys.exit(1)
32
33
Mike Frysinger2e65c542016-03-08 16:17:00 -050034_path = os.path.dirname(os.path.realpath(__file__))
35if sys.path[0] != _path:
36 sys.path.insert(0, _path)
37del _path
38
Mike Frysinger2ef213c2017-11-10 15:41:56 -050039# We have to import our local modules after the sys.path tweak. We can't use
40# relative imports because this is an executable program, not a module.
41# pylint: disable=wrong-import-position
Mike Frysingerb9608182016-10-20 20:45:04 -040042import rh
Mike Frysinger2e65c542016-03-08 16:17:00 -050043import rh.results
44import rh.config
45import rh.git
46import rh.hooks
47import rh.terminal
48import rh.utils
49
50
51# Repohooks homepage.
52REPOHOOKS_URL = 'https://android.googlesource.com/platform/tools/repohooks/'
53
54
Josh Gao25abf4b2016-09-23 18:36:27 -070055class Output(object):
56 """Class for reporting hook status."""
57
58 COLOR = rh.terminal.Color()
59 COMMIT = COLOR.color(COLOR.CYAN, 'COMMIT')
60 RUNNING = COLOR.color(COLOR.YELLOW, 'RUNNING')
61 PASSED = COLOR.color(COLOR.GREEN, 'PASSED')
62 FAILED = COLOR.color(COLOR.RED, 'FAILED')
Jason Monk0886c912017-11-10 13:17:17 -050063 WARNING = COLOR.color(COLOR.YELLOW, 'WARNING')
Josh Gao25abf4b2016-09-23 18:36:27 -070064
Mike Frysinger8f1ceeb2020-03-20 00:42:55 -040065 # How long a hook is allowed to run before we warn that it is "too slow".
66 _SLOW_HOOK_DURATION = datetime.timedelta(seconds=30)
67
Mike Frysinger42234b72019-02-15 16:21:41 -050068 def __init__(self, project_name):
Josh Gao25abf4b2016-09-23 18:36:27 -070069 """Create a new Output object for a specified project.
70
71 Args:
72 project_name: name of project.
Josh Gao25abf4b2016-09-23 18:36:27 -070073 """
74 self.project_name = project_name
Mike Frysinger42234b72019-02-15 16:21:41 -050075 self.num_hooks = None
Josh Gao25abf4b2016-09-23 18:36:27 -070076 self.hook_index = 0
77 self.success = True
Mike Frysinger579111e2019-12-04 21:36:01 -050078 self.start_time = datetime.datetime.now()
Mike Frysinger8f1ceeb2020-03-20 00:42:55 -040079 self.hook_start_time = None
80 self._curr_hook_name = None
Josh Gao25abf4b2016-09-23 18:36:27 -070081
Mike Frysinger42234b72019-02-15 16:21:41 -050082 def set_num_hooks(self, num_hooks):
83 """Keep track of how many hooks we'll be running.
84
85 Args:
86 num_hooks: number of hooks to be run.
87 """
88 self.num_hooks = num_hooks
89
Josh Gao25abf4b2016-09-23 18:36:27 -070090 def commit_start(self, commit, commit_summary):
91 """Emit status for new commit.
92
93 Args:
94 commit: commit hash.
95 commit_summary: commit summary.
96 """
97 status_line = '[%s %s] %s' % (self.COMMIT, commit[0:12], commit_summary)
98 rh.terminal.print_status_line(status_line, print_newline=True)
99 self.hook_index = 1
100
101 def hook_start(self, hook_name):
102 """Emit status before the start of a hook.
103
104 Args:
105 hook_name: name of the hook.
106 """
Mike Frysinger8f1ceeb2020-03-20 00:42:55 -0400107 self._curr_hook_name = hook_name
108 self.hook_start_time = datetime.datetime.now()
Josh Gao25abf4b2016-09-23 18:36:27 -0700109 status_line = '[%s %d/%d] %s' % (self.RUNNING, self.hook_index,
110 self.num_hooks, hook_name)
111 self.hook_index += 1
112 rh.terminal.print_status_line(status_line)
113
Mike Frysinger8f1ceeb2020-03-20 00:42:55 -0400114 def hook_finish(self):
115 """Finish processing any per-hook state."""
116 duration = datetime.datetime.now() - self.hook_start_time
117 if duration >= self._SLOW_HOOK_DURATION:
118 self.hook_warning(
119 'This hook took %s to finish which is fairly slow for '
120 'developers.\nPlease consider moving the check to the '
121 'server/CI system instead.' %
122 (rh.utils.timedelta_str(duration),))
123
124 def hook_error(self, error):
Mike Frysingera18d5f12019-02-15 16:27:35 -0500125 """Print an error for a single hook.
Josh Gao25abf4b2016-09-23 18:36:27 -0700126
127 Args:
Josh Gao25abf4b2016-09-23 18:36:27 -0700128 error: error string.
129 """
Mike Frysinger8f1ceeb2020-03-20 00:42:55 -0400130 self.error(self._curr_hook_name, error)
Josh Gao25abf4b2016-09-23 18:36:27 -0700131
Mike Frysinger8f1ceeb2020-03-20 00:42:55 -0400132 def hook_warning(self, warning):
Mike Frysingera18d5f12019-02-15 16:27:35 -0500133 """Print a warning for a single hook.
Jason Monk0886c912017-11-10 13:17:17 -0500134
135 Args:
Jason Monk0886c912017-11-10 13:17:17 -0500136 warning: warning string.
137 """
Mike Frysinger8f1ceeb2020-03-20 00:42:55 -0400138 status_line = '[%s] %s' % (self.WARNING, self._curr_hook_name)
Jason Monk0886c912017-11-10 13:17:17 -0500139 rh.terminal.print_status_line(status_line, print_newline=True)
140 print(warning, file=sys.stderr)
141
Mike Frysingera18d5f12019-02-15 16:27:35 -0500142 def error(self, header, error):
143 """Print a general error.
144
145 Args:
146 header: A unique identifier for the source of this error.
147 error: error string.
148 """
149 status_line = '[%s] %s' % (self.FAILED, header)
150 rh.terminal.print_status_line(status_line, print_newline=True)
151 print(error, file=sys.stderr)
152 self.success = False
153
Josh Gao25abf4b2016-09-23 18:36:27 -0700154 def finish(self):
Mike Frysingera18d5f12019-02-15 16:27:35 -0500155 """Print summary for all the hooks."""
Mike Frysinger579111e2019-12-04 21:36:01 -0500156 status_line = '[%s] repohooks for %s %s in %s' % (
Josh Gao25abf4b2016-09-23 18:36:27 -0700157 self.PASSED if self.success else self.FAILED,
158 self.project_name,
Mike Frysinger579111e2019-12-04 21:36:01 -0500159 'passed' if self.success else 'failed',
160 rh.utils.timedelta_str(datetime.datetime.now() - self.start_time))
Josh Gao25abf4b2016-09-23 18:36:27 -0700161 rh.terminal.print_status_line(status_line, print_newline=True)
162
163
164def _process_hook_results(results):
165 """Returns an error string if an error occurred.
Mike Frysinger2e65c542016-03-08 16:17:00 -0500166
167 Args:
Josh Gao25abf4b2016-09-23 18:36:27 -0700168 results: A list of HookResult objects, or None.
Mike Frysinger2e65c542016-03-08 16:17:00 -0500169
170 Returns:
Josh Gao25abf4b2016-09-23 18:36:27 -0700171 error output if an error occurred, otherwise None
Jason Monk0886c912017-11-10 13:17:17 -0500172 warning output if an error occurred, otherwise None
Mike Frysinger2e65c542016-03-08 16:17:00 -0500173 """
Josh Gao25abf4b2016-09-23 18:36:27 -0700174 if not results:
Jason Monk0886c912017-11-10 13:17:17 -0500175 return (None, None)
Mike Frysinger2e65c542016-03-08 16:17:00 -0500176
Mike Frysingere5ad9af2020-03-27 22:29:01 -0400177 # We track these as dedicated fields in case a hook doesn't output anything.
178 # We want to treat silent non-zero exits as failures too.
179 has_error = False
180 has_warning = False
181
Jason Monk0886c912017-11-10 13:17:17 -0500182 error_ret = ''
183 warning_ret = ''
Mike Frysinger2e65c542016-03-08 16:17:00 -0500184 for result in results:
185 if result:
Jason Monk0886c912017-11-10 13:17:17 -0500186 ret = ''
Mike Frysinger2e65c542016-03-08 16:17:00 -0500187 if result.files:
Josh Gao25abf4b2016-09-23 18:36:27 -0700188 ret += ' FILES: %s' % (result.files,)
Mike Frysinger2e65c542016-03-08 16:17:00 -0500189 lines = result.error.splitlines()
Josh Gao25abf4b2016-09-23 18:36:27 -0700190 ret += '\n'.join(' %s' % (x,) for x in lines)
Jason Monk0886c912017-11-10 13:17:17 -0500191 if result.is_warning():
Mike Frysingere5ad9af2020-03-27 22:29:01 -0400192 has_warning = True
Jason Monk0886c912017-11-10 13:17:17 -0500193 warning_ret += ret
194 else:
Mike Frysingere5ad9af2020-03-27 22:29:01 -0400195 has_error = True
Jason Monk0886c912017-11-10 13:17:17 -0500196 error_ret += ret
Mike Frysinger2e65c542016-03-08 16:17:00 -0500197
Mike Frysingere5ad9af2020-03-27 22:29:01 -0400198 return (error_ret if has_error else None,
199 warning_ret if has_warning else None)
Mike Frysinger2e65c542016-03-08 16:17:00 -0500200
201
Luis Hector Chavez5c4c2932016-10-16 21:56:58 -0700202def _get_project_config():
203 """Returns the configuration for a project.
Mike Frysinger2e65c542016-03-08 16:17:00 -0500204
205 Expects to be called from within the project root.
206 """
Mike Frysingerca797702016-09-03 02:00:55 -0400207 global_paths = (
208 # Load the global config found in the manifest repo.
209 os.path.join(rh.git.find_repo_root(), '.repo', 'manifests'),
210 # Load the global config found in the root of the repo checkout.
211 rh.git.find_repo_root(),
212 )
213 paths = (
214 # Load the config for this git repo.
215 '.',
216 )
Mike Frysinger1baec122020-08-25 00:27:52 -0400217 return rh.config.PreUploadSettings(paths=paths, global_paths=global_paths)
Mike Frysinger2e65c542016-03-08 16:17:00 -0500218
219
Luis Hector Chavezdab680c2016-12-21 13:57:09 -0800220def _attempt_fixes(fixup_func_list, commit_list):
221 """Attempts to run |fixup_func_list| given |commit_list|."""
222 if len(fixup_func_list) != 1:
223 # Only single fixes will be attempted, since various fixes might
224 # interact with each other.
225 return
226
227 hook_name, commit, fixup_func = fixup_func_list[0]
228
229 if commit != commit_list[0]:
230 # If the commit is not at the top of the stack, git operations might be
231 # needed and might leave the working directory in a tricky state if the
232 # fix is attempted to run automatically (e.g. it might require manual
233 # merge conflict resolution). Refuse to run the fix in those cases.
234 return
235
236 prompt = ('An automatic fix can be attempted for the "%s" hook. '
237 'Do you want to run it?' % hook_name)
238 if not rh.terminal.boolean_prompt(prompt):
239 return
240
241 result = fixup_func()
242 if result:
243 print('Attempt to fix "%s" for commit "%s" failed: %s' %
244 (hook_name, commit, result),
245 file=sys.stderr)
246 else:
247 print('Fix successfully applied. Amend the current commit before '
248 'attempting to upload again.\n', file=sys.stderr)
249
250
Mike Frysinger42234b72019-02-15 16:21:41 -0500251def _run_project_hooks_in_cwd(project_name, proj_dir, output, commit_list=None):
252 """Run the project-specific hooks in the cwd.
Mike Frysinger2e65c542016-03-08 16:17:00 -0500253
254 Args:
Mike Frysinger42234b72019-02-15 16:21:41 -0500255 project_name: The name of this project.
256 proj_dir: The directory for this project (for passing on in metadata).
257 output: Helper for summarizing output/errors to the user.
Mike Frysinger2e65c542016-03-08 16:17:00 -0500258 commit_list: A list of commits to run hooks against. If None or empty
259 list then we'll automatically get the list of commits that would be
260 uploaded.
261
262 Returns:
263 False if any errors were found, else True.
264 """
Mike Frysingera18d5f12019-02-15 16:27:35 -0500265 try:
266 config = _get_project_config()
267 except rh.config.ValidationError as e:
268 output.error('Loading config files', str(e))
Mike Frysingera65ecb92019-02-15 15:58:31 -0500269 return False
Mike Frysingera18d5f12019-02-15 16:27:35 -0500270
271 # If the repo has no pre-upload hooks enabled, then just return.
Luis Hector Chavez5c4c2932016-10-16 21:56:58 -0700272 hooks = list(config.callable_hooks())
Mike Frysinger558aff42016-04-04 16:02:55 -0400273 if not hooks:
274 return True
275
Mike Frysinger42234b72019-02-15 16:21:41 -0500276 output.set_num_hooks(len(hooks))
277
Mike Frysinger2e65c542016-03-08 16:17:00 -0500278 # Set up the environment like repo would with the forall command.
Luis Hector Chavezd8f36752016-09-15 13:25:03 -0700279 try:
280 remote = rh.git.get_upstream_remote()
Luis Hector Chaveze9db4eb2016-11-29 16:20:12 -0800281 upstream_branch = rh.git.get_upstream_branch()
Mike Frysinger36d2ce62019-12-04 22:17:07 -0500282 except rh.utils.CalledProcessError as e:
Mike Frysingera18d5f12019-02-15 16:27:35 -0500283 output.error('Upstream remote/tracking branch lookup',
284 '%s\nDid you run repo start? Is your HEAD detached?' %
285 (e,))
Mike Frysingera65ecb92019-02-15 15:58:31 -0500286 return False
Mike Frysingera18d5f12019-02-15 16:27:35 -0500287
Thiébaud Weksteenea528202020-08-28 15:54:29 +0200288 project = rh.Project(name=project_name, dir=proj_dir, remote=remote)
289 rel_proj_dir = os.path.relpath(proj_dir, rh.git.find_repo_root())
290
Mike Frysinger2e65c542016-03-08 16:17:00 -0500291 os.environ.update({
Luis Hector Chaveze9db4eb2016-11-29 16:20:12 -0800292 'REPO_LREV': rh.git.get_commit_for_ref(upstream_branch),
Thiébaud Weksteenea528202020-08-28 15:54:29 +0200293 'REPO_PATH': rel_proj_dir,
Luis Hector Chaveze9db4eb2016-11-29 16:20:12 -0800294 'REPO_PROJECT': project_name,
Mike Frysinger2e65c542016-03-08 16:17:00 -0500295 'REPO_REMOTE': remote,
Luis Hector Chaveze9db4eb2016-11-29 16:20:12 -0800296 'REPO_RREV': rh.git.get_remote_revision(upstream_branch, remote),
Mike Frysinger2e65c542016-03-08 16:17:00 -0500297 })
298
Mike Frysinger2e65c542016-03-08 16:17:00 -0500299 if not commit_list:
Luis Hector Chavez5c4c2932016-10-16 21:56:58 -0700300 commit_list = rh.git.get_commits(
301 ignore_merged_commits=config.ignore_merged_commits)
Mike Frysinger2e65c542016-03-08 16:17:00 -0500302
Mike Frysinger2e65c542016-03-08 16:17:00 -0500303 ret = True
Luis Hector Chavezdab680c2016-12-21 13:57:09 -0800304 fixup_func_list = []
Josh Gao25abf4b2016-09-23 18:36:27 -0700305
Mike Frysinger2e65c542016-03-08 16:17:00 -0500306 for commit in commit_list:
307 # Mix in some settings for our hooks.
Mike Frysingerdc253622016-04-04 15:43:02 -0400308 os.environ['PREUPLOAD_COMMIT'] = commit
Mike Frysinger2e65c542016-03-08 16:17:00 -0500309 diff = rh.git.get_affected_files(commit)
Mike Frysinger76b1bc72016-04-20 16:50:16 -0400310 desc = rh.git.get_commit_desc(commit)
Mike Frysinger737bf272020-07-15 00:55:02 -0400311 os.environ['PREUPLOAD_COMMIT_MESSAGE'] = desc
Mike Frysinger2e65c542016-03-08 16:17:00 -0500312
Josh Gao25abf4b2016-09-23 18:36:27 -0700313 commit_summary = desc.split('\n', 1)[0]
314 output.commit_start(commit=commit, commit_summary=commit_summary)
Mike Frysinger2e65c542016-03-08 16:17:00 -0500315
Thiébaud Weksteenea528202020-08-28 15:54:29 +0200316 for name, hook, exclusion_scope in hooks:
Josh Gao25abf4b2016-09-23 18:36:27 -0700317 output.hook_start(name)
Thiébaud Weksteenea528202020-08-28 15:54:29 +0200318 if rel_proj_dir in exclusion_scope:
319 break
Josh Gao25abf4b2016-09-23 18:36:27 -0700320 hook_results = hook(project, commit, desc, diff)
Mike Frysinger8f1ceeb2020-03-20 00:42:55 -0400321 output.hook_finish()
Jason Monk0886c912017-11-10 13:17:17 -0500322 (error, warning) = _process_hook_results(hook_results)
Mike Frysingere5ad9af2020-03-27 22:29:01 -0400323 if error is not None or warning is not None:
324 if warning is not None:
Mike Frysinger8f1ceeb2020-03-20 00:42:55 -0400325 output.hook_warning(warning)
Mike Frysingere5ad9af2020-03-27 22:29:01 -0400326 if error is not None:
Jason Monk0886c912017-11-10 13:17:17 -0500327 ret = False
Mike Frysinger8f1ceeb2020-03-20 00:42:55 -0400328 output.hook_error(error)
Luis Hector Chavezdab680c2016-12-21 13:57:09 -0800329 for result in hook_results:
330 if result.fixup_func:
331 fixup_func_list.append((name, commit,
332 result.fixup_func))
333
334 if fixup_func_list:
335 _attempt_fixes(fixup_func_list, commit_list)
Josh Gao25abf4b2016-09-23 18:36:27 -0700336
Mike Frysinger2e65c542016-03-08 16:17:00 -0500337 return ret
338
339
Mike Frysinger42234b72019-02-15 16:21:41 -0500340def _run_project_hooks(project_name, proj_dir=None, commit_list=None):
341 """Run the project-specific hooks in |proj_dir|.
342
343 Args:
344 project_name: The name of project to run hooks for.
345 proj_dir: If non-None, this is the directory the project is in. If None,
346 we'll ask repo.
347 commit_list: A list of commits to run hooks against. If None or empty
348 list then we'll automatically get the list of commits that would be
349 uploaded.
350
351 Returns:
352 False if any errors were found, else True.
353 """
354 output = Output(project_name)
355
356 if proj_dir is None:
357 cmd = ['repo', 'forall', project_name, '-c', 'pwd']
Mike Frysinger70b78f02019-12-04 21:42:39 -0500358 result = rh.utils.run(cmd, capture_output=True)
Mike Frysinger36d2ce62019-12-04 22:17:07 -0500359 proj_dirs = result.stdout.split()
Mike Frysinger5ac20862019-06-05 22:50:49 -0400360 if not proj_dirs:
Mike Frysinger42234b72019-02-15 16:21:41 -0500361 print('%s cannot be found.' % project_name, file=sys.stderr)
362 print('Please specify a valid project.', file=sys.stderr)
363 return False
364 if len(proj_dirs) > 1:
365 print('%s is associated with multiple directories.' % project_name,
366 file=sys.stderr)
367 print('Please specify a directory to help disambiguate.',
368 file=sys.stderr)
369 return False
370 proj_dir = proj_dirs[0]
371
372 pwd = os.getcwd()
373 try:
374 # Hooks assume they are run from the root of the project.
375 os.chdir(proj_dir)
376 return _run_project_hooks_in_cwd(project_name, proj_dir, output,
377 commit_list=commit_list)
378 finally:
379 output.finish()
380 os.chdir(pwd)
381
382
Mike Frysinger2e65c542016-03-08 16:17:00 -0500383def main(project_list, worktree_list=None, **_kwargs):
384 """Main function invoked directly by repo.
385
386 We must use the name "main" as that is what repo requires.
387
388 This function will exit directly upon error so that repo doesn't print some
389 obscure error message.
390
391 Args:
392 project_list: List of projects to run on.
393 worktree_list: A list of directories. It should be the same length as
394 project_list, so that each entry in project_list matches with a
395 directory in worktree_list. If None, we will attempt to calculate
396 the directories automatically.
397 kwargs: Leave this here for forward-compatibility.
398 """
399 found_error = False
400 if not worktree_list:
401 worktree_list = [None] * len(project_list)
402 for project, worktree in zip(project_list, worktree_list):
403 if not _run_project_hooks(project, proj_dir=worktree):
404 found_error = True
Mike Frysingera18d5f12019-02-15 16:27:35 -0500405 # If a repo had failures, add a blank line to help break up the
406 # output. If there were no failures, then the output should be
407 # very minimal, so we don't add it then.
408 print('', file=sys.stderr)
Mike Frysinger2e65c542016-03-08 16:17:00 -0500409
410 if found_error:
411 color = rh.terminal.Color()
412 print('%s: Preupload failed due to above error(s).\n'
413 'For more info, please see:\n%s' %
414 (color.color(color.RED, 'FATAL'), REPOHOOKS_URL),
415 file=sys.stderr)
416 sys.exit(1)
417
418
419def _identify_project(path):
420 """Identify the repo project associated with the given path.
421
422 Returns:
423 A string indicating what project is associated with the path passed in or
424 a blank string upon failure.
425 """
426 cmd = ['repo', 'forall', '.', '-c', 'echo ${REPO_PROJECT}']
Mike Frysinger7bd2a9a2020-04-08 18:23:57 -0400427 return rh.utils.run(cmd, capture_output=True, cwd=path).stdout.strip()
Mike Frysinger2e65c542016-03-08 16:17:00 -0500428
429
430def direct_main(argv):
431 """Run hooks directly (outside of the context of repo).
432
433 Args:
434 argv: The command line args to process.
435
436 Returns:
437 0 if no pre-upload failures, 1 if failures.
438
439 Raises:
440 BadInvocation: On some types of invocation errors.
441 """
442 parser = argparse.ArgumentParser(description=__doc__)
443 parser.add_argument('--dir', default=None,
444 help='The directory that the project lives in. If not '
445 'specified, use the git project root based on the cwd.')
446 parser.add_argument('--project', default=None,
447 help='The project repo path; this can affect how the '
448 'hooks get run, since some hooks are project-specific.'
449 'If not specified, `repo` will be used to figure this '
450 'out based on the dir.')
451 parser.add_argument('commits', nargs='*',
452 help='Check specific commits')
453 opts = parser.parse_args(argv)
454
455 # Check/normalize git dir; if unspecified, we'll use the root of the git
456 # project from CWD.
457 if opts.dir is None:
458 cmd = ['git', 'rev-parse', '--git-dir']
Mike Frysinger7bd2a9a2020-04-08 18:23:57 -0400459 git_dir = rh.utils.run(cmd, capture_output=True).stdout.strip()
Mike Frysinger2e65c542016-03-08 16:17:00 -0500460 if not git_dir:
461 parser.error('The current directory is not part of a git project.')
462 opts.dir = os.path.dirname(os.path.abspath(git_dir))
463 elif not os.path.isdir(opts.dir):
464 parser.error('Invalid dir: %s' % opts.dir)
Adrian Roos8ac865f2018-04-13 12:08:52 +0100465 elif not rh.git.is_git_repository(opts.dir):
466 parser.error('Not a git repository: %s' % opts.dir)
Mike Frysinger2e65c542016-03-08 16:17:00 -0500467
468 # Identify the project if it wasn't specified; this _requires_ the repo
469 # tool to be installed and for the project to be part of a repo checkout.
470 if not opts.project:
471 opts.project = _identify_project(opts.dir)
472 if not opts.project:
473 parser.error("Repo couldn't identify the project of %s" % opts.dir)
474
475 if _run_project_hooks(opts.project, proj_dir=opts.dir,
476 commit_list=opts.commits):
477 return 0
Mike Frysinger5ac20862019-06-05 22:50:49 -0400478 return 1
Mike Frysinger2e65c542016-03-08 16:17:00 -0500479
480
481if __name__ == '__main__':
482 sys.exit(direct_main(sys.argv[1:]))