Mike Frysinger | 84e7e16 | 2017-11-10 21:28:41 -0500 | [diff] [blame] | 1 | # repo hooks |
| 2 | |
| 3 | [TOC] |
| 4 | |
| 5 | Repo provides a mechanism to hook specific stages of the runtime with custom |
| 6 | python modules. All the hooks live in one git project which is checked out by |
| 7 | the manifest (specified during `repo init`), and the manifest itself defines |
| 8 | which hooks are registered. |
| 9 | |
| 10 | These are useful to run linters, check formatting, and run quick unittests |
| 11 | before allowing a step to proceed (e.g. before uploading a commit to Gerrit). |
| 12 | |
| 13 | A complete example can be found in the Android project. It can be easily |
| 14 | re-used by any repo based project and is not specific to Android.<br> |
| 15 | https://android.googlesource.com/platform/tools/repohooks |
| 16 | |
| 17 | ## Approvals |
| 18 | |
| 19 | When a hook is processed the first time, the user is prompted for approval. |
| 20 | We don't want to execute arbitrary code without explicit consent. For manifests |
| 21 | fetched via secure protocols (e.g. https://), the user is prompted once. For |
| 22 | insecure protocols (e.g. http://), the user is prompted whenever the registered |
| 23 | repohooks project is updated and a hook is triggered. |
| 24 | |
| 25 | ## Manifest Settings |
| 26 | |
Mike Frysinger | 3891b75 | 2018-10-05 19:26:15 -0400 | [diff] [blame] | 27 | For the full syntax, see the [repo manifest format](./manifest-format.md). |
Mike Frysinger | 84e7e16 | 2017-11-10 21:28:41 -0500 | [diff] [blame] | 28 | |
| 29 | Here's a short example from |
Mike Frysinger | 6e89c96 | 2020-11-15 18:42:26 -0500 | [diff] [blame] | 30 | [Android](https://android.googlesource.com/platform/manifest/+/HEAD/default.xml). |
Mike Frysinger | 84e7e16 | 2017-11-10 21:28:41 -0500 | [diff] [blame] | 31 | The `` line checks out the repohooks git repo to the local |
| 32 | `tools/repohooks/` path. The `` line says to look in the project |
| 33 | with the name `platform/tools/repohooks` for hooks to run during the |
| 34 | `pre-upload` phase. |
| 35 | |
| 36 | ```xml |
| 37 | |
| 38 | |
| 39 | ``` |
| 40 | |
| 41 | ## Source Layout |
| 42 | |
| 43 | The repohooks git repo should have a python file with the same name as the hook. |
| 44 | So if you want to support the `pre-upload` hook, you'll need to create a file |
| 45 | named `pre-upload.py`. Repo will dynamically load that module when processing |
| 46 | the hook and then call the `main` function in it. |
| 47 | |
| 48 | Hooks should have their `main` accept `**kwargs` for future compatibility. |
| 49 | |
| 50 | ## Runtime |
| 51 | |
| 52 | Hook return values are ignored. |
| 53 | |
| 54 | Any uncaught exceptions from the hook will cause the step to fail. This is |
| 55 | intended as a fallback safety check though rather than the normal flow. If |
| 56 | you want your hook to trigger a failure, it should call `sys.exit()` (after |
| 57 | displaying relevant diagnostics). |
| 58 | |
| 59 | Output (stdout & stderr) are not filtered in any way. Hooks should generally |
| 60 | not be too verbose. A short summary is nice, and some status information when |
| 61 | long running operations occur, but long/verbose output should be used only if |
| 62 | the hook ultimately fails. |
| 63 | |
Mike Frysinger | ed429c9 | 2018-03-20 20:00:14 -0400 | [diff] [blame] | 64 | The hook runs from the top level of the repo client where the operation is |
| 65 | started. |
| 66 | For example, if the repo client is under `~/tree/`, then that is where the hook |
| 67 | runs, even if you ran repo in a git repository at `~/tree/src/foo/`, or in a |
| 68 | subdirectory of that git repository in `~/tree/src/foo/bar/`. |
| 69 | Hooks frequently start off by doing a `os.chdir` to the specific project they're |
| 70 | called on (see below) and then changing back to the original dir when they're |
| 71 | finished. |
Mike Frysinger | 84e7e16 | 2017-11-10 21:28:41 -0500 | [diff] [blame] | 72 | |
| 73 | Python's `sys.path` is modified so that the top of repohooks directory comes |
| 74 | first. This should help simplify the hook logic to easily allow importing of |
| 75 | local modules. |
| 76 | |
| 77 | Repo does not modify the state of the git checkout. This means that the hooks |
| 78 | might be running in a dirty git repo with many commits and checked out to the |
| 79 | latest one. If the hook wants to operate on specific git commits, it needs to |
| 80 | manually discover the list of pending commits, extract the diff/commit, and |
| 81 | then check it directly. Hooks should not normally modify the active git repo |
| 82 | (such as checking out a specific commit to run checks) without first prompting |
| 83 | the user. Although user interaction is discouraged in the common case, it can |
| 84 | be useful when deploying automatic fixes. |
| 85 | |
Mike Frysinger | f7c5160 | 2019-06-18 17:23:39 -0400 | [diff] [blame] | 86 | ### Shebang Handling |
| 87 | |
| 88 | *** note |
| 89 | This is intended as a transitional feature. Hooks are expected to eventually |
| 90 | migrate to Python 3 only as Python 2 is EOL & deprecated. |
| 91 | *** |
| 92 | |
| 93 | If the hook is written against a specific version of Python (either 2 or 3), |
| 94 | the script can declare that explicitly. Repo will then attempt to execute it |
| 95 | under the right version of Python regardless of the version repo itself might |
| 96 | be executing under. |
| 97 | |
| 98 | Here are the shebangs that are recognized. |
| 99 | |
| 100 | * `#!/usr/bin/env python` & `#!/usr/bin/python`: The hook is compatible with |
| 101 | Python 2 & Python 3. For maximum compatibility, these are recommended. |
| 102 | * `#!/usr/bin/env python2` & `#!/usr/bin/python2`: The hook requires Python 2. |
| 103 | Version specific names like `python2.7` are also recognized. |
| 104 | * `#!/usr/bin/env python3` & `#!/usr/bin/python3`: The hook requires Python 3. |
| 105 | Version specific names like `python3.6` are also recognized. |
| 106 | |
| 107 | If no shebang is detected, or does not match the forms above, we assume that the |
| 108 | hook is compatible with both Python 2 & Python 3 as if `#!/usr/bin/python` was |
| 109 | used. |
| 110 | |
Mike Frysinger | 84e7e16 | 2017-11-10 21:28:41 -0500 | [diff] [blame] | 111 | ## Hooks |
| 112 | |
| 113 | Here are all the points available for hooking. |
| 114 | |
| 115 | ### pre-upload |
| 116 | |
| 117 | This hook runs when people run `repo upload`. |
| 118 | |
| 119 | The `pre-upload.py` file should be defined like: |
| 120 | |
| 121 | ```py |
| 122 | def main(project_list, worktree_list=None, **kwargs): |
| 123 | """Main function invoked directly by repo. |
| 124 | |
| 125 | We must use the name "main" as that is what repo requires. |
| 126 | |
| 127 | Args: |
| 128 | project_list: List of projects to run on. |
| 129 | worktree_list: A list of directories. It should be the same length as |
| 130 | project_list, so that each entry in project_list matches with a |
| 131 | directory in worktree_list. If None, we will attempt to calculate |
| 132 | the directories automatically. |
| 133 | kwargs: Leave this here for forward-compatibility. |
| 134 | """ |
| 135 | ``` |