|
| 1 | +--- |
| 2 | +description: 'Install, configure, troubleshoot, and safely remove repository-level Git hooks with Rstack CLI.' |
| 3 | +--- |
| 4 | + |
| 5 | +# hooks |
| 6 | + |
| 7 | +import { PackageManagerTabs } from '@rspress/core/theme'; |
| 8 | + |
| 9 | +The `rs hooks` command installs, updates, and removes repository-level [Git hooks](https://git-scm.com/docs/githooks). Hook scripts run in the project that installed them. |
| 10 | + |
| 11 | +## Usage |
| 12 | + |
| 13 | +```bash |
| 14 | +rs hooks [options] |
| 15 | +rs hooks uninstall |
| 16 | +``` |
| 17 | + |
| 18 | +Run `rs hooks` without a subcommand to install or update hooks. |
| 19 | + |
| 20 | +:::tip Alias |
| 21 | +`rs setup` is an alias for `rs hooks`. |
| 22 | +::: |
| 23 | + |
| 24 | +## Install hooks |
| 25 | + |
| 26 | +By default, hook scripts are stored in `.rstack/hooks` at the Git repository root. If the current directory is outside a Git repository, the command skips installation. |
| 27 | + |
| 28 | +Add `rs hooks` to the `prepare` script of the project that owns the repository hooks: |
| 29 | + |
| 30 | +```json title="package.json" |
| 31 | +{ |
| 32 | + "scripts": { |
| 33 | + "prepare": "rs hooks" |
| 34 | + } |
| 35 | +} |
| 36 | +``` |
| 37 | + |
| 38 | +Run the script once to install the hooks: |
| 39 | + |
| 40 | +<PackageManagerTabs |
| 41 | + command={{ |
| 42 | + npm: 'npm run prepare', |
| 43 | + yarn: 'yarn run prepare', |
| 44 | + pnpm: 'pnpm run prepare', |
| 45 | + bun: 'bun run prepare', |
| 46 | + }} |
| 47 | +/> |
| 48 | + |
| 49 | +For example, create a `pre-commit` hook that runs [`rs staged`](./staged): |
| 50 | + |
| 51 | +```sh title=".rstack/hooks/pre-commit" |
| 52 | +rs staged |
| 53 | +``` |
| 54 | + |
| 55 | +You can safely run the installation more than once, and it does not load `rstack.config.*`. Run `rs hooks` again after cloning the repository or if the generated hook files are missing. |
| 56 | + |
| 57 | +:::warning Existing Git hook managers |
| 58 | + |
| 59 | +`rs hooks` updates the repository's [`core.hooksPath`](https://git-scm.com/docs/git-config#Documentation/git-config.txt-corehooksPath). If it detects another hooks path or existing Git hooks, it skips installation. Run `rs hooks --force` to let Rstack manage hooks instead. |
| 60 | + |
| 61 | +::: |
| 62 | + |
| 63 | +## Install options |
| 64 | + |
| 65 | +### `--force` |
| 66 | + |
| 67 | +`--force` (or `-f`) installs Rstack hooks even when another Git hook setup already exists: |
| 68 | + |
| 69 | +```bash |
| 70 | +rs hooks --force |
| 71 | +``` |
| 72 | + |
| 73 | +Rstack keeps the existing hook files and points `core.hooksPath` to its generated hooks directory. While this setting is active, Git does not run hooks from the previous location. |
| 74 | + |
| 75 | +`--force` cannot replace hooks owned by another Rstack project. |
| 76 | + |
| 77 | +:::tip |
| 78 | +Run `rs hooks --force` only once. Use `rs hooks` without `--force` in the `prepare` script. |
| 79 | +::: |
| 80 | + |
| 81 | +### `--hooks-dir` |
| 82 | + |
| 83 | +Specifies a custom directory for hook scripts, relative to the Git repository root. |
| 84 | + |
| 85 | +```bash |
| 86 | +rs hooks --hooks-dir config/git-hooks |
| 87 | + |
| 88 | +# Quote paths that contain spaces |
| 89 | +rs hooks --hooks-dir "config/git hooks" |
| 90 | +``` |
| 91 | + |
| 92 | +When using a custom directory, add the full command to the `prepare` script of the project that manages hooks: |
| 93 | + |
| 94 | +```json title="package.json" |
| 95 | +{ |
| 96 | + "scripts": { |
| 97 | + "prepare": "rs hooks --hooks-dir config/git-hooks" |
| 98 | + } |
| 99 | +} |
| 100 | +``` |
| 101 | + |
| 102 | +> The path must not contain `..`. This prevents hook files from being created or overwritten outside the repository through a parent directory path. |
| 103 | +
|
| 104 | +### `--help` |
| 105 | + |
| 106 | +`--help` (or `-h`) displays the command's usage, subcommands, and installation options. |
| 107 | + |
| 108 | +```bash |
| 109 | +rs hooks --help |
| 110 | +``` |
| 111 | + |
| 112 | +## Uninstall hooks |
| 113 | + |
| 114 | +Run the command from the project that installed the hooks: |
| 115 | + |
| 116 | +```bash |
| 117 | +rs hooks uninstall |
| 118 | +``` |
| 119 | + |
| 120 | +Rstack automatically finds the active hooks and removes the `core.hooksPath` setting and the generated `_` directory. It does not delete project hook files such as `.rstack/hooks/pre-commit`. Hooks managed by another project or tool are left untouched. |
| 121 | + |
| 122 | +Remove `rs hooks` from the `prepare` script if you do not want the hooks to be installed again. |
| 123 | + |
| 124 | +If `--force` previously took over hooks in `.git/hooks`, those hooks become active again after uninstalling. |
| 125 | + |
| 126 | +## Hook files |
| 127 | + |
| 128 | +The default directory structure is: |
| 129 | + |
| 130 | +```text |
| 131 | +.rstack/ |
| 132 | +└── hooks/ |
| 133 | + ├── pre-commit # Repository hook script: edit and commit |
| 134 | + └── _/ # Generated by rs hooks; ignored by Git |
| 135 | + ├── .gitignore |
| 136 | + ├── .owner |
| 137 | + ├── runner |
| 138 | + ├── pre-commit |
| 139 | + ├── commit-msg |
| 140 | + └── ... |
| 141 | +``` |
| 142 | + |
| 143 | +Files alongside `_` are repository hook scripts. The `_` directory contains generated files and is ignored by Git. `rs hooks` points `core.hooksPath` to `.rstack/hooks/_`. |
| 144 | + |
| 145 | +## Supported hooks |
| 146 | + |
| 147 | +Rstack CLI supports these client-side Git hooks: |
| 148 | + |
| 149 | +- `pre-commit` |
| 150 | +- `pre-merge-commit` |
| 151 | +- `prepare-commit-msg` |
| 152 | +- `commit-msg` |
| 153 | +- `post-commit` |
| 154 | +- `applypatch-msg` |
| 155 | +- `pre-applypatch` |
| 156 | +- `post-applypatch` |
| 157 | +- `pre-rebase` |
| 158 | +- `post-rewrite` |
| 159 | +- `post-checkout` |
| 160 | +- `post-merge` |
| 161 | +- `pre-push` |
| 162 | +- `pre-auto-gc` |
| 163 | + |
| 164 | +Create a file with the matching name alongside the `_` directory. |
| 165 | + |
| 166 | +## Hook runtime |
| 167 | + |
| 168 | +Rstack CLI runs hook scripts with POSIX `sh -e`. It forwards Git's arguments and standard input, then returns the hook's exit code. Before running a hook, Rstack changes to the project that installed the hooks and prepends that project's `node_modules/.bin` to `PATH`. |
| 169 | + |
| 170 | +### Disable and debug |
| 171 | + |
| 172 | +Set `RSTACK_HOOKS=0` to skip installation or hook execution: |
| 173 | + |
| 174 | +```bash |
| 175 | +RSTACK_HOOKS=0 git commit -m "Skip hooks" |
| 176 | +``` |
| 177 | + |
| 178 | +Set `RSTACK_HOOKS=2` to trace the Rstack CLI hook runtime, including how it invokes the hook script and handles its exit code; to trace commands inside the hook script, add `set -x` to the script: |
| 179 | + |
| 180 | +```bash |
| 181 | +RSTACK_HOOKS=2 git commit -m "Trace hooks" |
| 182 | +``` |
| 183 | + |
| 184 | +### Configure the hook environment |
| 185 | + |
| 186 | +Before running a hook script, Rstack CLI loads this optional POSIX shell file: |
| 187 | + |
| 188 | +```text |
| 189 | +${XDG_CONFIG_HOME:-$HOME/.config}/rstack/hooks-init.sh |
| 190 | +``` |
| 191 | + |
| 192 | +Use it to initialize a Node.js version manager, update `PATH`, or set `RSTACK_HOOKS=0` for the current user. |
| 193 | + |
| 194 | +## Ownership safety |
| 195 | + |
| 196 | +Each generated hooks directory records its owning project. Only that project should include `rs hooks` in its `prepare` script. Rstack skips installation from any other project, even with `--force`, and refuses to remove hooks owned by another project. |
| 197 | + |
| 198 | +To transfer ownership: |
| 199 | + |
| 200 | +1. Remove `rs hooks` from the previous owner's `prepare` script. |
| 201 | +2. Run `rs hooks uninstall` from the previous owner. |
| 202 | +3. Add `rs hooks` to the new owner's `prepare` script and run it once. |
| 203 | + |
| 204 | +## Monorepo |
| 205 | + |
| 206 | +In a monorepo, the project that provides Rstack CLI may live in a subdirectory such as `frontend/`. Running `rs hooks` there still installs hooks at the Git repository root: |
| 207 | + |
| 208 | +```text |
| 209 | +repo/.rstack/hooks/ |
| 210 | +repo/.rstack/hooks/_/ |
| 211 | +core.hooksPath=.rstack/hooks/_ |
| 212 | +``` |
| 213 | + |
| 214 | +Rstack records `frontend` as the owning project. The hook scripts stay at the repository root but run from `frontend`, allowing them to use its configuration and dependencies without an explicit `cd`: |
| 215 | + |
| 216 | +```sh title=".rstack/hooks/pre-commit" |
| 217 | +rs staged |
| 218 | +``` |
| 219 | + |
| 220 | +## Worktree behavior |
| 221 | + |
| 222 | +Rstack preserves the Git configuration scope of the active `core.hooksPath`. If a linked worktree uses a worktree-scoped hooks path, `rs hooks --force` replaces it in that scope instead of writing a local setting that Git would ignore. |
| 223 | + |
| 224 | +`rs hooks uninstall` also removes the setting from that scope. Removing a worktree-scoped installation affects only the current worktree and leaves hooks in other linked checkouts unchanged. A local `core.hooksPath` setting is shared across linked checkouts, so changing or removing it affects every checkout that does not override it. |
| 225 | + |
| 226 | +## Troubleshooting |
| 227 | + |
| 228 | +### Hook does not run |
| 229 | + |
| 230 | +- Check that the hook script has a [supported name](#supported-hooks) and is next to the `_` directory. |
| 231 | +- Run `git config --show-scope --get core.hooksPath` and verify the effective scope and path. |
| 232 | +- Rerun `rs hooks` to restore generated files and executable permissions. |
| 233 | +- Check that `RSTACK_HOOKS` is not set to `0` in the environment or initialization file. |
| 234 | +- If another hook setup is detected, run `rs hooks --force`. |
| 235 | +- If another project is reported as the hooks owner, follow the steps in [Ownership safety](#ownership-safety). |
| 236 | + |
| 237 | +Hook scripts do not need to be executable because Rstack CLI runs them with `sh`. |
| 238 | + |
| 239 | +### Command not found |
| 240 | + |
| 241 | +For exit code 127, Rstack CLI prints the effective `PATH`. If a GUI Git client cannot find Node.js or the package manager, initialize them in `hooks-init.sh`. |
| 242 | + |
| 243 | +### Windows and Yarn |
| 244 | + |
| 245 | +On Windows, hooks run in the POSIX shell included with [Git for Windows](https://gitforwindows.org/). Use LF line endings and `/` path separators in hooks. |
| 246 | + |
| 247 | +[Yarn PnP](https://yarnpkg.com/features/pnp) does not provide `node_modules/.bin`. Run tools through a Yarn script, such as `yarn run test`, and make Node.js and Yarn available through `hooks-init.sh` when needed. |
0 commit comments