diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 65c9501d..b6bc5dfb 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -31,7 +31,7 @@ "requires_local_executor": true, "interface": { "displayName": "Mergify", - "shortDescription": "Run and debug your merge queue, stacked pull requests, and Mergify configuration from the terminal.", + "shortDescription": "Run and debug your merge queue", "longDescription": "Mergify serializes merges through a queue that runs CI on temporary merge commits, so a pull request that only breaks in combination with another one gets caught before it reaches your default branch. These skills drive the Mergify CLI so an agent can work that queue with you.\n\nAsk why a pull request left the queue and get the engine's own dequeue reason, the checks that failed, and a link straight to the job log. Read the activity log for one pull request or for the whole repository. Validate a .mergify.yml against the schema before you commit it, or simulate what your rules would do on a real pull request. Set up merge protections such as dependencies between pull requests or scheduled freezes for a release window. Turn a branch of commits into a stack of one pull request per commit and keep them rebased.\n\nSix skills ship in the bundle: mergify-merge-queue, mergify-events, mergify-config, mergify-merge-protections, mergify-ci, and mergify-stack.\n\nIt runs on the Mergify CLI, a single static binary with no runtime to install, so it needs a local execution environment: Codex, or ChatGPT desktop with local execution. You need a GitHub repository with Mergify enabled. Install the CLI with 'brew install mergifyio/tap/mergify-cli', sign in once with 'mergify auth login' or set MERGIFY_TOKEN, then ask for what you want in plain language.", "developerName": "Mergify", "category": "Developer Tools", diff --git a/Cargo.lock b/Cargo.lock index 9a347f08..0586678a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -909,12 +909,13 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" dependencies = [ "crc32fast", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -951,12 +952,12 @@ dependencies = [ [[package]] name = "fraction" -version = "0.15.4" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076045bb43dac435333ed5f04caf35c7463631d0dae2deb2638d94dd0a5b872" +checksum = "e246562084dde8ebbcc943b261c406ce4f68e5032ec28029a251a47d6a295500" dependencies = [ - "lazy_static", "num", + "num-bigint", ] [[package]] @@ -1586,9 +1587,9 @@ dependencies = [ [[package]] name = "jsonschema" -version = "0.49.9" +version = "0.55.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ec8a241beed129f06114aa68007e905ca350e7baeb6e17a7631bb7978d91b2" +checksum = "b68339c3d874e48151d74ffe256d93a58cffa240983cb0967d3cbaea083a44fe" dependencies = [ "ahash", "bytecount", @@ -1597,7 +1598,6 @@ dependencies = [ "fancy-regex", "fraction", "getrandom 0.3.4", - "idna", "itoa", "jsonschema-regex", "jsonschema-value", @@ -1615,25 +1615,27 @@ dependencies = [ [[package]] name = "jsonschema-regex" -version = "0.49.9" +version = "0.55.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91994f45017ed5e66aa8e59b8415f4cb033a6380d7200387b7cf117595fbdf85" +checksum = "6307b5b51216ec9b941b52244c74043fa0b1d6b657b56199f57cb1416d3641c5" dependencies = [ "regex-syntax", ] [[package]] name = "jsonschema-value" -version = "0.49.9" +version = "0.55.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ec7637f83e510868ae6ed625f7ebfbbde4554ee8ce49854caa5126a8b9b9ecb" +checksum = "0230ac05e09c6111e96c147b75c390579f5cbd45654b980c68ac60fe17b3f129" dependencies = [ "ahash", "bytecount", "fraction", + "getrandom 0.3.4", "num-cmp", "num-traits", "serde_json", + "zmij", ] [[package]] @@ -1945,9 +1947,9 @@ checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74" [[package]] name = "miniz_oxide" -version = "0.8.9" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" dependencies = [ "adler2", "simd-adler32", @@ -2001,9 +2003,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" dependencies = [ "num-integer", "num-traits", @@ -2351,9 +2353,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b" dependencies = [ "memchr", ] @@ -2533,9 +2535,9 @@ dependencies = [ [[package]] name = "referencing" -version = "0.49.9" +version = "0.55.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6efa2154ea6f5ce0fdecdd2a8d18f2fa1a39a8fbba91564f555a592e4dce8278" +checksum = "a196a5b4a8a12f46b6353174df865a05d41a6055aff212ec30877492788618b6" dependencies = [ "ahash", "fluent-uri", @@ -3979,11 +3981,17 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "zlib-rs" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" + [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] name = "zvariant" diff --git a/Cargo.toml b/Cargo.toml index fa80cf4d..6e876729 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ getrandom = "0.4" glob = "0.3" globset = "0.4" iana-time-zone = "0.1" -jsonschema = { version = "0.49", default-features = false } +jsonschema = { version = "0.55", default-features = false } # OS credential store for the token `mergify auth login` mints. The # default feature set is the one that keeps the Linux build free of C: # `zbus-secret-service-keyring-store` talks to the Secret Service over @@ -49,7 +49,7 @@ jsonschema = { version = "0.49", default-features = false } keyring = "4" opentelemetry-proto = { version = "0.32", default-features = false, features = ["gen-tonic-messages", "trace"] } prost = "0.14" -quick-xml = "0.41" +quick-xml = "0.42" regex = "1" # `form`: the OAuth device grant is `application/x-www-form-urlencoded` # on the wire, as RFC 6749 §4 requires. Nothing else in the CLI sends a diff --git a/crates/mergify-ci/src/junit_process/junit.rs b/crates/mergify-ci/src/junit_process/junit.rs index 3e9882a5..b34423c3 100644 --- a/crates/mergify-ci/src/junit_process/junit.rs +++ b/crates/mergify-ci/src/junit_process/junit.rs @@ -219,26 +219,22 @@ pub fn parse(xml: &[u8]) -> Result { // `GeneralRef` event below; entity resolution // happens in the `GeneralRef` arm. // - // `xml10_content()` (vs the plainer `decode()`) is - // important on Windows: when git checks fixtures - // out with `core.autocrlf` enabled the file's line - // endings are `\r\n`, and the XML 1.0 spec - // requires those to be normalized to `\n` before - // they reach element text. Without this, failure - // stacktraces ship `\r\n` and tests that diff on - // the assembled stacktrace fail only on Windows. - let s = e.xml10_content().map_err(|err| InvalidJunitXml { - details: format!("invalid UTF-8 in element text: {err}"), - })?; + // `xml10_content()` (vs the plain `Deref` content) is important on Windows: when git + // checks fixtures out with `core.autocrlf` enabled + // the file's line endings are `\r\n`, and the XML + // 1.0 spec requires those to be normalized to `\n` + // before they reach element text. Without this, + // failure stacktraces ship `\r\n` and tests that + // diff on the assembled stacktrace fail only on + // Windows. + let s = e.xml10_content(); state.append_failure_text(&s); } Ok(Event::CData(e)) => { - // CDATA bodies are by definition not entity-escaped, - // so plain UTF-8 decoding is correct here. - let text = std::str::from_utf8(e.as_ref()).map_err(|err| InvalidJunitXml { - details: format!("invalid UTF-8 in CDATA: {err}"), - })?; - state.append_failure_text(text); + // quick-xml validates UTF-8 up front, so the event + // already derefs to `str` — no decoding step needed. + state.append_failure_text(e.as_ref()); } Ok(Event::GeneralRef(e)) => { // quick-xml emits a separate `GeneralRef` event for @@ -258,10 +254,8 @@ pub fn parse(xml: &[u8]) -> Result { state.append_failure_text(ch.encode_utf8(&mut tmp)); } } else { - let name = e.decode().map_err(|err| InvalidJunitXml { - details: format!("invalid UTF-8 in entity reference: {err}"), - })?; - let resolved = match name.as_ref() { + let name = e.as_ref(); + let resolved = match name { "lt" => "<", "gt" => ">", "amp" => "&", @@ -294,14 +288,13 @@ pub fn parse(xml: &[u8]) -> Result { /// `testsuite` or `ns:testsuite` (XML namespaces); Python's /// `findall(".//{*}testsuite")` ignores the namespace prefix /// entirely, so we do the same. -fn local_name<'a>(name: QName<'a>) -> &'a [u8] { - // `QName::local_name` would return a wrapped type; we want - // raw bytes here. `into_inner()` (`QName: Deref` - // via `&'a [u8]`) gives the input-borrowed slice back so the - // returned `&[u8]` stays tied to `'a`, not the temporary - // function frame. - let raw: &'a [u8] = name.into_inner(); - raw.rsplit(|b| *b == b':').next().unwrap_or(raw) +fn local_name<'a>(name: QName<'a>) -> &'a str { + // `QName::local_name` would return a wrapped type; we want the + // raw string here. `into_inner()` gives the input-borrowed + // `&'a str` back so the return stays tied to `'a`, not the + // temporary function frame. + let raw: &'a str = name.into_inner(); + raw.rsplit(':').next().unwrap_or(raw) } /// Decode an attribute value to a `String`, resolving XML entity @@ -383,11 +376,11 @@ impl ParserState { let name = local_name(e.name()); if !self.saw_valid_root { match name { - b"testsuites" => { + "testsuites" => { self.saw_valid_root = true; return Ok(()); } - b"testsuite" => { + "testsuite" => { self.saw_valid_root = true; self.saw_any_testsuite = true; let suite_name = read_suite_name(e)?; @@ -404,13 +397,13 @@ impl ParserState { } match name { - b"testsuite" => { + "testsuite" => { self.saw_any_testsuite = true; let suite_name = read_suite_name(e)?; self.suite_names.push(suite_name.clone()); self.suite_stack.push(suite_name); } - b"testcase" => { + "testcase" => { if self.in_progress.is_some() { return Err(InvalidJunitXml { details: "nested not allowed".to_string(), @@ -424,11 +417,11 @@ impl ParserState { self.in_progress = Some(read_testcase(e, &suite_name)?); self.failure_captured = false; } - b"failure" | b"error" => { + "failure" | "error" => { if let Some(tc) = self.in_progress.as_mut() && !self.failure_captured { - tc.status = if name == b"failure" { + tc.status = if name == "failure" { TestStatus::Failed } else { TestStatus::Errored @@ -439,7 +432,7 @@ impl ParserState { self.failure_text_buf.clear(); } } - b"skipped" => { + "skipped" => { if let Some(tc) = self.in_progress.as_mut() { // `` wins over `` only when // the failure wasn't already recorded — Python @@ -465,7 +458,7 @@ impl ParserState { // mirror the work `on_start` does and just skip the // matching `on_end` since there's no body to track. match name { - b"testsuite" => { + "testsuite" => { self.saw_any_testsuite = true; if !self.saw_valid_root { self.saw_valid_root = true; @@ -477,7 +470,7 @@ impl ParserState { self.suite_names.push(read_suite_name(e)?); // Empty testsuite contributes nothing to `cases`. } - b"testcase" => { + "testcase" => { if !self.saw_valid_root { return Err(InvalidJunitXml { details: " outside ".to_string(), @@ -491,18 +484,18 @@ impl ParserState { let tc = read_testcase(e, &suite_name)?; self.output.push(tc); } - b"skipped" => { + "skipped" => { if let Some(tc) = self.in_progress.as_mut() && !self.failure_captured { tc.status = TestStatus::Skipped; } } - b"failure" | b"error" => { + "failure" | "error" => { if let Some(tc) = self.in_progress.as_mut() && !self.failure_captured { - tc.status = if name == b"failure" { + tc.status = if name == "failure" { TestStatus::Failed } else { TestStatus::Errored @@ -524,16 +517,16 @@ impl ParserState { fn on_end(&mut self, e: &quick_xml::events::BytesEnd<'_>) { let name = local_name(e.name()); match name { - b"testsuite" => { + "testsuite" => { self.suite_stack.pop(); } - b"testcase" => { + "testcase" => { if let Some(tc) = self.in_progress.take() { self.output.push(tc); } self.failure_captured = false; } - b"failure" | b"error" if self.in_failure => { + "failure" | "error" if self.in_failure => { // Flush the accumulated body text into the // testcase's failure record. Trimming once at the // close keeps the wire format identical to @@ -569,7 +562,7 @@ fn read_suite_name(e: &quick_xml::events::BytesStart<'_>) -> Result classname = Some(attr_value(&attr)?), - b"name" => name = Some(attr_value(&attr)?), - b"time" => { + "classname" => classname = Some(attr_value(&attr)?), + "name" => name = Some(attr_value(&attr)?), + "time" => { let raw = attr_value(&attr)?; if !raw.is_empty() { time = raw.parse::().ok(); } } - b"file" => file = Some(attr_value(&attr)?), - b"line" => line = Some(attr_value(&attr)?), + "file" => file = Some(attr_value(&attr)?), + "line" => line = Some(attr_value(&attr)?), _ => {} } } @@ -628,8 +621,8 @@ fn read_failure(e: &quick_xml::events::BytesStart<'_>) -> Result failure.kind = Some(attr_value(&attr)?), - b"message" => failure.message = Some(attr_value(&attr)?), + "type" => failure.kind = Some(attr_value(&attr)?), + "message" => failure.message = Some(attr_value(&attr)?), _ => {} } }