Add support for a new hook type "post-sync" declared in the manifest using <repo-hooks>. This allows executing a script automatically after a successful `repo sync`. This is useful for initializing developer environments, installing project-wide Git hooks, generating configs, and other post-sync automation tasks. Example manifest usage: <project name="myorg/repo-hooks" path="hooks" revision="main" /> <repo-hooks in-project="myorg/repo-hooks" enabled-list="post-sync"> <hook name="post-sync" /> </repo-hooks> The hook script must be named `post-sync.py` and located at the root of the hook project. The post-sync hook does not block `repo sync`; if the script fails, the sync still completes successfully with a warning. Test: Added `post-sync.py` in hook project and verified it runs after `repo sync` Bug: b/421694721 Change-Id: I69f3158f0fc319d73a85028d6e90fea02c1dc8c8 Signed-off-by: Kenny Cheng <chao.shun.cheng.tw@gmail.com> Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/480581 Reviewed-by: Scott Lee <ddoman@google.com> Reviewed-by: Gavin Mak <gavinmak@google.com>
175 lines
6.8 KiB
Markdown
175 lines
6.8 KiB
Markdown
# repo hooks
|
|
|
|
[TOC]
|
|
|
|
Repo provides a mechanism to hook specific stages of the runtime with custom
|
|
python modules. All the hooks live in one git project which is checked out by
|
|
the manifest (specified during `repo init`), and the manifest itself defines
|
|
which hooks are registered.
|
|
|
|
These are useful to run linters, check formatting, and run quick unittests
|
|
before allowing a step to proceed (e.g. before uploading a commit to Gerrit).
|
|
|
|
A complete example can be found in the Android project. It can be easily
|
|
re-used by any repo based project and is not specific to Android.<br>
|
|
https://android.googlesource.com/platform/tools/repohooks
|
|
|
|
## Approvals
|
|
|
|
When a hook is processed the first time, the user is prompted for approval.
|
|
We don't want to execute arbitrary code without explicit consent. For manifests
|
|
fetched via secure protocols (e.g. https://), the user is prompted once. For
|
|
insecure protocols (e.g. http://), the user is prompted whenever the registered
|
|
repohooks project is updated and a hook is triggered.
|
|
|
|
## Manifest Settings
|
|
|
|
For the full syntax, see the [repo manifest format](./manifest-format.md).
|
|
|
|
Here's a short example from
|
|
[Android](https://android.googlesource.com/platform/manifest/+/HEAD/default.xml).
|
|
The `<project>` line checks out the repohooks git repo to the local
|
|
`tools/repohooks/` path. The `<repo-hooks>` line says to look in the project
|
|
with the name `platform/tools/repohooks` for hooks to run during the
|
|
`pre-upload` phase.
|
|
|
|
```xml
|
|
<project path="tools/repohooks" name="platform/tools/repohooks" />
|
|
<repo-hooks in-project="platform/tools/repohooks" enabled-list="pre-upload" />
|
|
```
|
|
|
|
## Source Layout
|
|
|
|
The repohooks git repo should have a python file with the same name as the hook.
|
|
So if you want to support the `pre-upload` hook, you'll need to create a file
|
|
named `pre-upload.py`. Repo will dynamically load that module when processing
|
|
the hook and then call the `main` function in it.
|
|
|
|
Hooks should have their `main` accept `**kwargs` for future compatibility.
|
|
|
|
## Runtime
|
|
|
|
Hook return values are ignored.
|
|
|
|
Any uncaught exceptions from the hook will cause the step to fail. This is
|
|
intended as a fallback safety check though rather than the normal flow. If
|
|
you want your hook to trigger a failure, it should call `sys.exit()` (after
|
|
displaying relevant diagnostics).
|
|
|
|
Output (stdout & stderr) are not filtered in any way. Hooks should generally
|
|
not be too verbose. A short summary is nice, and some status information when
|
|
long running operations occur, but long/verbose output should be used only if
|
|
the hook ultimately fails.
|
|
|
|
The hook runs from the top level of the repo client where the operation is
|
|
started.
|
|
For example, if the repo client is under `~/tree/`, then that is where the hook
|
|
runs, even if you ran repo in a git repository at `~/tree/src/foo/`, or in a
|
|
subdirectory of that git repository in `~/tree/src/foo/bar/`.
|
|
Hooks frequently start off by doing a `os.chdir` to the specific project they're
|
|
called on (see below) and then changing back to the original dir when they're
|
|
finished.
|
|
|
|
Python's `sys.path` is modified so that the top of repohooks directory comes
|
|
first. This should help simplify the hook logic to easily allow importing of
|
|
local modules.
|
|
|
|
Repo does not modify the state of the git checkout. This means that the hooks
|
|
might be running in a dirty git repo with many commits and checked out to the
|
|
latest one. If the hook wants to operate on specific git commits, it needs to
|
|
manually discover the list of pending commits, extract the diff/commit, and
|
|
then check it directly. Hooks should not normally modify the active git repo
|
|
(such as checking out a specific commit to run checks) without first prompting
|
|
the user. Although user interaction is discouraged in the common case, it can
|
|
be useful when deploying automatic fixes.
|
|
|
|
### Shebang Handling
|
|
|
|
*** note
|
|
This is intended as a transitional feature. Hooks are expected to eventually
|
|
migrate to Python 3 only as Python 2 is EOL & deprecated.
|
|
***
|
|
|
|
If the hook is written against a specific version of Python (either 2 or 3),
|
|
the script can declare that explicitly. Repo will then attempt to execute it
|
|
under the right version of Python regardless of the version repo itself might
|
|
be executing under.
|
|
|
|
Here are the shebangs that are recognized.
|
|
|
|
* `#!/usr/bin/env python` & `#!/usr/bin/python`: The hook is compatible with
|
|
Python 2 & Python 3. For maximum compatibility, these are recommended.
|
|
* `#!/usr/bin/env python2` & `#!/usr/bin/python2`: The hook requires Python 2.
|
|
Version specific names like `python2.7` are also recognized.
|
|
* `#!/usr/bin/env python3` & `#!/usr/bin/python3`: The hook requires Python 3.
|
|
Version specific names like `python3.6` are also recognized.
|
|
|
|
If no shebang is detected, or does not match the forms above, we assume that the
|
|
hook is compatible with both Python 2 & Python 3 as if `#!/usr/bin/python` was
|
|
used.
|
|
|
|
## Hooks
|
|
|
|
Here are all the points available for hooking.
|
|
|
|
### pre-upload
|
|
|
|
This hook runs when people run `repo upload`.
|
|
|
|
The `pre-upload.py` file should be defined like:
|
|
|
|
```py
|
|
def main(project_list, worktree_list=None, **kwargs):
|
|
"""Main function invoked directly by repo.
|
|
|
|
We must use the name "main" as that is what repo requires.
|
|
|
|
Args:
|
|
project_list: List of projects to run on.
|
|
worktree_list: A list of directories. It should be the same length as
|
|
project_list, so that each entry in project_list matches with a
|
|
directory in worktree_list. If None, we will attempt to calculate
|
|
the directories automatically.
|
|
kwargs: Leave this here for forward-compatibility.
|
|
"""
|
|
```
|
|
|
|
### post-sync
|
|
|
|
This hook runs when `repo sync` completes without errors.
|
|
|
|
Note: This includes cases where no actual checkout may occur. The hook will still run.
|
|
For example:
|
|
- `repo sync -n` performs network fetches only and skips the checkout phase.
|
|
- `repo sync <project>` only updates the specified project(s).
|
|
- Partial failures may still result in a successful exit.
|
|
|
|
This hook is useful for post-processing tasks such as setting up git hooks,
|
|
bootstrapping configuration files, or running project initialization logic.
|
|
|
|
The hook is defined using the existing `<repo-hooks>` manifest block and is
|
|
optional. If the hook script fails or is missing, `repo sync` will still
|
|
complete successfully, and the error will be printed as a warning.
|
|
|
|
Example:
|
|
|
|
```xml
|
|
<project name="myorg/dev-tools" path="tools" revision="main" />
|
|
<repo-hooks in-project="myorg/dev-tools" enabled-list="post-sync">
|
|
<hook name="post-sync" />
|
|
</repo-hooks>
|
|
```
|
|
|
|
The `post-sync.py` file should be defined like:
|
|
|
|
```py
|
|
def main(repo_topdir=None, **kwargs):
|
|
"""Main function invoked directly by repo.
|
|
|
|
We must use the name "main" as that is what repo requires.
|
|
|
|
Args:
|
|
repo_topdir: The absolute path to the top-level directory of the repo workspace.
|
|
kwargs: Leave this here for forward-compatibility.
|
|
"""
|
|
```
|