Skip to content

Support multiple .pre-commit-config.yaml in a workspace (monorepo mode)#583

Merged
j178 merged 14 commits intomasterfrom
workspace
Sep 6, 2025
Merged

Support multiple .pre-commit-config.yaml in a workspace (monorepo mode)#583
j178 merged 14 commits intomasterfrom
workspace

Conversation

@j178
Copy link
Owner

@j178 j178 commented Sep 1, 2025

Closes #18

Workspace Mode

prek supports a powerful workspace mode that allows you to manage multiple projects with their own pre-commit configurations within a single repository. This is particularly useful for monorepos or projects with complex directory structures.

Overview

A workspace is a directory structure that contains:

  • A root .pre-commit-config.yaml file
  • Zero or more nested .pre-commit-config.yaml files in subdirectories

Each directory containing a .pre-commit-config.yaml file is considered a project. Projects can be nested infinitely deep.

Discovery

When you run prek run without the --config option, prek automatically discovers the workspace:

  1. Find workspace root: Starting from the current working directory, prek walks up the directory tree until it finds a .pre-commit-config.yaml file. This becomes the workspace root.

  2. Discover all projects: From the workspace root, prek recursively searches all subdirectories for additional .pre-commit-config.yaml files. Each one becomes a separate project.

  3. Git repository boundary: The search stops at the git repository root (.git directory) to avoid including unrelated projects.

Note: The workspace root is not necessarily the same as the git repository root, a workspace can exist within a subdirectory of a git repository.

Note: The current working directory determines the workspace root discovery. prek starts searching from your current location and stops at the first .pre-commit-config.yaml file found while traversing up the directory tree. Running from different directories may discover different workspace roots. Use prek -C <dir> to change the working directory before execution.

Project Organization

Example Structure

my-monorepo/
├── .pre-commit-config.yaml          # Workspace root config
├── .git/
├── docs/
│   └── .pre-commit-config.yaml      # Nested project
├── src/
│   ├── .pre-commit-config.yaml      # Nested project
│   └── backend/
│       └── .pre-commit-config.yaml  # Deeply nested project
└── frontend/
    └── .pre-commit-config.yaml      # Nested project

In this example:

  • my-monorepo/ is the workspace root
  • docs/, src/, src/backend/, and frontend/ are individual projects
  • Each project has its own .pre-commit-config.yaml file

Execution Model

File Collection

When running in workspace mode:

  1. Collect all files: prek collects all files within the workspace root directory
  2. Apply global filters: Files are filtered based on include/exclude patterns from the workspace root config
  3. Distribute to projects: Each project receives a subset of files based on its location

Hook Execution

For each project:

  1. Scope to project directory: Hooks run within their project's root directory
  2. Filter files: Only files within the project's directory tree are passed to its hooks
  3. Independent execution: Each project's hooks run independently with their own environment

Execution Order

Projects are executed from deepest to shallowest:

  1. src/backend/ (deepest)
  2. src/
  3. docs/
  4. frontend/
  5. my-monorepo/ (root, last)

This ensures that more specific configurations (deeper projects) take precedence over general ones.

Note: Files in subprojects will be processed multiple times - once for each project in the hierarchy that contains them. For example, a file in src/backend/ will be checked by hooks in src/backend/, then src/, then the workspace root.

Example Output

When running prek run on the example structure above, you might see output like this:

$ prek run
Running hooks for `src/backend`:
check python ast.........................................................Passed
check for merge conflicts................................................Passed
black....................................................................Passed
isort....................................................................Passed

Running hooks for `docs`:
Markdownlint.........................................(unimplemented yet)Skipped

Running hooks for `frontend`:
prettier.................................................................Passed

Running hooks for `src`:
isort....................................................................Passed
mypy.....................................................................Passed
check python ast.........................................................Passed
check docstring is first.................................................Passed

Running hooks for `.`:
fix end of files.........................................................Passed
check yaml...............................................................Passed
check for added large files..............................................Passed
trim trailing whitespace.................................................Passed
check for merge conflicts................................................Passed

Notice how:

  • Files in src/backend/ are processed by both the src/backend/ project and the src/ project
  • Each project runs in its own working directory
  • The workspace root processes all files in the entire workspace
  • Projects are executed from deepest to shallowest as described in the execution order

Command Line Usage

# Run from current directory, auto-discover workspace
prek run

# Run specific hook across all projects
prek run black

# Run from specific directory
cd src/backend && prek run

# Use -C option to change directory automatically
prek run -C src/backend

The -C <dir> or --cd <dir> option automatically changes to the specified directory before running, allowing you to target specific projects from any location in the workspace.

Note: When using prek install, only the workspace root configuration's default_install_hook_types will be honored. Nested project configurations are not considered during installation.

Project and Hook Selection

In workspace mode, you can selectively run hooks from specific projects or skip certain projects/hooks using flexible selector syntax.

Selector Syntax

The selector syntax has three different forms:

  1. <hook-id>: Matches all hooks with the given ID across all projects.
  2. <project-path>/: Matches all hooks from the specified project and its subprojects.
  3. <project-path>:<hook-id>: Matches only the specified hook from the specified project.

Selectors can be used to select specific hooks or projects, and combined with --skip to exclude certain hooks or projects.

Note: <project-path> can be a relative path, which is then resolved relative to the current working directory.
Note that the trailing slash / in a <project-path> is important, if a selector does not contain a slash, it is interpreted as a hook ID.

Running Specific Hooks or Projects

# Run all hooks with a specific ID across all projects
prek run <hook-id>

# Run only hooks from a specific project
prek run <project-path>/

# Run only hooks with a specific ID from a specific project
prek run <project-path>:<hook-id>

Examples:

# Run all 'black' hooks across all projects
prek run black

# Run all hooks from the 'frontend' project
prek run frontend/

# Run only the 'lint' hook from the 'frontend' project
prek run frontend:lint

# Run the 'lint' from 'frontend' and 'black' from 'src/backend'
prek run frontend:lint src/backend:black

Skipping Projects or Hooks

You can skip specific projects or hooks using the --skip option, with the same syntax as for selecting projects or hooks.

# Skip all hooks from a specific project
prek run --skip <project-path>/

# Skip specific hooks within a selected project
prek run <project-path>/ --skip <subproject-path>/

# Skip all hooks with a specific ID across all projects
prek run --skip <hook-id>

Examples:

# Run all hooks except those from the 'frontend' project
prek run --skip frontend/

# Run hooks from 'frontend' but skip 'frontend/docs'
prek run frontend/ --skip frontend/docs

# Run hooks from 'frontend' but skip 'frontend/docs' and 'frontend:lint'
prek run frontend/ --skip frontend/docs --skip frontend:lint

# Run all hooks except 'black' and 'markdownlint' hooks
prek run --skip black --skip markdownlint

Note: Selecting a project includes all its subprojects unless explicitly skipped. Skipping a project also skips all its subprojects.

Note: The PREK_SKIP or SKIP environment variable can be used as an alternative to --skip. Multiple values should be comma-delimited:

# Skip 'frontend' and 'tests' projects
PREK_SKIP=frontend/,tests prek run

# Skip 'frontend/docs' project and 'src/backend:lint' hook
SKIP=frontend/docs,src/backend:lint prek run

Precedence rules for --skip command line options and environment variables are: --skip > PREK_SKIP > SKIP.

Advanced Examples

# Run 'lint' hooks from all projects except 'tests'
prek run lint --skip tests

# Run all hooks from 'src' and 'docs' but skip 'src/legacy'
prek run src/ docs/ --skip src/legacy

# Run 'format' hooks only from Python projects
prek run python:format

Single Config Mode

When you specify a configuration file using the -c or --config parameter, workspace mode is disabled and only the specified configuration file is used. This mode provides traditional pre-commit behavior similar to the original pre-commit tool.

In single config mode:

  • No workspace discovery: Only the explicitly specified configuration file is used
  • Single execution context: All hooks run from the git repository root directory
  • Global file scope: All files in the git repository are passed to all hooks
  • No project isolation: Hooks don't have access to project-specific working directories

Usage Examples

# Disable workspace mode, use specific config
prek run --config .pre-commit-config.yaml

# Use config from a subdirectory
prek run --config src/.pre-commit-config.yaml

# Short form using -c
prek run -c docs/.pre-commit-config.yaml

Key Differences: Workspace vs Single Config

Feature Workspace Mode Single Config Mode
Discovery Auto-discovers all .pre-commit-config.yaml files Uses single specified config file
Working Directory Uses workspace root Uses git repository root
File Scope All files in workspace All files in git repo
Hook Scope Project-specific file filtering All files pass to all hooks
Execution Context Each project runs in its own directory All hooks run from git root
Configuration Multiple configs Single config file only

Migration from Single Config

To migrate an existing single-config setup to workspace mode:

  1. Create workspace root: Move existing .pre-commit-config.yaml to repository root
  2. Add project configs: Create .pre-commit-config.yaml in subdirectories as needed
  3. Update file patterns: Adjust files/exclude patterns to be project-relative
  4. Test execution: Verify hooks run in correct directories with correct file sets

Debugging

# See which projects were discovered
prek run -vvv

# Check file collection for specific project
prek run -C project/dir -vvv

Limitations (TODO)

  1. For performance considerations, hook completions are only available for the next-level projects, not for nested ones.
  2. Skipped hooks will not be printed with a SKIPPED status.

The workspace mode provides powerful organization capabilities while maintaining backward compatibility with existing single-config workflows.

@j178 j178 added enhancement New feature or request extended feature labels Sep 1, 2025
@j178 j178 force-pushed the workspace branch 3 times, most recently from 90b1e2f to 372452d Compare September 2, 2025 06:57
prek run lint --skip tests

# Run all hooks from 'src' and 'docs' but skip 'src/legacy'
prek run src docs --skip src/legacy
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be nicer and less ambiguous if skipping path would have different flag:

--skip-path

Copy link

@potiuk potiuk Sep 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly it feels a bit wrong to be able to pass both hook id and path as run argument. This might lead to a number of ambuiguties.

Maybe paths should be prefixed and have multiplication option or allow them to be colon separated:

  • prek run --path src
  • prek run --path src --path doc

or:

  • prek run --paths src:doc

@j178
Copy link
Owner Author

j178 commented Sep 3, 2025

Hey @potiuk! I was going through the details of the workspace document and was wondering if you could take another look at it. Here’s the link: https://github.com/j178/prek/pull/583/files#diff-1f5ae9e589a468b8b28636fef58e8221229fc93e403da49bf89dcf4940d2e4e1. Thanks!


### Skipping Projects or Hooks

You can skip specific projects or hooks using the `--skip` option, with the same syntax as for selecting projects or hooks.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing missing here is describing how you skip multiple skips. It is not clear if it is the same as SKIP (coma separated)? or multiple --skip. Also it's not clear if SKIP env var also works with paths. If it does then theorethically , is not great idea as path separator :).

Copy link

@potiuk potiuk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks really cool. The only thing I think needs more clarity is how to handle multiple skips

@dheerajturaga
Copy link

Looks pretty good!

j178 added 13 commits September 6, 2025 16:39
…ode) (#481)

* Remove color from trace log

Since `tracing-subscriber 0.3.20`, ANSI codes will be escaped so that color can not be shown in log. tokio-rs/tracing@4c52ca5

* Refactor project constructure

# Conflicts:
#	src/cli/run/run.rs

* .

# Conflicts:
#	src/workspace.rs

# Conflicts:
#	src/workspace.rs

# Conflicts:
#	src/workspace.rs

.

# Conflicts:
#	src/git.rs

# Conflicts:
#	src/git.rs

# Conflicts:
#	src/git.rs
#	src/languages/script.rs

Support multiple `.pre-commit-connfig.yaml` in a workspace (monorepo mode)

.

.

Make it compile

Fix

.

.

.

Fix

Fix

Fix

.

.

.

# Conflicts:
#	src/builtin/pre_commit_hooks/fix_trailing_whitespace.rs
#	src/builtin/pre_commit_hooks/mod.rs
#	src/hook.rs
#	src/languages/docker.rs
#	src/languages/docker_image.rs
#	src/languages/golang/golang.rs
#	src/languages/node/node.rs
#	src/languages/python/python.rs
#	src/languages/system.rs
#	src/run.rs

* Refactor

* Fix

* .

* Fix `--config FILE`

* Add docs

* Misc

* Fix
# Conflicts:
#	README.md
* .

* Fix config not staged relative filename display
* Support `prek list` in workspace mode

* Update snapshot
…orkspace mode (#595)

* Support `prek install|install-hooks|hook-impl|init-template-dir` in workspace mode

* Fix

* Fix windows
# Conflicts:
#	src/cli/hook_impl.rs
#	tests/hook_impl.rs

# Conflicts:
#	src/cli/install.rs
* Implement `auto-update` in workspace mode

* Deduplicate remote hooks

* Minor
* selection

* .

* complete

* rewrite

* Fix

* Fix docs

* Update doc

* Fix single config mode

* Improve

* Fix doc

* Fixes and tweaks

* Fix tests

* Fixes and tests

* Use path clean instead of canonicalized
@j178 j178 merged commit e9fc08b into master Sep 6, 2025
21 checks passed
@j178 j178 deleted the workspace branch September 6, 2025 08:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve support for mono repos

3 participants