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
18 changes: 7 additions & 11 deletions crates/prek/src/cli/hook_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ pub(crate) async fn hook_impl(
let Some(run_args) = to_run_args(hook_type, &args, &stdin).await? else {
return Ok(legacy_code.into());
};
let file_selection = run_args.file_selection.into();

let status = cli::run(
store,
Expand All @@ -127,12 +128,7 @@ pub(crate) async fn hook_impl(
vec![],
vec![],
Some(hook_type.into()),
run_args.from_ref,
run_args.to_ref,
run_args.all_files,
vec![],
vec![],
false,
file_selection,
false,
flag(run_args.fail_fast, run_args.no_fail_fast),
false,
Expand Down Expand Up @@ -254,9 +250,9 @@ async fn to_run_args(
run_args.extra.remote_url = Some(args[1].to_string_lossy().into_owned());

if let Some(push_info) = parse_pre_push_info(&args[0].to_string_lossy(), stdin).await? {
run_args.from_ref = push_info.from_ref;
run_args.to_ref = push_info.to_ref;
run_args.all_files = push_info.all_files;
run_args.file_selection.from_ref = push_info.from_ref;
run_args.file_selection.to_ref = push_info.to_ref;
run_args.file_selection.all_files = push_info.all_files;
run_args.extra.remote_branch = push_info.remote_branch;
run_args.extra.local_branch = push_info.local_branch;
} else {
Expand All @@ -278,8 +274,8 @@ async fn to_run_args(
}
}
HookType::PostCheckout => {
run_args.from_ref = Some(args[0].to_string_lossy().into_owned());
run_args.to_ref = Some(args[1].to_string_lossy().into_owned());
run_args.file_selection.from_ref = Some(args[0].to_string_lossy().into_owned());
run_args.file_selection.to_ref = Some(args[1].to_string_lossy().into_owned());
run_args.extra.checkout_type = Some(args[2].to_string_lossy().into_owned());
}
HookType::PostMerge => run_args.extra.is_squash_merge = args[0] == "1",
Expand Down
142 changes: 103 additions & 39 deletions crates/prek/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,45 +467,17 @@ pub(crate) struct RunExtraArgs {
pub(crate) rewrite_command: Option<String>,
}

#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, Default, Args)]
pub(crate) struct RunOptions {
/// Include the specified hooks or projects.
///
/// Supports flexible selector syntax:
///
/// - `hook-id`: Run all hooks with the specified ID across all projects
///
/// - `project-path/`: Run all hooks from the specified project
///
/// - `project-path:hook-id`: Run only the specified hook from the specified project
///
/// Can be specified multiple times to select multiple hooks/projects.
#[arg(
value_name = "HOOK|PROJECT",
value_hint = ValueHint::Other,
add = ArgValueCompleter::new(selector_completer)
)]
pub(crate) includes: Vec<String>,
pub(crate) struct FileSelectionArgs {
/// Run hooks on all tracked files in the repository.
#[arg(short, long, conflicts_with_all = ["files", "glob", "from_ref", "to_ref"])]
pub(crate) all_files: bool,

/// Skip the specified hooks or projects.
///
/// Supports flexible selector syntax:
///
/// - `hook-id`: Skip all hooks with the specified ID across all projects
/// Run hooks on the specified file paths.
///
/// - `project-path/`: Skip all hooks from the specified project
///
/// - `project-path:hook-id`: Skip only the specified hook from the specified project
///
/// Can be specified multiple times. Also accepts `PREK_SKIP` or `SKIP` environment variables (comma-delimited).
#[arg(long = "skip", value_name = "HOOK|PROJECT", add = ArgValueCompleter::new(selector_completer))]
pub(crate) skips: Vec<String>,

/// Run on all files in the repo.
#[arg(short, long, conflicts_with_all = ["files", "from_ref", "to_ref"])]
pub(crate) all_files: bool,
/// Specific filenames to run hooks on.
/// Paths are resolved relative to the current working directory after applying `--cd`. They may
/// be tracked or untracked. This option accepts multiple paths and can be combined with
/// `--glob` and `--directory`.
#[arg(
long,
conflicts_with_all = ["all_files", "from_ref", "to_ref"],
Expand All @@ -514,9 +486,22 @@ pub(crate) struct RunOptions {
]
pub(crate) files: Vec<String>,

/// Run hooks on all files in the specified directories.
/// Run hooks on tracked files matching the specified glob pattern.
///
/// Patterns are matched against paths relative to the current working directory after applying
/// `--cd`. Quote patterns to prevent shell expansion. This option can be repeated and combined
/// with `--files` and `--directory`.
#[arg(
long,
value_name = "PATTERN",
conflicts_with_all = ["all_files", "from_ref", "to_ref"]
)]
pub(crate) glob: Vec<Glob>,

/// Run hooks on tracked files under the specified directory.
///
/// You can specify multiple directories. It can be used in conjunction with `--files`.
/// Paths are resolved relative to the current working directory after applying `--cd`. This
/// option can be repeated and combined with `--files` and `--glob`.
#[arg(
short,
long,
Expand Down Expand Up @@ -544,8 +529,87 @@ pub(crate) struct RunOptions {
pub(crate) to_ref: Option<String>,

/// Run hooks against the last commit. Equivalent to `--from-ref HEAD~1 --to-ref HEAD`.
#[arg(long, conflicts_with_all = ["all_files", "files", "directory", "from_ref", "to_ref"])]
#[arg(long, conflicts_with_all = ["all_files", "files", "glob", "directory", "from_ref", "to_ref"])]
pub(crate) last_commit: bool,
}

impl From<FileSelectionArgs> for run::FileSelection {
fn from(args: FileSelectionArgs) -> Self {
let FileSelectionArgs {
all_files,
files,
glob,
directory,
from_ref,
to_ref,
last_commit,
} = args;

if last_commit {
return Self::Diff {
from_ref: "HEAD~1".to_string(),
to_ref: "HEAD".to_string(),
};
}

let (from_ref, to_ref) = match (from_ref, to_ref) {
(Some(from_ref), Some(to_ref)) => return Self::Diff { from_ref, to_ref },
refs => refs,
};

if !files.is_empty() || !glob.is_empty() || !directory.is_empty() {
return Self::Explicit {
files,
globs: glob,
directories: directory,
};
}

if all_files {
Self::All { from_ref, to_ref }
} else {
Self::Default
}
}
}

#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, Default, Args)]
pub(crate) struct RunOptions {
/// Include the specified hooks or projects.
///
/// Supports flexible selector syntax:
///
/// - `hook-id`: Run all hooks with the specified ID across all projects
///
/// - `project-path/`: Run all hooks from the specified project
///
/// - `project-path:hook-id`: Run only the specified hook from the specified project
///
/// Can be specified multiple times to select multiple hooks/projects.
#[arg(
value_name = "HOOK|PROJECT",
value_hint = ValueHint::Other,
add = ArgValueCompleter::new(selector_completer)
)]
pub(crate) includes: Vec<String>,

/// Skip the specified hooks or projects.
///
/// Supports flexible selector syntax:
///
/// - `hook-id`: Skip all hooks with the specified ID across all projects
///
/// - `project-path/`: Skip all hooks from the specified project
///
/// - `project-path:hook-id`: Skip only the specified hook from the specified project
///
/// Can be specified multiple times. Also accepts `PREK_SKIP` or `SKIP` environment variables (comma-delimited).
#[arg(long = "skip", value_name = "HOOK|PROJECT", add = ArgValueCompleter::new(selector_completer))]
pub(crate) skips: Vec<String>,

#[command(flatten)]
pub(crate) file_selection: FileSelectionArgs,

/// When hooks fail, run `git diff` directly afterward.
#[arg(long)]
Expand Down
Loading
Loading