Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package prek for openSUSE:Factory checked in at 2025-11-20 14:50:05 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/prek (Old) and /work/SRC/openSUSE:Factory/.prek.new.2061 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "prek" Thu Nov 20 14:50:05 2025 rev:2 rq:1318710 version:0.2.17 Changes: -------- --- /work/SRC/openSUSE:Factory/prek/prek.changes 2025-11-19 14:56:19.294189040 +0100 +++ /work/SRC/openSUSE:Factory/.prek.new.2061/prek.changes 2025-11-20 14:51:41.269691174 +0100 @@ -1,0 +2,18 @@ +Wed Nov 19 09:54:27 UTC 2025 - Johannes Kastl <[email protected]> + +- Update to version 0.2.17: + * Bug fixes + - Revert back to use serde_yaml again (#1112) + +------------------------------------------------------------------- +Tue Nov 18 12:22:59 UTC 2025 - Johannes Kastl <[email protected]> + +- Update to version 0.2.16: + * Bug fixes + - Disallow hook-level minimum_prek_version (#1101) + - Do not require a project in prek init-template-dir (#1109) + - Make sure uv pip install uses the Python from virtualenv + (#1108) + - Restore using serde_yaml in check-yaml hook (#1106) + +------------------------------------------------------------------- Old: ---- prek-0.2.15.obscpio New: ---- prek-0.2.17.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ prek.spec ++++++ --- /var/tmp/diff_new_pack.UE6P40/_old 2025-11-20 14:51:42.981763391 +0100 +++ /var/tmp/diff_new_pack.UE6P40/_new 2025-11-20 14:51:42.981763391 +0100 @@ -17,7 +17,7 @@ Name: prek -Version: 0.2.15 +Version: 0.2.17 Release: 0 Summary: Reimagined version of pre-commit, built in Rust License: MIT ++++++ _service ++++++ --- /var/tmp/diff_new_pack.UE6P40/_old 2025-11-20 14:51:43.025765247 +0100 +++ /var/tmp/diff_new_pack.UE6P40/_new 2025-11-20 14:51:43.029765416 +0100 @@ -3,7 +3,7 @@ <param name="url">https://github.com/j178/prek.git</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v0.2.15</param> + <param name="revision">v0.2.17</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.UE6P40/_old 2025-11-20 14:51:43.053766429 +0100 +++ /var/tmp/diff_new_pack.UE6P40/_new 2025-11-20 14:51:43.061766766 +0100 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/j178/prek.git</param> - <param name="changesrevision">11f369ed7a209cd5e79bab764b3b173395272df2</param></service></servicedata> + <param name="changesrevision">5110eb9fc14b39e89c5310b177cb35c9cb9407da</param></service></servicedata> (No newline at EOF) ++++++ prek-0.2.15.obscpio -> prek-0.2.17.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prek-0.2.15/.pre-commit-config.yaml new/prek-0.2.17/.pre-commit-config.yaml --- old/prek-0.2.15/.pre-commit-config.yaml 2025-11-17 11:41:36.000000000 +0100 +++ new/prek-0.2.17/.pre-commit-config.yaml 2025-11-18 16:04:44.000000000 +0100 @@ -9,6 +9,7 @@ - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: + - id: check-yaml - id: trailing-whitespace - id: end-of-file-fixer exclude: docs/cli.md # Exclude generated doc diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prek-0.2.15/CHANGELOG.md new/prek-0.2.17/CHANGELOG.md --- old/prek-0.2.15/CHANGELOG.md 2025-11-17 11:41:36.000000000 +0100 +++ new/prek-0.2.17/CHANGELOG.md 2025-11-18 16:04:44.000000000 +0100 @@ -1,5 +1,32 @@ # Changelog +## 0.2.17 + +Released on 2025-11-18. + +### Bug fixes + +- Revert back to use `serde_yaml` again ([#1112](https://github.com/j178/prek/pull/1112)) + +### Contributors + +- @j178 + +## 0.2.16 + +Released on 2025-11-18. + +### Bug fixes + +- Disallow hook-level `minimum_prek_version` ([#1101](https://github.com/j178/prek/pull/1101)) +- Do not require a project in `prek init-template-dir` ([#1109](https://github.com/j178/prek/pull/1109)) +- Make sure `uv pip install` uses the Python from virtualenv ([#1108](https://github.com/j178/prek/pull/1108)) +- Restore using `serde_yaml` in `check-yaml` hook ([#1106](https://github.com/j178/prek/pull/1106)) + +### Contributors + +- @j178 + ## 0.2.15 Released on 2025-11-17. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prek-0.2.15/Cargo.lock new/prek-0.2.17/Cargo.lock --- old/prek-0.2.15/Cargo.lock 2025-11-17 11:41:36.000000000 +0100 +++ new/prek-0.2.17/Cargo.lock 2025-11-18 16:04:44.000000000 +0100 @@ -114,12 +114,6 @@ ] [[package]] -name = "arraydeque" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" - -[[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -400,9 +394,9 @@ [[package]] name = "clap" -version = "4.5.51" +version = "4.5.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" +checksum = "aa8120877db0e5c011242f96806ce3c94e0737ab8108532a76a3300a01db2ab8" dependencies = [ "clap_builder", "clap_derive", @@ -410,9 +404,9 @@ [[package]] name = "clap_builder" -version = "4.5.51" +version = "4.5.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" +checksum = "02576b399397b659c26064fbc92a75fede9d18ffd5f80ca1cd74ddab167016e1" dependencies = [ "anstream", "anstyle", @@ -764,12 +758,6 @@ [[package]] name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "foldhash" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" @@ -973,29 +961,11 @@ [[package]] name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash 0.1.5", -] - -[[package]] -name = "hashbrown" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" [[package]] -name = "hashlink" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" -dependencies = [ - "hashbrown 0.15.5", -] - -[[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1082,7 +1052,7 @@ "itoa", "pin-project-lite", "pin-utils", - "smallvec 1.15.1", + "smallvec", "tokio", "want", ] @@ -1165,7 +1135,7 @@ "icu_normalizer_data", "icu_properties", "icu_provider", - "smallvec 1.15.1", + "smallvec", "zerovec", ] @@ -1217,7 +1187,7 @@ checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", - "smallvec 1.15.1", + "smallvec", "utf8_iter", ] @@ -1266,7 +1236,7 @@ checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown", ] [[package]] @@ -1631,12 +1601,6 @@ ] [[package]] -name = "nohash-hasher" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" - -[[package]] name = "normalize-line-endings" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1813,7 +1777,7 @@ "log", "nix 0.26.4", "once_cell", - "smallvec 1.15.1", + "smallvec", "spin", "symbolic-demangle", "tempfile", @@ -1861,7 +1825,7 @@ [[package]] name = "prek" -version = "0.2.15" +version = "0.2.17" dependencies = [ "anstream", "anyhow", @@ -1910,11 +1874,11 @@ "same-file", "semver", "serde", - "serde-saphyr", "serde_json", "serde_stacker", + "serde_yaml", "shlex", - "smallvec 1.15.1", + "smallvec", "target-lexicon", "tempfile", "textwrap", @@ -1930,14 +1894,14 @@ [[package]] name = "prek-consts" -version = "0.2.15" +version = "0.2.17" dependencies = [ "tracing", ] [[package]] name = "prek-pty" -version = "0.2.15" +version = "0.2.17" dependencies = [ "rustix", "tokio", @@ -2316,16 +2280,6 @@ ] [[package]] -name = "saphyr-parser" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb771b59f6b1985d1406325ec28f97cfb14256abcec4fdfb37b36a1766d6af7" -dependencies = [ - "arraydeque", - "hashlink", -] - -[[package]] name = "schannel" version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2395,22 +2349,6 @@ ] [[package]] -name = "serde-saphyr" -version = "0.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd76af9505b2498740576f95f60b3b4e2c469b5b677a8d2dd1d2da18b58193de" -dependencies = [ - "base64", - "nohash-hasher", - "num-traits", - "ryu", - "saphyr-parser", - "serde", - "serde_json", - "smallvec 2.0.0-alpha.11", -] - -[[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2476,6 +2414,19 @@ ] [[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] name = "sharded-slab" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2524,12 +2475,6 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] -name = "smallvec" -version = "2.0.0-alpha.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87b96efa4bd6bdd2ff0c6615cc36fc4970cbae63cfd46ddff5cee35a1b4df570" - -[[package]] name = "smawk" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2593,9 +2538,9 @@ [[package]] name = "symbolic-common" -version = "12.16.3" +version = "12.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d03f433c9befeea460a01d750e698aa86caf86dcfbd77d552885cd6c89d52f50" +checksum = "b3d8046c5674ab857104bc4559d505f4809b8060d57806e45d49737c97afeb60" dependencies = [ "debugid", "memmap2", @@ -2605,9 +2550,9 @@ [[package]] name = "symbolic-demangle" -version = "12.16.3" +version = "12.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13d359ef6192db1760a34321ec4f089245ede4342c27e59be99642f12a859de8" +checksum = "1accb6e5c4b0f682de907623912e616b44be1c9e725775155546669dbff720ec" dependencies = [ "cpp_demangle", "rustc-demangle", @@ -2833,7 +2778,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ - "foldhash 0.2.0", + "foldhash", "indexmap", "serde_core", "serde_spanned", @@ -2966,7 +2911,7 @@ "once_cell", "regex-automata", "sharded-slab", - "smallvec 1.15.1", + "smallvec", "thread_local", "tracing", "tracing-core", @@ -3011,9 +2956,15 @@ [[package]] name = "unit-prefix" -version = "0.5.1" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "untrusted" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prek-0.2.15/Cargo.toml new/prek-0.2.17/Cargo.toml --- old/prek-0.2.15/Cargo.toml 2025-11-17 11:41:36.000000000 +0100 +++ new/prek-0.2.17/Cargo.toml 2025-11-18 16:04:44.000000000 +0100 @@ -2,18 +2,17 @@ members = ["crates/*"] [workspace.package] -version = "0.2.15" +version = "0.2.17" edition = "2024" repository = "https://github.com/j178/prek" homepage = "https://prek.j178.dev/" license = "MIT" [workspace.dependencies] -prek-consts = { path = "crates/prek-consts", version = "0.2.15" } -prek-pty = { path = "crates/prek-pty", version = "0.2.15" } +prek-consts = { path = "crates/prek-consts", version = "0.2.17" } +prek-pty = { path = "crates/prek-pty", version = "0.2.17" } rustix = { version = "1.0.8", features = ["pty", "process", "fs", "termios"] } -serde-saphyr = { version = "0.0.7" } thiserror = { version = "2.0.11" } tokio = { version = "1.47.1", features = ["fs", "process", "rt", "sync", "macros", "net"] } tracing = { version = "0.1.40" } @@ -81,7 +80,7 @@ serde = { version = "1.0.210", features = ["derive"] } serde_json = { version = "1.0.132", features = ["unbounded_depth"] } serde_stacker = { version = "0.1.12" } -serde-saphyr = { workspace = true } +serde_yaml = { version = "0.9.34" } shlex = { version = "1.3.0" } smallvec = { version = "1.15.1" } target-lexicon = { version = "0.13.0" } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prek-0.2.15/README.md new/prek-0.2.17/README.md --- old/prek-0.2.15/README.md 2025-11-17 11:41:36.000000000 +0100 +++ new/prek-0.2.17/README.md 2025-11-18 16:04:44.000000000 +0100 @@ -58,7 +58,7 @@ <!-- linux-standalone-install:start --> ```bash -curl --proto '=https' --tlsv1.2 -LsSf https://github.com/j178/prek/releases/download/v0.2.15-alpha.3/prek-installer.sh | sh +curl --proto '=https' --tlsv1.2 -LsSf https://github.com/j178/prek/releases/download/v0.2.17/prek-installer.sh | sh ``` <!-- linux-standalone-install:end --> @@ -66,7 +66,7 @@ <!-- windows-standalone-install:start --> ```powershell -powershell -ExecutionPolicy ByPass -c "irm https://github.com/j178/prek/releases/download/v0.2.15-alpha.3/prek-installer.ps1 | iex" +powershell -ExecutionPolicy ByPass -c "irm https://github.com/j178/prek/releases/download/v0.2.17/prek-installer.ps1 | iex" ``` <!-- windows-standalone-install:end --> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prek-0.2.15/crates/prek-consts/src/env_vars.rs new/prek-0.2.17/crates/prek-consts/src/env_vars.rs --- old/prek-0.2.15/crates/prek-consts/src/env_vars.rs 2025-11-17 11:41:36.000000000 +0100 +++ new/prek-0.2.17/crates/prek-consts/src/env_vars.rs 2025-11-18 16:04:44.000000000 +0100 @@ -33,7 +33,10 @@ pub const PREK_INTERNAL__NODE_BINARY_NAME: &'static str = "PREK_INTERNAL__NODE_BINARY_NAME"; pub const PREK_GENERATE: &'static str = "PREK_GENERATE"; - // UV related + // Python & uv related + pub const VIRTUAL_ENV: &'static str = "VIRTUAL_ENV"; + pub const PYTHONHOME: &'static str = "PYTHONHOME"; + pub const UV_PYTHON: &'static str = "UV_PYTHON"; pub const UV_CACHE_DIR: &'static str = "UV_CACHE_DIR"; pub const UV_PYTHON_INSTALL_DIR: &'static str = "UV_PYTHON_INSTALL_DIR"; pub const UV_MANAGED_PYTHON: &'static str = "UV_MANAGED_PYTHON"; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prek-0.2.15/pyproject.toml new/prek-0.2.17/pyproject.toml --- old/prek-0.2.15/pyproject.toml 2025-11-17 11:41:36.000000000 +0100 +++ new/prek-0.2.17/pyproject.toml 2025-11-18 16:04:44.000000000 +0100 @@ -1,6 +1,6 @@ [project] name = "prek" -version = "0.2.15" +version = "0.2.17" description = "Better `pre-commit`, re-engineered in Rust" authors = [{ name = "j178", email = "[email protected]" }] requires-python = ">=3.8" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prek-0.2.15/src/builtin/pre_commit_hooks/check_yaml.rs new/prek-0.2.17/src/builtin/pre_commit_hooks/check_yaml.rs --- old/prek-0.2.15/src/builtin/pre_commit_hooks/check_yaml.rs 2025-11-17 11:41:36.000000000 +0100 +++ new/prek-0.2.17/src/builtin/pre_commit_hooks/check_yaml.rs 2025-11-18 16:04:44.000000000 +0100 @@ -3,6 +3,7 @@ use anyhow::Result; use clap::Parser; use futures::StreamExt; +use serde::Deserialize; use crate::hook::Hook; use crate::run::CONCURRENCY; @@ -53,16 +54,19 @@ if content.is_empty() { return Ok((0, Vec::new())); } - let content = String::from_utf8(content)?; + let deserializer = serde_yaml::Deserializer::from_slice(&content); if allow_multi_docs { - if let Err(e) = serde_saphyr::from_multiple::<serde_json::Value>(&content) { - let error_message = format!("{}: Failed to yaml decode ({e})\n", filename.display()); - return Ok((1, error_message.into_bytes())); + for doc in deserializer { + if let Err(e) = serde_yaml::Value::deserialize(doc) { + let error_message = + format!("{}: Failed to yaml decode ({e})\n", filename.display()); + return Ok((1, error_message.into_bytes())); + } } Ok((0, Vec::new())) } else { - match serde_saphyr::from_str::<serde_json::Value>(&content) { + match serde_yaml::from_slice::<serde_yaml::Value>(&content) { Ok(_) => Ok((0, Vec::new())), Err(e) => { let error_message = @@ -158,5 +162,29 @@ assert_eq!(code, 0); assert!(output.is_empty()); Ok(()) + } + + #[tokio::test] + async fn test_yaml_with_binary_scalar() -> Result<()> { + let dir = tempdir()?; + let content = b"\ +response: + body: + string: !!binary | + H4sIAAAAAAAAA4xTPW/bMBDd9SsON9uFJaeJ4y0oujRIEXQpisiQaOokM6VIgjzFSQ3/94KSYzmt + A2TRwPfBd/eoXQKAqsIloNwIlq3T0y/rF6JfbXYT2m3rvan+NLfXt/zj2/f5NsVJVNj1I0l+VX2S + tnWaWFkzwNKTYIqu6dXlPL28mmeLHmhtRTrKGsfTCzvNZtnFNE2n2ewg3FglKeASHhIAgF3/jRFN + Rc+4hNnk9aSlEERDuDySANBbHU9QhKACC8M4GUFpDZPpU5dl+Risyc0uNwA5smJNOS4hxxu4Jx8c + SVZPBNbA12enhRFxugC2hjurSXZaeLj3VCkZAbiLg4UcJ4Of6HhjfYiODzn+JK3FVjATEIPQOa4O + vMqqyDGd1rnZ56Ysy9PEnuouCH1gnADCGMtDpHjF6oDsj9vRtnHersM/UqyVUWFTeBLBmriJwNZh + j+4TgFXfQvdmsei8bR0XbH9Tf91iPtjhWPsIzq8PIFsWejxPs2xyxq6oiIXS4aRGlEJuqBqlY+ei + q5Q9AZKTof9Pc857GFyZ5iP2IyAlOaaqcMfGz9E8xb/iPdpxyX1gDOSflKSCFflYREW16PTwYDG8 + BKa2qJVpyDuvhldbu0LOFtnicypnC0z2yV8AAAD//wMALvIkjL4DAAA= +"; + let file_path = create_test_file(&dir, "binary.yaml", content).await?; + let (code, output) = check_file(Path::new(""), &file_path, false).await?; + assert_eq!(code, 0); + assert!(output.is_empty()); + Ok(()) } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prek-0.2.15/src/cli/auto_update.rs new/prek-0.2.17/src/cli/auto_update.rs --- old/prek-0.2.15/src/cli/auto_update.rs 2025-11-17 11:41:36.000000000 +0100 +++ new/prek-0.2.17/src/cli/auto_update.rs 2025-11-18 16:04:44.000000000 +0100 @@ -1,9 +1,9 @@ -use std::collections::HashMap; use std::fmt::Write; use std::path::{Path, PathBuf}; use std::process::Stdio; use anyhow::{Context, Result}; +use bstr::ByteSlice; use futures::StreamExt; use itertools::Itertools; use lazy_regex::regex; @@ -11,6 +11,8 @@ use prek_consts::MANIFEST_FILE; use rustc_hash::FxHashMap; use rustc_hash::FxHashSet; +use serde::Serializer; +use serde::ser::SerializeMap; use tracing::trace; use crate::cli::ExitStatus; @@ -377,11 +379,15 @@ continue; }; - let new_rev: HashMap<String, String> = - HashMap::from([("rev".to_string(), revision.rev.clone())]); - let new_rev = serde_saphyr::to_string(&new_rev)?; + let mut new_rev = Vec::new(); + let mut serializer = serde_yaml::Serializer::new(&mut new_rev); + serializer + .serialize_map(Some(1))? + .serialize_entry("rev", &revision.rev)?; + serializer.end()?; let (_, new_rev) = new_rev + .to_str()? .split_once(':') .expect("Failed to split serialized revision"); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prek-0.2.15/src/cli/install.rs new/prek-0.2.17/src/cli/install.rs --- old/prek-0.2.15/src/cli/install.rs 2025-11-17 11:41:36.000000000 +0100 +++ new/prek-0.2.17/src/cli/install.rs 2025-11-18 16:04:44.000000000 +0100 @@ -6,12 +6,14 @@ use anyhow::{Context, Result}; use bstr::ByteSlice; use owo_colors::OwoColorize; +use prek_consts::CONFIG_FILE; use same_file::is_same_file; use crate::cli::reporter::{HookInitReporter, HookInstallReporter}; use crate::cli::run; use crate::cli::run::{SelectorSource, Selectors}; use crate::cli::{ExitStatus, HookType}; +use crate::config::read_config; use crate::fs::{CWD, Simplified}; use crate::git::{GIT_ROOT, git_cmd}; use crate::printer::Printer; @@ -42,7 +44,7 @@ } let project = Project::discover(config.as_deref(), &CWD).ok(); - let hook_types = get_hook_types(project.as_ref(), hook_types); + let hook_types = get_hook_types(project.as_ref(), config.as_deref(), hook_types); let hooks_path = if let Some(dir) = git_dir { dir.join("hooks") @@ -111,7 +113,11 @@ Ok(ExitStatus::Success) } -fn get_hook_types(project: Option<&Project>, hook_types: Vec<HookType>) -> Vec<HookType> { +fn get_hook_types( + project: Option<&Project>, + config: Option<&Path>, + hook_types: Vec<HookType>, +) -> Vec<HookType> { let mut hook_types = if hook_types.is_empty() { if let Some(project) = project { project @@ -120,7 +126,11 @@ .clone() .unwrap_or_default() } else { - vec![] + let config = config.unwrap_or(Path::new(CONFIG_FILE)); + read_config(config) + .ok() + .and_then(|cfg| cfg.default_install_hook_types.clone()) + .unwrap_or_default() } } else { hook_types @@ -319,7 +329,7 @@ let project = Project::discover(config.as_deref(), &CWD).ok(); let hooks_path = git::get_git_common_dir().await?.join("hooks"); - for hook_type in get_hook_types(project.as_ref(), hook_types) { + for hook_type in get_hook_types(project.as_ref(), config.as_deref(), hook_types) { let hook_path = hooks_path.join(hook_type.as_str()); let legacy_path = hooks_path.join(format!("{}.legacy", hook_type.as_str())); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prek-0.2.15/src/config.rs new/prek-0.2.17/src/config.rs --- old/prek-0.2.15/src/config.rs 2025-11-17 11:41:36.000000000 +0100 +++ new/prek-0.2.17/src/config.rs 2025-11-18 16:04:44.000000000 +0100 @@ -13,9 +13,9 @@ use serde::{Deserialize, Deserializer, Serialize}; use crate::fs::Simplified; -use crate::identify; use crate::version; use crate::warn_user; +use crate::{identify, yaml}; #[derive(Clone)] pub struct SerdeRegex(Regex); @@ -299,9 +299,6 @@ /// Print the output of the hook even if it passes. /// Default is false. pub verbose: Option<bool>, - /// The minimum version of prek required to run this hook. - #[serde(deserialize_with = "deserialize_and_validate_minimum_version", default)] - pub minimum_prek_version: Option<String>, #[serde(skip_serializing)] #[serde(flatten)] _unused_keys: BTreeMap<String, serde_json::Value>, @@ -337,7 +334,6 @@ require_serial, stages, verbose, - minimum_prek_version, ); } } @@ -643,7 +639,10 @@ Io(#[from] std::io::Error), #[error("Failed to parse `{0}`")] - Yaml(String, #[source] serde_saphyr::Error), + Yaml(String, #[source] serde_yaml::Error), + + #[error("Failed to merge keys in `{0}`")] + YamlMerge(String, #[source] yaml::MergeKeyError), } /// Keys that prek does not use. @@ -765,7 +764,13 @@ Err(e) => return Err(e.into()), }; - let config: Config = serde_saphyr::from_str(&content) + let config: serde_yaml::Value = serde_yaml::from_str(&content) + .map_err(|e| Error::Yaml(path.user_display().to_string(), e))?; + + let config = yaml::merge_keys(config) + .map_err(|e| Error::YamlMerge(path.user_display().to_string(), e))?; + + let config: Config = serde_yaml::from_value(config) .map_err(|e| Error::Yaml(path.user_display().to_string(), e))?; let unused_paths = collect_unused_paths(&config); @@ -814,7 +819,7 @@ /// Read the manifest file from the given path. pub fn read_manifest(path: &Path) -> Result<Manifest, Error> { let content = fs_err::read_to_string(path)?; - let manifest = serde_saphyr::from_str(&content) + let manifest = serde_yaml::from_str(&content) .map_err(|e| Error::Yaml(path.user_display().to_string(), e))?; Ok(manifest) } @@ -886,7 +891,7 @@ entry: cargo fmt -- language: system "}; - let result = serde_saphyr::from_str::<Config>(yaml); + let result = serde_yaml::from_str::<Config>(yaml); insta::assert_debug_snapshot!(result, @r#" Ok( Config { @@ -918,7 +923,6 @@ require_serial: None, stages: None, verbose: None, - minimum_prek_version: None, _unused_keys: {}, }, }, @@ -949,8 +953,8 @@ types: - rust "}; - let result = serde_saphyr::from_str::<Config>(yaml); - insta::assert_snapshot!(result.unwrap_err().to_string(), @"Invalid local repo: missing field `entry`"); + let result = serde_yaml::from_str::<Config>(yaml); + insta::assert_snapshot!(result.unwrap_err().to_string(), @"repos: Invalid local repo: missing field `entry` at line 2 column 3"); // Remote hook should have `rev`. let yaml = indoc::indoc! {r" @@ -960,7 +964,7 @@ hooks: - id: typos "}; - let result = serde_saphyr::from_str::<Config>(yaml); + let result = serde_yaml::from_str::<Config>(yaml); insta::assert_debug_snapshot!(result, @r#" Ok( Config { @@ -993,7 +997,6 @@ require_serial: None, stages: None, verbose: None, - minimum_prek_version: None, _unused_keys: {}, }, }, @@ -1020,8 +1023,8 @@ hooks: - id: typos "}; - let result = serde_saphyr::from_str::<Config>(yaml); - insta::assert_snapshot!(result.unwrap_err().to_string(), @"Invalid remote repo: missing field `rev`"); + let result = serde_yaml::from_str::<Config>(yaml); + insta::assert_snapshot!(result.unwrap_err().to_string(), @"repos: Invalid remote repo: missing field `rev` at line 2 column 3"); } #[test] @@ -1035,8 +1038,8 @@ - name: typos alias: typo "}; - let result = serde_saphyr::from_str::<Config>(yaml); - insta::assert_snapshot!(result.unwrap_err().to_string(), @"Invalid remote repo: missing field `id`"); + let result = serde_yaml::from_str::<Config>(yaml); + insta::assert_snapshot!(result.unwrap_err().to_string(), @"repos: Invalid remote repo: missing field `id` at line 2 column 3"); // Local hook should have `id`, `name`, and `entry` and `language`. let yaml = indoc::indoc! { r" @@ -1049,8 +1052,8 @@ types: - rust "}; - let result = serde_saphyr::from_str::<Config>(yaml); - insta::assert_snapshot!(result.unwrap_err().to_string(), @"Invalid local repo: missing field `language`"); + let result = serde_yaml::from_str::<Config>(yaml); + insta::assert_snapshot!(result.unwrap_err().to_string(), @"repos: Invalid local repo: missing field `language` at line 2 column 3"); let yaml = indoc::indoc! { r" repos: @@ -1061,7 +1064,7 @@ entry: cargo fmt language: rust "}; - let result = serde_saphyr::from_str::<Config>(yaml); + let result = serde_yaml::from_str::<Config>(yaml); insta::assert_debug_snapshot!(result, @r#" Ok( Config { @@ -1093,7 +1096,6 @@ require_serial: None, stages: None, verbose: None, - minimum_prek_version: None, _unused_keys: {}, }, }, @@ -1126,8 +1128,8 @@ - name: typos alias: typo "}; - let result = serde_saphyr::from_str::<Config>(yaml); - insta::assert_snapshot!(result.unwrap_err().to_string(), @"Invalid meta repo: missing field `id`"); + let result = serde_yaml::from_str::<Config>(yaml); + insta::assert_snapshot!(result.unwrap_err().to_string(), @"repos: Invalid meta repo: missing field `id` at line 2 column 3"); // Invalid meta hook id let yaml = indoc::indoc! { r" @@ -1136,8 +1138,8 @@ hooks: - id: hello "}; - let result = serde_saphyr::from_str::<Config>(yaml); - insta::assert_snapshot!(result.unwrap_err().to_string(), @"Invalid meta repo: unknown meta hook id `hello`"); + let result = serde_yaml::from_str::<Config>(yaml); + insta::assert_snapshot!(result.unwrap_err().to_string(), @"repos: Invalid meta repo: unknown meta hook id `hello` at line 2 column 3"); // Invalid language let yaml = indoc::indoc! { r" @@ -1147,8 +1149,8 @@ - id: check-hooks-apply language: python "}; - let result = serde_saphyr::from_str::<Config>(yaml); - insta::assert_snapshot!(result.unwrap_err().to_string(), @"Invalid meta repo: language must be `system` for meta hooks"); + let result = serde_yaml::from_str::<Config>(yaml); + insta::assert_snapshot!(result.unwrap_err().to_string(), @"repos: Invalid meta repo: language must be `system` for meta hooks at line 2 column 3"); // Invalid entry let yaml = indoc::indoc! { r" @@ -1158,8 +1160,8 @@ - id: check-hooks-apply entry: echo hell world "}; - let result = serde_saphyr::from_str::<Config>(yaml); - insta::assert_snapshot!(result.unwrap_err().to_string(), @"Invalid meta repo: entry is not allowed for meta hooks"); + let result = serde_yaml::from_str::<Config>(yaml); + insta::assert_snapshot!(result.unwrap_err().to_string(), @"repos: Invalid meta repo: entry is not allowed for meta hooks at line 2 column 3"); // Valid meta hook let yaml = indoc::indoc! { r" @@ -1170,7 +1172,7 @@ - id: check-useless-excludes - id: identity "}; - let result = serde_saphyr::from_str::<Config>(yaml); + let result = serde_yaml::from_str::<Config>(yaml); insta::assert_debug_snapshot!(result, @r#" Ok( Config { @@ -1207,7 +1209,6 @@ require_serial: None, stages: None, verbose: None, - minimum_prek_version: None, _unused_keys: {}, }, }, @@ -1240,7 +1241,6 @@ require_serial: None, stages: None, verbose: None, - minimum_prek_version: None, _unused_keys: {}, }, }, @@ -1271,7 +1271,6 @@ verbose: Some( true, ), - minimum_prek_version: None, _unused_keys: {}, }, }, @@ -1316,7 +1315,7 @@ language: system language_version: '3.8' "}; - let result = serde_saphyr::from_str::<Config>(yaml); + let result = serde_yaml::from_str::<Config>(yaml); insta::assert_debug_snapshot!(result, @r#" Ok( Config { @@ -1350,7 +1349,6 @@ require_serial: None, stages: None, verbose: None, - minimum_prek_version: None, _unused_keys: {}, }, }, @@ -1379,7 +1377,6 @@ require_serial: None, stages: None, verbose: None, - minimum_prek_version: None, _unused_keys: {}, }, }, @@ -1408,7 +1405,6 @@ require_serial: None, stages: None, verbose: None, - minimum_prek_version: None, _unused_keys: {}, }, }, @@ -1456,7 +1452,7 @@ entry: echo test language: system "}; - let result = serde_saphyr::from_str::<Config>(yaml); + let result = serde_yaml::from_str::<Config>(yaml); assert!(result.is_ok()); let config = result.unwrap(); assert!(config.minimum_prek_version.is_none()); @@ -1472,7 +1468,7 @@ language: system minimum_prek_version: '' "}; - let result = serde_saphyr::from_str::<Config>(yaml); + let result = serde_yaml::from_str::<Config>(yaml); assert!(result.is_ok()); let config = result.unwrap(); assert!(config.minimum_prek_version.is_none()); @@ -1488,21 +1484,7 @@ language: system minimum_prek_version: '10.0.0' "}; - let result = serde_saphyr::from_str::<Config>(yaml); - assert!(result.is_err()); - - // Test that valid minimum_prek_version field works in hook config - let yaml = indoc::indoc! {r" - repos: - - repo: local - hooks: - - id: test-hook - name: Test Hook - entry: echo test - language: system - minimum_prek_version: '10.0.0' - "}; - let result = serde_saphyr::from_str::<Config>(yaml); + let result = serde_yaml::from_str::<Config>(yaml); assert!(result.is_err()); } @@ -1521,7 +1503,7 @@ types_or: [text, binary] exclude_types: [symlink] "; - let result = serde_saphyr::from_str::<Config>(yaml_valid); + let result = serde_yaml::from_str::<Config>(yaml_valid); assert!(result.is_ok(), "Should parse valid tags successfully"); // Empty lists and missing keys should also be fine @@ -1537,7 +1519,7 @@ exclude_types: [] # types_or is missing, which is also valid "; - let result_empty = serde_saphyr::from_str::<Config>(yaml_empty); + let result_empty = serde_yaml::from_str::<Config>(yaml_empty); assert!( result_empty.is_ok(), "Should parse empty/missing tags successfully" @@ -1554,7 +1536,7 @@ language: system types: [pythoon] # Deliberate typo "; - let result_invalid_types = serde_saphyr::from_str::<Config>(yaml_invalid_types); + let result_invalid_types = serde_yaml::from_str::<Config>(yaml_invalid_types); assert!(result_invalid_types.is_err()); assert!( @@ -1575,7 +1557,7 @@ language: system types_or: [invalidtag] "; - let result_invalid_types_or = serde_saphyr::from_str::<Config>(yaml_invalid_types_or); + let result_invalid_types_or = serde_yaml::from_str::<Config>(yaml_invalid_types_or); assert!(result_invalid_types_or.is_err()); assert!( result_invalid_types_or @@ -1596,7 +1578,7 @@ exclude_types: [not-a-real-tag] "; let result_invalid_exclude_types = - serde_saphyr::from_str::<Config>(yaml_invalid_exclude_types); + serde_yaml::from_str::<Config>(yaml_invalid_exclude_types); assert!(result_invalid_exclude_types.is_err()); assert!( result_invalid_exclude_types @@ -1663,7 +1645,6 @@ require_serial: None, stages: None, verbose: None, - minimum_prek_version: None, _unused_keys: {}, }, }, @@ -1695,7 +1676,6 @@ require_serial: None, stages: None, verbose: None, - minimum_prek_version: None, _unused_keys: {}, }, }, @@ -1781,7 +1761,6 @@ ], ), verbose: None, - minimum_prek_version: None, _unused_keys: {}, }, }, @@ -1817,4 +1796,29 @@ Ok(()) } + + #[test] + fn test_list_with_unindented_square() { + let yaml = indoc::indoc! {r#" + repos: + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.18.2 + hooks: + - id: mypy + exclude: tests/data + args: [ "--pretty", "--show-error-codes" ] + additional_dependencies: [ + 'keyring==24.2.0', + 'nox==2024.03.02', + 'pytest', + 'types-docutils==0.20.0.3', + 'types-setuptools==68.2.0.0', + 'types-freezegun==1.1.10', + 'types-pyyaml==6.0.12.12', + 'typing-extensions', + ] + "#}; + let result = serde_yaml::from_str::<Config>(yaml); + assert!(result.is_ok()); + } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prek-0.2.15/src/hook.rs new/prek-0.2.17/src/hook.rs --- old/prek-0.2.15/src/hook.rs 2025-11-17 11:41:36.000000000 +0100 +++ new/prek-0.2.17/src/hook.rs 2025-11-18 16:04:44.000000000 +0100 @@ -305,7 +305,6 @@ log_file: options.log_file, require_serial: options.require_serial.expect("require_serial not set"), verbose: options.verbose.expect("verbose not set"), - minimum_prek_version: options.minimum_prek_version, }; if let Err(err) = extract_metadata_from_entry(&mut hook).await { @@ -415,7 +414,6 @@ pub require_serial: bool, pub stages: Stages, pub verbose: bool, - pub minimum_prek_version: Option<String>, } impl Display for Hook { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prek-0.2.15/src/languages/python/python.rs new/prek-0.2.17/src/languages/python/python.rs --- old/prek-0.2.15/src/languages/python/python.rs 2025-11-17 11:41:36.000000000 +0100 +++ new/prek-0.2.17/src/languages/python/python.rs 2025-11-18 16:04:44.000000000 +0100 @@ -103,7 +103,11 @@ .arg("install") .arg(".") .args(&hook.additional_dependencies) - .env("VIRTUAL_ENV", &info.env_path) + .env(EnvVars::VIRTUAL_ENV, &info.env_path) + // Make sure uv uses the venv's python + .env_remove(EnvVars::UV_PYTHON) + .env_remove(EnvVars::UV_MANAGED_PYTHON) + .env_remove(EnvVars::UV_NO_MANAGED_PYTHON) .check(true) .output() .await?; @@ -113,7 +117,11 @@ .arg("pip") .arg("install") .args(&hook.additional_dependencies) - .env("VIRTUAL_ENV", &info.env_path) + .env(EnvVars::VIRTUAL_ENV, &info.env_path) + // Make sure uv uses the venv's python + .env_remove(EnvVars::UV_PYTHON) + .env_remove(EnvVars::UV_MANAGED_PYTHON) + .env_remove(EnvVars::UV_NO_MANAGED_PYTHON) .check(true) .output() .await?; @@ -178,9 +186,9 @@ let mut output = Cmd::new(&entry[0], "python hook") .current_dir(hook.work_dir()) .args(&entry[1..]) - .env("VIRTUAL_ENV", env_dir) - .env("PATH", &new_path) - .env_remove("PYTHONHOME") + .env(EnvVars::VIRTUAL_ENV, env_dir) + .env(EnvVars::PATH, &new_path) + .env_remove(EnvVars::PYTHONHOME) .args(&hook.args) .args(batch) .check(false) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prek-0.2.15/src/main.rs new/prek-0.2.17/src/main.rs --- old/prek-0.2.15/src/main.rs 2025-11-17 11:41:36.000000000 +0100 +++ new/prek-0.2.17/src/main.rs 2025-11-18 16:04:44.000000000 +0100 @@ -44,6 +44,7 @@ mod version; mod warnings; mod workspace; +mod yaml; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub(crate) enum Level { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prek-0.2.15/src/snapshots/prek__config__tests__read_config.snap new/prek-0.2.17/src/snapshots/prek__config__tests__read_config.snap --- old/prek-0.2.15/src/snapshots/prek__config__tests__read_config.snap 2025-11-17 11:41:36.000000000 +0100 +++ new/prek-0.2.17/src/snapshots/prek__config__tests__read_config.snap 2025-11-18 16:04:44.000000000 +0100 @@ -32,7 +32,6 @@ require_serial: None, stages: None, verbose: None, - minimum_prek_version: None, _unused_keys: {}, }, }, @@ -68,7 +67,6 @@ require_serial: None, stages: None, verbose: None, - minimum_prek_version: None, _unused_keys: {}, }, }, @@ -109,7 +107,6 @@ require_serial: None, stages: None, verbose: None, - minimum_prek_version: None, _unused_keys: {}, }, }, @@ -154,7 +151,6 @@ require_serial: None, stages: None, verbose: None, - minimum_prek_version: None, _unused_keys: {}, }, }, @@ -195,7 +191,6 @@ require_serial: None, stages: None, verbose: None, - minimum_prek_version: None, _unused_keys: {}, }, }, @@ -231,7 +226,6 @@ require_serial: None, stages: None, verbose: None, - minimum_prek_version: None, _unused_keys: {}, }, }, @@ -263,7 +257,6 @@ require_serial: None, stages: None, verbose: None, - minimum_prek_version: None, _unused_keys: {}, }, }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prek-0.2.15/src/snapshots/prek__config__tests__read_manifest.snap new/prek-0.2.17/src/snapshots/prek__config__tests__read_manifest.snap --- old/prek-0.2.15/src/snapshots/prek__config__tests__read_manifest.snap 2025-11-17 11:41:36.000000000 +0100 +++ new/prek-0.2.17/src/snapshots/prek__config__tests__read_manifest.snap 2025-11-18 16:04:44.000000000 +0100 @@ -39,7 +39,6 @@ require_serial: None, stages: None, verbose: None, - minimum_prek_version: None, _unused_keys: { "minimum_pre_commit_version": String("2.9.2"), }, @@ -80,7 +79,6 @@ require_serial: None, stages: None, verbose: None, - minimum_prek_version: None, _unused_keys: { "minimum_pre_commit_version": String("2.9.2"), }, @@ -124,7 +122,6 @@ require_serial: None, stages: None, verbose: None, - minimum_prek_version: None, _unused_keys: { "minimum_pre_commit_version": String("2.9.2"), }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prek-0.2.15/src/yaml.rs new/prek-0.2.17/src/yaml.rs --- old/prek-0.2.15/src/yaml.rs 1970-01-01 01:00:00.000000000 +0100 +++ new/prek-0.2.17/src/yaml.rs 2025-11-18 16:04:44.000000000 +0100 @@ -0,0 +1,96 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Adapted from https://crates.io/crates/yaml-merge-keys to remove `yaml-rust2` from dependency. + +use serde_yaml::{Mapping, Sequence, Value}; + +/// Errors which may occur when performing the YAML merge key process. +/// +/// This enum is `non_exhaustive`, but cannot be marked as such until it is stable. In the +/// meantime, there is a hidden variant. +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +pub enum MergeKeyError { + /// A non-hash value was given as a value to merge into a hash. + /// + /// This happens with a document such as: + /// + /// ```yaml + /// - + /// <<: 4 + /// x: 1 + /// ``` + #[error("only mappings and arrays of mappings may be merged")] + InvalidMergeValue, +} + +/// Merge two hashes together. +fn merge_hashes(mut hash: Mapping, rhs: Mapping) -> Mapping { + rhs.into_iter().for_each(|(key, value)| { + hash.entry(key).or_insert(value); + }); + hash +} + +/// Merge values together. +fn merge_values(hash: Mapping, value: Value) -> Result<Mapping, MergeKeyError> { + let merge_values = match value { + Value::Sequence(arr) => { + let init: Result<Mapping, _> = Ok(Mapping::new()); + + arr.into_iter().fold(init, |res_hash, item| { + // Merge in the next item. + res_hash.and_then(move |res_hash| { + if let Value::Mapping(next_hash) = item { + Ok(merge_hashes(res_hash, next_hash)) + } else { + // Non-hash values at this level are not allowed. + Err(MergeKeyError::InvalidMergeValue) + } + }) + })? + } + Value::Mapping(merge_hash) => merge_hash, + _ => return Err(MergeKeyError::InvalidMergeValue), + }; + + Ok(merge_hashes(hash, merge_values)) +} + +/// Recurse into a hash and handle items with merge keys in them. +fn merge_hash(hash: Mapping) -> Result<Value, MergeKeyError> { + let mut hash = hash + .into_iter() + // First handle any merge keys in the key or value... + .map(|(key, value)| { + merge_keys(key).and_then(|key| merge_keys(value).map(|value| (key, value))) + }) + .collect::<Result<Mapping, _>>()?; + + if let Some(merge_value) = hash.remove("<<") { + merge_values(hash, merge_value).map(Value::Mapping) + } else { + Ok(Value::Mapping(hash)) + } +} + +/// Recurse into an array and handle items with merge keys in them. +fn merge_array(arr: Sequence) -> Result<Value, MergeKeyError> { + arr.into_iter() + .map(merge_keys) + .collect::<Result<Sequence, _>>() + .map(Value::Sequence) +} + +/// Handle merge keys in a YAML document. +pub fn merge_keys(doc: Value) -> Result<Value, MergeKeyError> { + match doc { + Value::Mapping(hash) => merge_hash(hash), + Value::Sequence(arr) => merge_array(arr), + _ => Ok(doc), + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prek-0.2.15/tests/auto_update.rs new/prek-0.2.17/tests/auto_update.rs --- old/prek-0.2.15/tests/auto_update.rs 2025-11-17 11:41:36.000000000 +0100 +++ new/prek-0.2.17/tests/auto_update.rs 2025-11-18 16:04:44.000000000 +0100 @@ -924,13 +924,13 @@ insta::with_settings!( { filters => filters.clone() }, { - assert_snapshot!(context.read(CONFIG_FILE), @r#" + assert_snapshot!(context.read(CONFIG_FILE), @r" repos: - repo: [HOME]/test-repos/test-repo - rev: "0.50" + rev: '0.50' hooks: - id: test-hook - "#); + "); } ); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prek-0.2.15/tests/builtin_hooks.rs new/prek-0.2.17/tests/builtin_hooks.rs --- old/prek-0.2.15/tests/builtin_hooks.rs 2025-11-17 11:41:36.000000000 +0100 +++ new/prek-0.2.17/tests/builtin_hooks.rs 2025-11-18 16:04:44.000000000 +0100 @@ -109,18 +109,18 @@ context.git_add("."); // First run: hooks should fail - cmd_snapshot!(context.filters(), context.run(), @r" + cmd_snapshot!(context.filters(), context.run(), @r#" success: false exit_code: 1 ----- stdout ----- check yaml...............................................................Failed - hook id: check-yaml - exit code: 1 - duplicate.yaml: Failed to yaml decode (duplicate mapping key: a at line 2, column 1) - invalid.yaml: Failed to yaml decode (mapping values are not allowed in this context at line 1, column 5) + duplicate.yaml: Failed to yaml decode (duplicate entry with key "a") + invalid.yaml: Failed to yaml decode (mapping values are not allowed in this context at line 1 column 5) ----- stderr ----- - "); + "#); // Fix the files cwd.child("invalid.yaml").write_str("a:\n b: c")?; @@ -182,7 +182,7 @@ Rust version.............................................................Failed - hook id: check-yaml - exit code: 1 - multiple.yaml: Failed to yaml decode (multiple YAML documents detected; use from_multiple or from_multiple_with_options at line 4, column 1) + multiple.yaml: Failed to yaml decode (deserializing from YAML containing more than one document is not supported) ----- stderr ----- "); @@ -554,7 +554,7 @@ context.git_add("."); // First run: expect failures and auto-fixes where applicable. - cmd_snapshot!(context.filters(), context.run(), @r" + cmd_snapshot!(context.filters(), context.run(), @r#" success: false exit_code: 1 ----- stdout ----- @@ -588,8 +588,8 @@ check yaml...............................................................Failed - hook id: check-yaml - exit code: 1 - duplicate.yaml: Failed to yaml decode (duplicate mapping key: a at line 2, column 1) - invalid.yaml: Failed to yaml decode (mapping values are not allowed in this context at line 1, column 5) + duplicate.yaml: Failed to yaml decode (duplicate entry with key "a") + invalid.yaml: Failed to yaml decode (mapping values are not allowed in this context at line 1 column 5) check json...............................................................Failed - hook id: check-json - exit code: 1 @@ -627,7 +627,7 @@ app/trailing_ws.txt ----- stderr ----- - "); + "#); // Fix YAML and JSON issues, then stage. app.child("invalid.yaml").write_str("a:\n b: c")?; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prek-0.2.15/tests/install.rs new/prek-0.2.17/tests/install.rs --- old/prek-0.2.15/tests/install.rs 2025-11-17 11:41:36.000000000 +0100 +++ new/prek-0.2.17/tests/install.rs 2025-11-18 16:04:44.000000000 +0100 @@ -572,6 +572,26 @@ ----- stderr ----- warning: git config `init.templateDir` not set to the target directory, try `git config --global init.templateDir '.git'` "#); + + context.write_pre_commit_config(indoc::indoc! {" + default_install_hook_types: + - pre-commit + - commit-msg + - pre-push + repos: + "}); + cmd_snapshot!(context.filters(), context.command().arg("init-template-dir").arg("-c").arg(context.work_dir().join(CONFIG_FILE)).arg(".git"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Overwriting existing hook at `.git/hooks/pre-commit` + prek installed at `.git/hooks/pre-commit` with specified config `[TEMP_DIR]/.pre-commit-config.yaml` + prek installed at `.git/hooks/commit-msg` with specified config `[TEMP_DIR]/.pre-commit-config.yaml` + prek installed at `.git/hooks/pre-push` with specified config `[TEMP_DIR]/.pre-commit-config.yaml` + + ----- stderr ----- + warning: git config `init.templateDir` not set to the target directory, try `git config --global init.templateDir '.git'` + "); } #[test] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prek-0.2.15/tests/validate.rs new/prek-0.2.17/tests/validate.rs --- old/prek-0.2.15/tests/validate.rs 2025-11-17 11:41:36.000000000 +0100 +++ new/prek-0.2.17/tests/validate.rs 2025-11-18 16:04:44.000000000 +0100 @@ -112,7 +112,7 @@ ----- stderr ----- error: Failed to parse `hooks-1.yaml` - caused by: missing field `entry` + caused by: .[0]: missing field `entry` at line 1 column 5 "); Ok(()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prek-0.2.15/tests/workspace.rs new/prek-0.2.17/tests/workspace.rs --- old/prek-0.2.15/tests/workspace.rs 2025-11-17 11:41:36.000000000 +0100 +++ new/prek-0.2.17/tests/workspace.rs 2025-11-18 16:04:44.000000000 +0100 @@ -765,7 +765,7 @@ ----- stderr ----- error: Failed to parse `project3/.pre-commit-config.yaml` - caused by: while parsing a node, did not find expected node content at line 2, column 1 + caused by: did not find expected node content at line 2 column 1, while parsing a flow node "); // Should skip the invalid config ++++++ prek.obsinfo ++++++ --- /var/tmp/diff_new_pack.UE6P40/_old 2025-11-20 14:51:43.469783977 +0100 +++ /var/tmp/diff_new_pack.UE6P40/_new 2025-11-20 14:51:43.493784989 +0100 @@ -1,5 +1,5 @@ name: prek -version: 0.2.15 -mtime: 1763376096 -commit: 11f369ed7a209cd5e79bab764b3b173395272df2 +version: 0.2.17 +mtime: 1763478284 +commit: 5110eb9fc14b39e89c5310b177cb35c9cb9407da ++++++ vendor.tar.zst ++++++ /work/SRC/openSUSE:Factory/prek/vendor.tar.zst /work/SRC/openSUSE:Factory/.prek.new.2061/vendor.tar.zst differ: char 7, line 1
