diff --git a/crates/prek/src/cli/install.rs b/crates/prek/src/cli/install.rs index ee0e691c6..c742a774b 100644 --- a/crates/prek/src/cli/install.rs +++ b/crates/prek/src/cli/install.rs @@ -18,7 +18,7 @@ use crate::fs::{CWD, Simplified}; use crate::git::{GIT_ROOT, git_cmd}; use crate::printer::Printer; use crate::store::Store; -use crate::workspace::{Project, Workspace}; +use crate::workspace::{Error as WorkspaceError, Project, Workspace}; use crate::{git, warn_user}; #[allow(clippy::fn_params_excessive_bools)] @@ -43,7 +43,15 @@ pub(crate) async fn install( ); } - let project = Project::discover(config.as_deref(), &CWD).ok(); + let project = match Project::discover(config.as_deref(), &CWD) { + Ok(project) => Some(project), + Err(err) => { + if let WorkspaceError::Config(err) = &err { + err.warn_parse_error(); + } + None + } + }; let hook_types = get_hook_types(hook_types, project.as_ref(), config.as_deref()); let hooks_path = if let Some(dir) = git_dir { @@ -133,13 +141,17 @@ fn get_hook_types( .iter() .map(Path::new) .filter(|p| p.exists()); - config - .into_iter() - .chain(fallbacks) - .next() - .and_then(|p| load_config(p).ok()) - .and_then(|cfg| cfg.default_install_hook_types.clone()) - .unwrap_or_default() + if let Some(path) = config.into_iter().chain(fallbacks).next() { + match load_config(path) { + Ok(cfg) => cfg.default_install_hook_types.clone().unwrap_or_default(), + Err(err) => { + err.warn_parse_error(); + vec![] + } + } + } else { + vec![] + } }; if hook_types.is_empty() { hook_types = vec![HookType::PreCommit]; diff --git a/crates/prek/src/config.rs b/crates/prek/src/config.rs index 9c2490382..e73d5f3ae 100644 --- a/crates/prek/src/config.rs +++ b/crates/prek/src/config.rs @@ -1,6 +1,7 @@ #[cfg(feature = "schemars")] use std::borrow::Cow; use std::collections::BTreeMap; +use std::error::Error as _; use std::fmt::Display; use std::ops::RangeInclusive; use std::path::Path; @@ -17,6 +18,7 @@ use serde::{Deserialize, Deserializer, Serialize}; use crate::fs::Simplified; use crate::version; use crate::warn_user; +use crate::warn_user_once; use crate::{identify, yaml}; pub(crate) static CONFIG_FILE_REGEX: LazyLock = LazyLock::new(|| { @@ -1011,6 +1013,20 @@ pub(crate) enum Error { YamlMerge(String, #[source] yaml::MergeKeyError), } +impl Error { + /// Warn the user if the config error is a parse error (not "file not found"). + pub(crate) fn warn_parse_error(&self) { + if matches!(self, Self::NotFound(_)) { + return; + } + if let Some(cause) = self.source() { + warn_user_once!("{self}: {cause}"); + } else { + warn_user_once!("{self}"); + } + } +} + /// Keys that prek does not use. const EXPECTED_UNUSED: &[&str] = &["minimum_pre_commit_version", "ci"]; diff --git a/crates/prek/tests/install.rs b/crates/prek/tests/install.rs index 8e82c1241..af80fea86 100644 --- a/crates/prek/tests/install.rs +++ b/crates/prek/tests/install.rs @@ -883,3 +883,29 @@ fn workspace_init_template_dir() -> anyhow::Result<()> { Ok(()) } + +/// Test that a warning is shown when the config file exists but is invalid. +#[test] +fn install_invalid_config_warning() { + let context = TestContext::new(); + context.init_project(); + + // Write an invalid config (missing required `rev` field). + context.write_pre_commit_config(indoc! {r" + repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + hooks: + - id: trailing-whitespace + "}); + + // Install should succeed but show a warning about the invalid config. + cmd_snapshot!(context.filters(), context.install(), @r#" + success: true + exit_code: 0 + ----- stdout ----- + prek installed at `.git/hooks/pre-commit` + + ----- stderr ----- + warning: Failed to parse `.pre-commit-config.yaml`: Invalid remote repo: missing field `rev` + "#); +}