Conversation
Many repos I work on use `dev` or `develop` for the trunk branch -- it'd be convenient for those to be protected as defaults. However, others may well use `dev` as an intentionally-volatile branch. Instead of guessing, we can infer the default branch by inspecting `origin/HEAD`. If that fails for whatever reason, fall back to the previous default of `master`+`main`
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1898 +/- ##
=======================================
Coverage 92.15% 92.15%
=======================================
Files 112 112
Lines 22529 22622 +93
=======================================
+ Hits 20761 20847 +86
- Misses 1768 1775 +7 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
📦 Cargo Bloat ComparisonBinary size change: -0.39% (25.6 MiB → 25.5 MiB) Expand for cargo-bloat outputHead Branch ResultsBase Branch Results |
⚡️ Hyperfine BenchmarksSummary: 0 regressions, 0 improvements above the 10% threshold. Environment
CLI CommandsBenchmarking basic commands in the main repo:
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base --version |
2.1 ± 0.1 | 2.0 | 2.4 | 1.00 ± 0.04 |
prek-head --version |
2.1 ± 0.1 | 2.1 | 2.3 | 1.00 |
prek list
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base list |
8.5 ± 0.2 | 8.1 | 9.8 | 1.01 ± 0.04 |
prek-head list |
8.4 ± 0.2 | 8.1 | 9.0 | 1.00 |
prek validate-config .pre-commit-config.yaml
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base validate-config .pre-commit-config.yaml |
2.9 ± 0.1 | 2.7 | 3.0 | 1.00 |
prek-head validate-config .pre-commit-config.yaml |
3.1 ± 1.9 | 2.7 | 16.5 | 1.09 ± 0.67 |
prek sample-config
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base sample-config |
2.4 ± 0.4 | 2.3 | 5.0 | 1.00 ± 0.20 |
prek-head sample-config |
2.4 ± 0.3 | 2.3 | 4.6 | 1.00 |
Cold vs Warm Runs
Comparing first run (cold) vs subsequent runs (warm cache):
prek run --all-files (cold - no cache)
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run --all-files |
138.6 ± 5.0 | 132.3 | 147.0 | 1.00 |
prek-head run --all-files |
140.0 ± 2.8 | 133.9 | 143.1 | 1.01 ± 0.04 |
prek run --all-files (warm - with cache)
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run --all-files |
140.0 ± 4.6 | 132.6 | 148.7 | 1.00 |
prek-head run --all-files |
142.1 ± 3.3 | 135.7 | 148.3 | 1.02 ± 0.04 |
Full Hook Suite
Running the builtin hook suite on the benchmark workspace:
prek run --all-files (full builtin hook suite)
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run --all-files |
140.4 ± 3.3 | 132.2 | 147.2 | 1.01 ± 0.04 |
prek-head run --all-files |
138.9 ± 4.0 | 132.7 | 148.6 | 1.00 |
Individual Hook Performance
Benchmarking each hook individually on the test repo:
prek run trailing-whitespace --all-files
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run trailing-whitespace --all-files |
20.1 ± 1.4 | 18.9 | 26.7 | 1.03 ± 0.08 |
prek-head run trailing-whitespace --all-files |
19.5 ± 0.6 | 18.5 | 21.0 | 1.00 |
prek run end-of-file-fixer --all-files
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run end-of-file-fixer --all-files |
37.7 ± 44.7 | 22.9 | 254.4 | 1.44 ± 1.71 |
prek-head run end-of-file-fixer --all-files |
26.2 ± 2.4 | 22.9 | 30.1 | 1.00 |
prek run check-json --all-files
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run check-json --all-files |
11.0 ± 0.4 | 10.5 | 12.5 | 1.01 ± 0.05 |
prek-head run check-json --all-files |
10.9 ± 0.4 | 10.4 | 11.7 | 1.00 |
prek run check-yaml --all-files
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run check-yaml --all-files |
11.2 ± 0.8 | 10.4 | 13.1 | 1.05 ± 0.08 |
prek-head run check-yaml --all-files |
10.7 ± 0.2 | 10.4 | 11.1 | 1.00 |
prek run check-toml --all-files
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run check-toml --all-files |
11.0 ± 0.4 | 10.4 | 12.1 | 1.01 ± 0.05 |
prek-head run check-toml --all-files |
10.9 ± 0.4 | 10.3 | 11.7 | 1.00 |
prek run check-xml --all-files
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run check-xml --all-files |
10.9 ± 0.3 | 10.4 | 11.7 | 1.00 |
prek-head run check-xml --all-files |
10.9 ± 0.3 | 10.2 | 11.5 | 1.00 ± 0.04 |
prek run detect-private-key --all-files
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run detect-private-key --all-files |
16.6 ± 1.0 | 15.0 | 18.5 | 1.00 |
prek-head run detect-private-key --all-files |
16.9 ± 1.3 | 14.7 | 19.4 | 1.02 ± 0.10 |
prek run fix-byte-order-marker --all-files
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run fix-byte-order-marker --all-files |
21.9 ± 1.5 | 19.0 | 24.3 | 1.02 ± 0.11 |
prek-head run fix-byte-order-marker --all-files |
21.5 ± 1.8 | 18.8 | 24.2 | 1.00 |
Installation Performance
Benchmarking hook installation (fast path hooks skip Python setup):
prek install-hooks (cold - no cache)
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base install-hooks |
4.3 ± 0.1 | 4.2 | 4.5 | 1.00 |
prek-head install-hooks |
4.4 ± 0.1 | 4.2 | 4.6 | 1.01 ± 0.04 |
prek install-hooks (warm - with cache)
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base install-hooks |
4.2 ± 0.0 | 4.2 | 4.3 | 1.00 |
prek-head install-hooks |
4.2 ± 0.0 | 4.2 | 4.3 | 1.00 ± 0.01 |
File Filtering/Scoping Performance
Testing different file selection modes:
prek run (staged files only)
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run |
48.9 ± 0.9 | 47.4 | 51.0 | 1.01 ± 0.03 |
prek-head run |
48.6 ± 1.3 | 46.1 | 51.6 | 1.00 |
prek run --files '*.json' (specific file type)
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run --files '*.json' |
8.2 ± 0.2 | 7.8 | 8.6 | 1.01 ± 0.04 |
prek-head run --files '*.json' |
8.0 ± 0.2 | 7.9 | 8.5 | 1.00 |
Workspace Discovery & Initialization
Benchmarking hook discovery and initialization overhead:
prek run --dry-run --all-files (measures init overhead)
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run --dry-run --all-files |
12.3 ± 0.3 | 11.8 | 12.8 | 1.00 |
prek-head run --dry-run --all-files |
12.3 ± 0.3 | 12.1 | 13.1 | 1.00 ± 0.03 |
Meta Hooks Performance
Benchmarking meta hooks separately:
prek run check-hooks-apply --all-files
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run check-hooks-apply --all-files |
11.3 ± 0.1 | 11.0 | 11.6 | 1.00 |
prek-head run check-hooks-apply --all-files |
11.4 ± 0.3 | 11.1 | 12.4 | 1.01 ± 0.03 |
prek run check-useless-excludes --all-files
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run check-useless-excludes --all-files |
11.2 ± 0.1 | 11.0 | 11.4 | 1.00 |
prek-head run check-useless-excludes --all-files |
11.4 ± 0.5 | 11.1 | 13.1 | 1.02 ± 0.04 |
prek run identity --all-files
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
prek-base run identity --all-files |
9.8 ± 0.2 | 9.6 | 10.2 | 1.00 |
prek-head run identity --all-files |
9.8 ± 0.1 | 9.7 | 10.0 | 1.00 ± 0.02 |
There was a problem hiding this comment.
Pull request overview
Adds repo-aware default-branch protection to the no-commit-to-branch builtin hook by attempting to infer the default branch from origin/HEAD, extending the existing main/master defaults.
Changes:
- Detects the default branch via
git symbolic-ref --short origin/HEADand includes it in the hook’s default protected branch list. - Updates builtin hook documentation to describe the expanded defaults.
- Adds unit tests for argument parsing and branch/pattern matching behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| docs/builtin.md | Updates no-commit-to-branch docs to mention origin/HEAD-based default branch protection. |
| crates/prek/src/hooks/pre_commit_hooks/no_commit_to_branch.rs | Implements origin/HEAD detection to extend default protected branches and adds unit tests. |
| /// Default protected branches: "main", "master", and the repo's default branch pointed to | ||
| /// by `origin/HEAD` (if detectable and not already covered by main+master) | ||
| static DEFAULT_BRANCHES: LazyLock<Vec<String>> = LazyLock::new(|| { | ||
| let detected = GIT.as_ref().ok().and_then(|git| { | ||
| let output = std::process::Command::new(git) | ||
| .arg("symbolic-ref") |
There was a problem hiding this comment.
DEFAULT_BRANCHES is a process-global LazyLock that detects the default branch using the current working directory once and then reuses it forever. In a prek run that executes hooks for multiple projects/repos in the same process, this can cause the “detected” branch from the first repo to be applied to subsequent repos (wrong branches protected). Compute the detected default branch per hook invocation (scoped to hook.work_dir() / repo path) instead of caching it in a static.
There was a problem hiding this comment.
Silly question, but -- is such a prek run possible? I thought one run was always one project/repo.
| #[derive(Parser)] | ||
| #[command(disable_help_subcommand = true)] | ||
| #[command(disable_version_flag = true)] | ||
| #[command(disable_help_flag = true)] | ||
| struct Args { | ||
| #[arg(short, long = "branch", default_values = &["main", "master"])] | ||
| #[arg(short, long = "branch", default_values_t = DEFAULT_BRANCHES.clone())] | ||
| branches: Vec<String>, |
There was a problem hiding this comment.
Using default_values_t = DEFAULT_BRANCHES.clone() means argument parsing will force initialization of DEFAULT_BRANCHES (spawning a git symbolic-ref origin/HEAD subprocess) even when the user provides explicit -b/--branch values. Consider parsing branches without defaults (or as Option<Vec<String>>) and applying the computed defaults only when the argument is absent, inside no_commit_to_branch where you can also run the git command with .current_dir(hook.work_dir()) and reuse existing git/process helpers.
There was a problem hiding this comment.
Ooh, yeah this is a bit gross. I wanted to stick with the arg-parsing pattern, but avoiding the unnecessary git call may be worth switching to a runtime git call (that may also allow switching to an async shell-out to git instead of the current blocking approach)
Many repos I work on use
devordevelopfor the trunk branch -- it'd be convenient for those to be protected as defaults. However, others may well usedevas an intentionally-volatile branch. Instead of guessing, we can infer the default branch by inspectingorigin/HEADand protect it as well as the commonmain+master. If that fails for whatever reason, fall back to the previous default ofmaster+mainNote that this does deviate from the behavior of
pre-commit, so if cross-compatibility is a critical, this may not be acceptable.