Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 23 additions & 9 deletions crates/prek/src/cli/run/selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,22 +364,36 @@ impl Selectors {
return false;
}

// If no project prefix selectors are present, all paths are included
if !self
.includes
.iter()
.any(|include| matches!(include.expr, SelectorExpr::ProjectPrefix(_)))
{
if self.includes.is_empty() {
return true;
}

let mut included = false;
// This is a project-discovery prefilter, not final hook selection.
// Keep scanning after a match so project-prefix selectors still get
// usage recorded for accurate unused-selector warnings.
for (idx, include) in self.includes.iter().enumerate() {
if let SelectorExpr::ProjectPrefix(project_path) = &include.expr {
if path.starts_with(project_path) {
usage.use_include(idx);
match &include.expr {
SelectorExpr::HookId(_) => {
// A bare hook id can match any project, but we cannot mark
// it used until final hook filtering proves a hook exists.
included = true;
}
SelectorExpr::ProjectPrefix(project_path) => {
// Project-prefix selectors select projects directly, so a
// matching discovered project is enough to mark them used.
if path.starts_with(project_path) {
usage.use_include(idx);
included = true;
}
}
SelectorExpr::ProjectHook { project_path, .. } => {
// `project:hook` only needs this exact project initialized;
// usage is still decided by the final hook-id match.
if path == project_path {
included = true;
}
}
}
}
included
Expand Down
80 changes: 80 additions & 0 deletions crates/prek/tests/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,86 @@ fn run_with_selectors() -> Result<()> {
Ok(())
}

#[test]
fn run_with_mixed_project_and_hook_selectors() -> Result<()> {
let context = TestContext::new();
context.init_project();

context.write_pre_commit_config(indoc! {r"
repos:
- repo: local
hooks:
- id: root-hook
name: root hook
entry: echo root
language: system
pass_filenames: false
"});

let sub = context.work_dir().child("sub");
sub.create_dir_all()?;
sub.child(".pre-commit-config.yaml").write_str(indoc! {r"
repos:
- repo: local
hooks:
- id: sub-hook
name: sub hook
entry: echo sub
language: system
pass_filenames: false
"})?;
sub.child("file.txt").write_str("")?;

let empty = context.work_dir().child("empty");
empty.create_dir_all()?;
empty
.child(".pre-commit-config.yaml")
.write_str("repos: []\n")?;

let unselected = context.work_dir().child("unselected");
unselected.create_dir_all()?;
unselected
.child(".pre-commit-config.yaml")
.write_str("invalid: config\n")?;

context.git_add(".");

cmd_snapshot!(context.filters(), context.run().arg("--all-files").arg("sub/").arg(".:root-hook"), @r"
success: true
exit_code: 0
----- stdout -----
✓ sub
sub hook...............................................................Passed
✓ <workspace>
root hook..............................................................Passed

----- stderr -----
");

cmd_snapshot!(context.filters(), context.run().arg("--all-files").arg("sub/").arg("root-hook"), @r"
success: true
exit_code: 0
----- stdout -----
✓ sub
sub hook...............................................................Passed
✓ <workspace>
root hook..............................................................Passed

----- stderr -----
");

cmd_snapshot!(context.filters(), context.run().arg("--all-files").arg("empty/").arg("root-hook"), @r"
success: true
exit_code: 0
----- stdout -----
root hook................................................................Passed

----- stderr -----
");

Ok(())
}

#[test]
fn skips() -> Result<()> {
let context = TestContext::new();
Expand Down
Loading