Skip to content
Open
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
1 change: 1 addition & 0 deletions crates/prek-consts/src/env_vars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ impl EnvVars {
pub const PREK_CONTAINER_RUNTIME: &'static str = "PREK_CONTAINER_RUNTIME";
pub const PREK_DOCKER_NO_INIT: &'static str = "PREK_DOCKER_NO_INIT";
pub const PREK_QUIET: &'static str = "PREK_QUIET";
pub const PREK_VERBOSE: &'static str = "PREK_VERBOSE";

// PREK internal environment variables
pub const PREK_INTERNAL__TEST_DIR: &'static str = "PREK_INTERNAL__TEST_DIR";
Expand Down
3 changes: 2 additions & 1 deletion crates/prek/src/cli/hook_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub(crate) async fn hook_impl(
skip_on_missing_config: bool,
script_version: Option<usize>,
args: Vec<OsString>,
verbose: bool,
printer: Printer,
) -> Result<ExitStatus> {
let stdin = read_hook_stdin(hook_type).await?;
Expand Down Expand Up @@ -138,7 +139,7 @@ pub(crate) async fn hook_impl(
false,
false,
run_args.extra,
false,
verbose,
printer,
)
.await?;
Expand Down
5 changes: 4 additions & 1 deletion crates/prek/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,10 @@ pub(crate) struct GlobalArgs {
pub quiet: u8,

/// Use verbose output.
#[arg(global = true, short, long, action = ArgAction::Count)]
///
/// When running hooks, this also prints a hook's `description`, if set, whenever the
/// hook fails, to help explain the failure.
#[arg(global = true, short, long, env = EnvVars::PREK_VERBOSE, action = ArgAction::Count)]
pub(crate) verbose: u8,

/// Write trace logs to the specified file.
Expand Down
10 changes: 10 additions & 0 deletions crates/prek/src/cli/run/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,16 @@ impl<'a> HookRunSession<'a> {
"{detail_prefix}{}",
format!("- hook id: {}", result.hook.id).dimmed()
)?;
if status == RunStatus::Failed
&& (self.verbose || result.hook.verbose)
&& let Some(description) = result.hook.description.as_deref()
{
writeln!(
stdout,
"{detail_prefix}{}",
format!("- description: {description}").dimmed()
)?;
}
if self.verbose || result.hook.verbose {
writeln!(
stdout,
Expand Down
3 changes: 2 additions & 1 deletion crates/prek/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,8 @@ pub(crate) struct HookOptions {
/// Append filenames that would be checked to the hook entry as arguments.
/// Default is true.
pub pass_filenames: Option<PassFilenames>,
/// A description of the hook. For metadata only.
/// A description of the hook, shown in listings and printed on failure when verbose output is
/// enabled (via `--verbose`/`PREK_VERBOSE` or `verbose: true`).
pub description: Option<String>,
/// Run the hook on a specific version of the language.
/// Default is `default`.
Expand Down
1 change: 1 addition & 0 deletions crates/prek/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
args.skip_on_missing_config,
args.script_version,
args.args,
cli.globals.verbose > 0,
printer,
)
.await
Expand Down
56 changes: 56 additions & 0 deletions crates/prek/tests/hook_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,62 @@ fn hook_impl() {
");
}

/// Test that `PREK_VERBOSE=1` makes a Git-triggered hook (e.g. `git commit`) print a
/// failing hook's `description`, since Git does not let users pass CLI flags like
/// `--verbose` through to the underlying `prek hook-impl` invocation.
#[test]
fn hook_impl_verbose_env_var_prints_hook_description() {
let context = TestContext::new();
context.init_project();
context.write_pre_commit_config(indoc! { r"
repos:
- repo: local
hooks:
- id: fail
name: fail
description: This hook always fails.
language: fail
entry: always fail
always_run: true
"});

context.git_add(".");

let mut commit = git_cmd(context.work_dir());
commit
.arg("commit")
.env(EnvVars::PREK_HOME, &**context.home_dir())
.env(EnvVars::PREK_VERBOSE, "1")
.arg("-m")
.arg("Initial commit");

cmd_snapshot!(context.filters(), context.install(), @r#"
success: true
exit_code: 0
----- stdout -----
prek installed at `.git/hooks/pre-commit`

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

cmd_snapshot!(context.filters(), commit, @r"
success: false
exit_code: 1
----- stdout -----

----- stderr -----
fail.....................................................................Failed
- hook id: fail
- description: This hook always fails.
- duration: [TIME]
- exit code: 1

always fail

.pre-commit-config.yaml
");
}

#[test]
fn hook_impl_allows_missing_hook_dir() -> anyhow::Result<()> {
let context = TestContext::new();
Expand Down
99 changes: 99 additions & 0 deletions crates/prek/tests/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1519,6 +1519,105 @@ fn fail_fast_cli_flag() {
");
}

/// Test that `--verbose` prints a hook's `description` when it fails, to help
/// explain the failure without needing to inspect the config file.
#[test]
fn verbose_prints_hook_description_on_failure() {
let context = TestContext::new();
context.init_project();

context.write_pre_commit_config(indoc::indoc! {r#"
repos:
- repo: local
hooks:
- id: failing-hook
name: failing-hook
description: Checks that things are correct.
language: system
entry: python3 -c 'print("Failed"); exit(1)'
always_run: true
- id: passing-hook
name: passing-hook
description: This one always passes.
language: system
entry: python3 -c 'print("Passed")'
always_run: true
"#});
context.git_add(".");

// Without `--verbose`, the description is not shown.
cmd_snapshot!(context.filters(), context.run(), @r"
success: false
exit_code: 1
----- stdout -----
failing-hook.............................................................Failed
- hook id: failing-hook
- exit code: 1

Failed
passing-hook.............................................................Passed

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

// With `--verbose`, the description is shown only for the failing hook.
cmd_snapshot!(context.filters(), context.run().arg("--verbose"), @r"
success: false
exit_code: 1
----- stdout -----
failing-hook.............................................................Failed
- hook id: failing-hook
- description: Checks that things are correct.
- duration: [TIME]
- exit code: 1

Failed
passing-hook.............................................................Passed
- hook id: passing-hook
- duration: [TIME]

Passed

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

/// Test that `PREK_VERBOSE` environment variable enables the same behavior as `--verbose`,
/// which is needed since Git-triggered hooks (e.g. `git commit`) can't be passed CLI flags.
#[test]
fn prek_verbose_env_var_prints_hook_description_on_failure() {
let context = TestContext::new();
context.init_project();

context.write_pre_commit_config(indoc::indoc! {r#"
repos:
- repo: local
hooks:
- id: failing-hook
name: failing-hook
description: Checks that things are correct.
language: system
entry: python3 -c 'print("Failed"); exit(1)'
always_run: true
"#});
context.git_add(".");

cmd_snapshot!(context.filters(), context.run().env(EnvVars::PREK_VERBOSE, "1"), @r"
success: false
exit_code: 1
----- stdout -----
failing-hook.............................................................Failed
- hook id: failing-hook
- description: Checks that things are correct.
- duration: [TIME]
- exit code: 1

Failed

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

/// Test --no-fail-fast CLI flag overrides config-level `fail_fast`.
#[test]
fn no_fail_fast_cli_flag() {
Expand Down
2 changes: 1 addition & 1 deletion docs/authoring-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ each manifest hook:
| `always_run` | No | No | boolean | Run even when no files match. |
| `fail_fast` | No | No | boolean | Stop the run immediately if this hook fails. |
| `pass_filenames` | No | No | boolean or positive integer | Control whether, or how many, matching filenames are passed. |
| `description` | No | No | string | Free-form metadata shown in listings. |
| `description` | No | No | string | Free-form metadata shown in listings; printed on failure when `--verbose` is enabled. |
| `language_version` | No | No | string | Language/toolchain version request. |
| `log_file` | No | No | string path | Write hook output to a file when the hook fails or is verbose. |
| `require_serial` | No | No | boolean | Avoid concurrent invocations of this hook. |
Expand Down
Loading
Loading