From d2a391b2af7a735e0cd3446495b16173f4e7d9a7 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Fri, 13 Nov 2026 23:04:20 -0500 Subject: [PATCH 1/3] Give back a Cargo config with no final newline Hosted `scan` then `remove` left a newline at the end of a `.cargo/config.toml` that had none. The rewriter appended two newlines before its `[registries.socket-patch-]` block there: one to end the user's last line, one as a blank separator. `remove` takes back the block and one newline. The edit record carried the extra newline for the ledger-based revert older CLIs had; v5 hosted mode keeps no ledger, and the file itself cannot tell that config from one that ended in a newline, so the extra one stayed. The rewriter now puts exactly one newline before the block whatever the config ends with. After a final newline that is the blank separator, as before. After none it is the line ending the config lacked, and the block follows the last line directly. remove_registry_block already inverts that form byte for byte and is unchanged. A config that 5.1.0 or earlier already hosted still comes back with the final newline added: nothing in the file says which form it had. Co-Authored-By: Claude Fable 5.2 --- .../src/patch/redirect/mod.rs | 93 ++++++++++++++++--- .../src/patch/redirect/upstream/cargo.rs | 30 ++++-- 2 files changed, 101 insertions(+), 22 deletions(-) diff --git a/crates/socket-patch-core/src/patch/redirect/mod.rs b/crates/socket-patch-core/src/patch/redirect/mod.rs index 4bc6359fb..73cae946e 100644 --- a/crates/socket-patch-core/src/patch/redirect/mod.rs +++ b/crates/socket-patch-core/src/patch/redirect/mod.rs @@ -4490,27 +4490,24 @@ fn plan_cargo_config( }, }); } - // Absent (or surviving only in comments): append a fresh block. + // Absent (or surviving only in comments): append a fresh block after + // exactly one newline, whatever the config ends with. After a final + // newline that is a blank separator line; after none it is the line + // ending the config lacked, and the block follows its last line + // directly. `remove` keeps no record of the append and takes back the + // block plus that one newline (`upstream::cargo::remove_registry_block`), + // so a second newline here would stay behind in a config that had none. let block = format!("{header}\n{index_line}\n"); - let sep = if !config.is_empty() && !config.ends_with('\n') { - "\n" - } else { - "" - }; let prefix = if config.is_empty() { "" } else { "\n" }; - // The newline a config without a final one needed rides in the recorded - // fragment, so the revert (which also drops the one blank separator - // before the fragment) restores the config's exact bytes. - let recorded = format!("{sep}{block}"); Some(CargoConfigPlan { - content: format!("{config}{sep}{prefix}{block}"), + content: format!("{config}{prefix}{block}"), edit: FileEdit { path: config_key.into(), kind: "redirect_cargo_registry".into(), action: "added".into(), key: Some(reg.to_string()), original: None, - new: Some(Value::String(recorded)), + new: Some(Value::String(block)), }, }) } @@ -27921,7 +27918,8 @@ packages: ); assert!(r.confirmed_cargo_uuids.contains(CARGO_UUID)); - // Newline-less user config: exactly one blank line before the block. + // Newline-less user config: the block follows the last line after + // the one newline that line lacked, and no second one. let mut files = cargo_files(manifest); files.insert( ".cargo/config.toml".to_string(), @@ -27932,15 +27930,80 @@ packages: assert_eq!( cfg, &format!( - "[net]\nretry = 3\n\n[registries.{}]\nindex = \"{}\"\n", + "[net]\nretry = 3\n[registries.{}]\nindex = \"{}\"\n", cargo_reg(), cargo_index_url() ), - "a newline must be added before the separator blank line" + "one newline ends the user's last line; a second could not be taken back" ); assert!(r.confirmed_cargo_uuids.contains(CARGO_UUID)); } + /// `remove` keeps no record of what the rewriter appended, so the append + /// has to be one `upstream::cargo::remove_registry_block` inverts from + /// the file alone: the config comes back byte for byte, whatever it + /// ended with, in LF and CRLF. + #[test] + fn cargo_config_append_is_undone_byte_for_byte() { + let manifest = "[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n\ + [dependencies]\nserde = \"1.0.190\"\n"; + for original in [ + "[net]\nretry = 3\n", + "[net]\nretry = 3", + "[net]\nretry = 3\n\n\n", + "# only a comment", + "[net]\r\nretry = 3\r\n", + "[net]\r\nretry = 3", + ] { + let mut files = cargo_files(manifest); + files.insert(".cargo/config.toml".to_string(), original.to_string()); + let r = rewrite_registry_redirect(&files, &[cargo_sparse_override()]); + let hosted = r.files.get(".cargo/config.toml").expect("block appended"); + let parsed = hosted + .parse::() + .unwrap_or_else(|e| panic!("{original:?} hosted is not TOML: {e}")); + assert_eq!( + parsed["registries"][&cargo_reg()]["index"].as_str(), + Some(cargo_index_url().as_str()), + "{original:?}" + ); + assert_eq!( + upstream::cargo::remove_registry_block(hosted, &cargo_reg()).as_deref(), + Some(original), + "{original:?}" + ); + } + + // Two patches appended to a newline-less config, removed in either + // order. + let original = "[net]\nretry = 3"; + let other_reg = format!("socket-patch-{CARGO_UUID_2}"); + let one = plan_cargo_config( + original, + ".cargo/config.toml", + &cargo_reg(), + &cargo_index_url(), + ) + .expect("first block appended") + .content; + let both = plan_cargo_config( + &one, + ".cargo/config.toml", + &other_reg, + &format!("sparse+https://patch.test/cargo/{CARGO_UUID_2}/index/"), + ) + .expect("second block appended") + .content; + for order in [[&cargo_reg(), &other_reg], [&other_reg, &cargo_reg()]] { + let mut config = both.clone(); + for reg in order { + config = upstream::cargo::remove_registry_block(&config, reg) + .unwrap_or_else(|| panic!("{reg} block not found in {config:?}")); + } + assert_eq!(config, original, "{order:?}"); + } + } + /// A custom-registry pnpm lock carries extra `resolution:` fields /// (`registry:`) beside integrity/tarball — the splice must preserve them /// while replacing the integrity and tarball values. diff --git a/crates/socket-patch-core/src/patch/redirect/upstream/cargo.rs b/crates/socket-patch-core/src/patch/redirect/upstream/cargo.rs index b976e192e..c0c4c1595 100644 --- a/crates/socket-patch-core/src/patch/redirect/upstream/cargo.rs +++ b/crates/socket-patch-core/src/patch/redirect/upstream/cargo.rs @@ -469,13 +469,16 @@ pub(crate) fn remove_registry_block(config: &str, reg: &str) -> Option { } /// Invert the cargo rewriter's append of a `[registries.…]` block: it wrote -/// `config + "\n" + block` (just `block` into an empty config), or -/// `config + "\n\n" + block` when the config lacked a final newline. -/// Removing the block plus the one newline before it restores the config's -/// exact bytes, a missing final newline or trailing blank lines included, -/// and anything the user appended after the block kept. An all-CRLF file is -/// inverted as LF and written back CRLF. `None` when the block is not in -/// the file. +/// `config + "\n" + block` (just `block` into an empty config), whether or +/// not the config ended in a newline. Removing the block plus the one +/// newline before it restores the config's exact bytes, a missing final +/// newline or trailing blank lines included, and anything the user appended +/// after the block kept. An all-CRLF file is inverted as LF and written back +/// CRLF. `None` when the block is not in the file. +/// +/// Earlier releases wrote `config + "\n\n" + block` into a config with no +/// final newline. Nothing in the file tells that apart from a config that +/// had one, so a config they patched comes back with a final newline added. fn remove_appended_cargo_block(content: &str, fragment: &str) -> Option { if !content.is_empty() && content.matches('\n').count() == content.matches("\r\n").count() { return remove_appended_cargo_block( @@ -818,6 +821,19 @@ mod tests { assert_eq!(after, None, "the emptied config must be deleted"); } + /// A config that had no final newline before the rewriter appended its + /// block comes back without one: the restore takes away the block and + /// the one newline put before it, nothing more and nothing less. + #[tokio::test] + async fn restore_gives_back_a_config_with_no_final_newline() { + for original in ["[net]\nretry = 3", "[net]\nretry = 3\n"] { + let hosted = format!("{original}\n{}", block(B)); + let (outcome, after) = restore_b(&manifest(""), &hosted).await; + assert_eq!(outcome.restored().count(), 1, "{:?}", outcome.pins); + assert_eq!(after.as_deref(), Some(original), "{original:?}"); + } + } + /// A generation the restore did not select that a manifest still pins /// stays wired: only unreferenced blocks are swept. #[tokio::test] From db8ae68a1557e85b0bbd3a6d901fb27dad19e570 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Fri, 13 Nov 2026 23:12:33 -0500 Subject: [PATCH 2/3] Require a byte-exact restore in the cargo e2e e2e_redirect_cargo_shapes let a file with no final newline come back with one added, because the rewriter could not help it. With one newline before the appended registry block the restore is exact, so the check now compares every file byte for byte. cargo_hosted_config_trailing_bytes_are_restored covers the `config-unterminated` shape with real cargo. Co-Authored-By: Claude Fable 5.2 --- .../tests/e2e_redirect_cargo_shapes.rs | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/crates/socket-patch-cli/tests/e2e_redirect_cargo_shapes.rs b/crates/socket-patch-cli/tests/e2e_redirect_cargo_shapes.rs index 7f6a35ac7..12f91ea71 100644 --- a/crates/socket-patch-cli/tests/e2e_redirect_cargo_shapes.rs +++ b/crates/socket-patch-cli/tests/e2e_redirect_cargo_shapes.rs @@ -755,23 +755,11 @@ async fn run_shape(shape: Shape) -> Option<()> { } let after = snapshot(&proj); for (rel, bytes) in &before { - // v5 keeps no ledger fragment, and the rewriter separates its - // appended registry block with the same bytes whether or not the - // original file ended in a newline — so an UNTERMINATED file can - // only come back with its final newline (every other byte exact). - let bytes = &shape + let want = shape .restored .iter() .find(|(path, _)| path == rel) .map_or_else(|| bytes.clone(), |(_, text)| text.as_bytes().to_vec()); - let want = match after.get(rel) { - Some(got) - if !bytes.ends_with(b"\n") && *got == [bytes.as_slice(), b"\n"].concat() => - { - got.clone() - } - _ => bytes.clone(), - }; assert_eq!( after .get(rel) @@ -1195,10 +1183,11 @@ async fn cargo_hosted_dotted_and_literal_spellings_are_pinned() { } /// Bug H: a config ending in a blank line comes back byte-for-byte (the -/// appended block's removal once normalized the trailing newline run). A -/// config without a final newline comes back with every byte but that -/// missing newline: v5 keeps no ledger fragment to tell it apart from a -/// terminated file, since the rewriter's separator is the same for both. +/// appended block's removal once normalized the trailing newline run). So +/// does a config without a final newline: v5 keeps no ledger fragment to +/// tell it apart from a terminated file, so the rewriter puts one newline +/// before its block whatever the config ends with, and `remove` takes back +/// exactly that one. #[tokio::test(flavor = "multi_thread")] async fn cargo_hosted_config_trailing_bytes_are_restored() { for (tag, rel, config) in [ From 2c6cb5d0838b52222ef66038f35ae156a3077353 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Fri, 13 Nov 2026 23:31:44 -0500 Subject: [PATCH 3/3] Keep lines apart and user comments on remove From /code-review high on the PR. With the block straight after a config's last line, content the user later put directly after the block was glued to that line on `remove` (`retry = 3[build]`), which cargo cannot parse. That was new with the previous commit. The newline before the block now stays when taking it would join two lines. Three older defects in the same function are fixed with it: - A config with mixed line endings came back all CRLF. Only a file whose every line ends in CRLF is converted, the rewriter's own rule. - A comment heading the next section was deleted with the block. Blank lines and comments after the block's last key are the user's and stay. - The block was found by searching for its text, which could match inside another line. It is located by its header line. remove_appended_cargo_block is folded into remove_registry_block; its CRLF branches were unreachable from there. A new golden, redirect/cargo/cargo/config-unterminated, pins the append for a config with no final newline in the rewrite, restore and vex discovery goldens. restore_gives_back_a_config_with_no_final_newline takes its hosted bytes from the rewriter, so it no longer passes without the fix. Co-Authored-By: Claude Fable 5.2 --- .../tests/e2e_redirect_cargo_shapes.rs | 5 +- .../src/patch/redirect/upstream/cargo.rs | 190 +++++++++++++----- .../config-unterminated/expected-edits.json | 25 +++ .../expected/.cargo/config.toml | 4 + .../config-unterminated/expected/Cargo.lock | 16 ++ .../config-unterminated/expected/Cargo.toml | 8 + .../input/.cargo/config.toml | 2 + .../config-unterminated/input/Cargo.lock | 16 ++ .../config-unterminated/input/Cargo.toml | 8 + .../cargo/config-unterminated/overrides.json | 22 ++ .../vex-discover-golden/redirect-cargo.json | 55 +++++ 11 files changed, 295 insertions(+), 56 deletions(-) create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/expected/.cargo/config.toml create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/expected/Cargo.lock create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/expected/Cargo.toml create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/input/.cargo/config.toml create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/input/Cargo.lock create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/input/Cargo.toml create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/overrides.json diff --git a/crates/socket-patch-cli/tests/e2e_redirect_cargo_shapes.rs b/crates/socket-patch-cli/tests/e2e_redirect_cargo_shapes.rs index 12f91ea71..f4c0c50c3 100644 --- a/crates/socket-patch-cli/tests/e2e_redirect_cargo_shapes.rs +++ b/crates/socket-patch-cli/tests/e2e_redirect_cargo_shapes.rs @@ -7,8 +7,9 @@ //! own version's registry, and removing both purls restores every byte. //! * `legacy_config` — an existing legacy `.cargo/config`: the registry //! block lands there, and `remove` restores the file byte-for-byte — -//! also when it ends in a blank line (one lacking a final newline gets -//! that newline back: v5 keeps no ledger fragment to tell them apart). +//! also when it ends in a blank line or lacks a final newline (v5 keeps +//! no ledger fragment to tell the two apart, so the rewriter puts one +//! newline before its block either way and `remove` takes that one back). //! * `crlf` — CRLF `Cargo.toml` + `Cargo.lock`: rewritten with CRLF kept, //! and restored byte-for-byte. //! * `workspace_direct_member` — a virtual workspace whose root pins diff --git a/crates/socket-patch-core/src/patch/redirect/upstream/cargo.rs b/crates/socket-patch-core/src/patch/redirect/upstream/cargo.rs index c0c4c1595..4e4f13ba2 100644 --- a/crates/socket-patch-core/src/patch/redirect/upstream/cargo.rs +++ b/crates/socket-patch-core/src/patch/redirect/upstream/cargo.rs @@ -438,68 +438,74 @@ fn unpin_line(line: &str, reg: &str) -> Option> { Some(Some(rebuilt)) } -/// The config with its `[registries.]` block (and the blank separator -/// the rewriter put before it) removed; `None` when absent. +/// The config with its `[registries.]` block removed, together with +/// the one newline the rewriter put before it; `None` when absent. +/// +/// The rewriter wrote `config + "\n" + block` (just `block` into an empty +/// config), whether or not the config ended in a newline: that newline is +/// a blank separator line after a final newline, and the line ending of a +/// config that had none. Taking the block and that newline back restores +/// the config's exact bytes, a missing final newline or trailing blank +/// lines included. A file whose every line ends in CRLF is edited as LF +/// and written back CRLF, as the rewriter does; mixed endings stay as they +/// are. +/// +/// The block is the header and the keys under it. Blank lines and comments +/// after its last key are the user's (a separator, or the heading of the +/// next section) and stay, as does everything after them. When the user +/// put content straight after the block of a config that had no final +/// newline, the newline stays too: without it the config's last line and +/// that content would become one line. +/// +/// Earlier releases wrote `config + "\n\n" + block` into a config with no +/// final newline. Nothing in the file tells that apart from a config that +/// had one, so a config they patched comes back with a final newline added. pub(crate) fn remove_registry_block(config: &str, reg: &str) -> Option { - let crlf = config.contains("\r\n"); - let lf = config.replace("\r\n", "\n"); + let lf = super::super::crlf_to_lf(config); + let text = lf.as_deref().unwrap_or(config); let header = format!("[registries.{reg}]"); - let lines: Vec<&str> = lf.split('\n').collect(); + let lines: Vec<&str> = text.split('\n').collect(); let i = lines.iter().position(|l| l.trim() == header)?; - let mut end = lines.len(); - for (j, l) in lines.iter().enumerate().skip(i + 1) { - if l.trim_start().starts_with('[') { - end = j; + let mut end = lines[i + 1..] + .iter() + .position(|l| l.trim_start().starts_with('[')) + .map_or(lines.len(), |next| i + 1 + next); + while end > i + 1 { + let line = lines[end - 1].trim(); + if !(line.is_empty() || line.starts_with('#')) { break; } - } - while end > i + 1 && lines[end - 1].trim().is_empty() { end -= 1; } - let fragment = format!("{}\n", lines[i..end].join("\n")); - let removed = remove_appended_cargo_block(&lf, &fragment).or_else(|| { - // The block ends the file with no final newline. - remove_appended_cargo_block(&lf, fragment.trim_end_matches('\n')) - })?; - Some(if crlf { + let bytes = |lines: &[&str]| lines.iter().map(|l| l.len() + 1).sum::(); + let start = bytes(&lines[..i]); + // The last line of a file with no final newline has no newline to take. + let stop = (start + bytes(&lines[i..end])).min(text.len()); + let (before, after) = (&text[..start], &text[stop..]); + let unseparated = before + .strip_suffix("\r\n") + .or_else(|| before.strip_suffix('\n')) + .unwrap_or(before); + // Without the newline, would the line before the block and what follows + // it still be separate lines? + let lines_stay_apart = unseparated.is_empty() + || unseparated.ends_with('\n') + || after.is_empty() + || after.starts_with('\n') + || after.starts_with("\r\n"); + let kept = if lines_stay_apart { + unseparated + } else { + before + }; + let removed = format!("{kept}{after}"); + Some(if lf.is_some() { removed.replace('\n', "\r\n") } else { removed }) } -/// Invert the cargo rewriter's append of a `[registries.…]` block: it wrote -/// `config + "\n" + block` (just `block` into an empty config), whether or -/// not the config ended in a newline. Removing the block plus the one -/// newline before it restores the config's exact bytes, a missing final -/// newline or trailing blank lines included, and anything the user appended -/// after the block kept. An all-CRLF file is inverted as LF and written back -/// CRLF. `None` when the block is not in the file. -/// -/// Earlier releases wrote `config + "\n\n" + block` into a config with no -/// final newline. Nothing in the file tells that apart from a config that -/// had one, so a config they patched comes back with a final newline added. -fn remove_appended_cargo_block(content: &str, fragment: &str) -> Option { - if !content.is_empty() && content.matches('\n').count() == content.matches("\r\n").count() { - return remove_appended_cargo_block( - &content.replace("\r\n", "\n"), - &fragment.replace("\r\n", "\n"), - ) - .map(|lf| lf.replace('\n', "\r\n")); - } - let lf_fragment = fragment.replace("\r\n", "\n"); - let (pos, len) = match content.find(fragment) { - Some(pos) => (pos, fragment.len()), - None => (content.find(&lf_fragment)?, lf_fragment.len()), - }; - let before = &content[..pos]; - let before = before - .strip_suffix("\r\n") - .or_else(|| before.strip_suffix('\n')) - .unwrap_or(before); - Some(format!("{before}{}", &content[pos + len..])) -} - /// Respell the dependents' references to each merged hit's crate the way /// cargo writes them once the Socket block is gone (`lock`: the text with /// the merged blocks removed; `model`: the lock before). A v1 lock spells @@ -821,19 +827,95 @@ mod tests { assert_eq!(after, None, "the emptied config must be deleted"); } - /// A config that had no final newline before the rewriter appended its - /// block comes back without one: the restore takes away the block and - /// the one newline put before it, nothing more and nothing less. + /// The rewriter's own append (`plan_cargo_config`), undone by the + /// restore `remove` runs: a config comes back as it was, with no final + /// newline when it had none. #[tokio::test] async fn restore_gives_back_a_config_with_no_final_newline() { - for original in ["[net]\nretry = 3", "[net]\nretry = 3\n"] { - let hosted = format!("{original}\n{}", block(B)); + for original in [ + "[net]\nretry = 3", + "[net]\nretry = 3\n", + "[net]\nretry = 3\n\n", + ] { + let hosted = super::super::super::plan_cargo_config( + original, + ".cargo/config.toml", + &format!("socket-patch-{B}"), + "sparse+https://patch.socket.dev/patch-registry/cargo/tok/b/index/", + ) + .expect("block appended") + .content; let (outcome, after) = restore_b(&manifest(""), &hosted).await; assert_eq!(outcome.restored().count(), 1, "{:?}", outcome.pins); assert_eq!(after.as_deref(), Some(original), "{original:?}"); } } + /// What the user wrote after the block stays, on lines of its own. + #[test] + fn content_after_the_block_is_kept() { + let block = format!("[registries.{REG}]\nindex = \"sparse+https://x/\"\n"); + let rest = "[build]\njobs = 4\n"; + // A config with no final newline, then `[build]` put straight after + // the block: the newline before the block has to stay, or + // `retry = 3[build]` is one line cargo cannot parse. + assert_eq!( + remove_registry_block(&format!("[net]\nretry = 3\n{block}{rest}"), REG).as_deref(), + Some("[net]\nretry = 3\n[build]\njobs = 4\n") + ); + // Behind a blank line, or after a config that ended in a newline, + // the newline before the block goes as it does at the end of the + // file. + assert_eq!( + remove_registry_block(&format!("[net]\nretry = 3\n{block}\n{rest}"), REG).as_deref(), + Some("[net]\nretry = 3\n[build]\njobs = 4\n") + ); + assert_eq!( + remove_registry_block(&format!("[net]\nretry = 3\n\n{block}{rest}"), REG).as_deref(), + Some("[net]\nretry = 3\n[build]\njobs = 4\n") + ); + // A comment heading the next section is the user's, with the blank + // line before it. + assert_eq!( + remove_registry_block( + &format!("[net]\nretry = 3\n\n{block}\n# build settings\n{rest}"), + REG + ) + .as_deref(), + Some("[net]\nretry = 3\n\n# build settings\n[build]\njobs = 4\n") + ); + // So is a comment at the end of the file. + assert_eq!( + remove_registry_block(&format!("[net]\nretry = 3\n{block}# note"), REG).as_deref(), + Some("[net]\nretry = 3\n# note") + ); + } + + /// A file of mixed line endings is not an all-CRLF file: the lines the + /// block never touched keep the endings they had. + #[test] + fn mixed_line_endings_are_left_as_they_are() { + let block = format!("[registries.{REG}]\nindex = \"sparse+https://x/\"\n"); + for original in ["[a]\r\nx = 1\ny = 2", "[a]\r\nx = 1\ny = 2\n"] { + assert_eq!( + remove_registry_block(&format!("{original}\n{block}"), REG).as_deref(), + Some(original), + "{original:?}" + ); + } + let crlf = "[a]\r\nx = 1\r\n"; + assert_eq!( + remove_registry_block( + &format!("{crlf}\n{block}") + .replace("\r\n", "\n") + .replace('\n', "\r\n"), + REG + ) + .as_deref(), + Some(crlf) + ); + } + /// A generation the restore did not select that a manifest still pins /// stays wired: only unreferenced blocks are swept. #[tokio::test] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/expected-edits.json new file mode 100644 index 000000000..0e48e6854 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/expected-edits.json @@ -0,0 +1,25 @@ +[ + { + "path": ".cargo/config.toml", + "kind": "redirect_cargo_registry", + "action": "added", + "key": "socket-patch-55555555-5555-5555-5555-555555555555", + "new": "[registries.socket-patch-55555555-5555-5555-5555-555555555555]\nindex = \"sparse+https://patch.socket.dev/patch-registry/cargo/11111111-1111-1111-1111-111111111111/55555555-5555-5555-5555-555555555555/index/\"\n" + }, + { + "kind": "redirect_cargo_toml_dep", + "action": "rewritten", + "key": "serde", + "original": "serde = \"1.0.190\"", + "new": "serde = { version = \"1.0.190\", registry = \"socket-patch-55555555-5555-5555-5555-555555555555\" }", + "path": "Cargo.toml" + }, + { + "kind": "redirect_cargo_lock_entry", + "action": "rewritten", + "key": "serde@1.0.190", + "original": "[[package]]\nname = \"serde\"\nversion = \"1.0.190\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"91d3c334ca1ee894a2c6f6ad7bf058a4d9a3b30e9e0d5a9d1f3e8f0c2c9c0000\"", + "new": "[[package]]\nname = \"serde\"\nversion = \"1.0.190\"\nsource = \"sparse+https://patch.socket.dev/patch-registry/cargo/11111111-1111-1111-1111-111111111111/55555555-5555-5555-5555-555555555555/index/\"\nchecksum = \"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef\"", + "path": "Cargo.lock" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/expected/.cargo/config.toml b/crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/expected/.cargo/config.toml new file mode 100644 index 000000000..0b8526b0d --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/expected/.cargo/config.toml @@ -0,0 +1,4 @@ +[net] +retry = 2 +[registries.socket-patch-55555555-5555-5555-5555-555555555555] +index = "sparse+https://patch.socket.dev/patch-registry/cargo/11111111-1111-1111-1111-111111111111/55555555-5555-5555-5555-555555555555/index/" diff --git a/crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/expected/Cargo.lock b/crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/expected/Cargo.lock new file mode 100644 index 000000000..e737470e1 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/expected/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "myapp" +version = "0.1.0" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.190" +source = "sparse+https://patch.socket.dev/patch-registry/cargo/11111111-1111-1111-1111-111111111111/55555555-5555-5555-5555-555555555555/index/" +checksum = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" diff --git a/crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/expected/Cargo.toml b/crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/expected/Cargo.toml new file mode 100644 index 000000000..fb287c7a7 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/expected/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "myapp" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = { version = "1.0.190", registry = "socket-patch-55555555-5555-5555-5555-555555555555" } +anyhow = { version = "1.0", features = ["backtrace"] } diff --git a/crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/input/.cargo/config.toml b/crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/input/.cargo/config.toml new file mode 100644 index 000000000..3d12ceefe --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/input/.cargo/config.toml @@ -0,0 +1,2 @@ +[net] +retry = 2 \ No newline at end of file diff --git a/crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/input/Cargo.lock b/crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/input/Cargo.lock new file mode 100644 index 000000000..bf6fa585b --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/input/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "myapp" +version = "0.1.0" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91d3c334ca1ee894a2c6f6ad7bf058a4d9a3b30e9e0d5a9d1f3e8f0c2c9c0000" diff --git a/crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/input/Cargo.toml b/crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/input/Cargo.toml new file mode 100644 index 000000000..d5cb90394 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/input/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "myapp" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = "1.0.190" +anyhow = { version = "1.0", features = ["backtrace"] } diff --git a/crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/overrides.json new file mode 100644 index 000000000..5fc4f53ab --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/cargo/cargo/config-unterminated/overrides.json @@ -0,0 +1,22 @@ +[ + { + "ecosystem": "cargo", + "name": "serde", + "version": "1.0.190", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "55555555-5555-5555-5555-555555555555", + "artifactUrl": "https://patch.socket.dev/patch/cargo/serde/1.0.190/11111111-1111-1111-1111-111111111111/55555555-5555-5555-5555-555555555555/serde-1.0.190.crate", + "registryOverride": { + "kind": "cargo-sparse", + "indexUrl": "sparse+https://patch.socket.dev/patch-registry/cargo/11111111-1111-1111-1111-111111111111/55555555-5555-5555-5555-555555555555/index/", + "identifiers": { + "name": "serde", + "version": "1.0.190", + "cargoCksumSha256": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + } + }, + "integrity": { + "sha256": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/vex-discover-golden/redirect-cargo.json b/crates/socket-patch-core/tests/fixtures/vex-discover-golden/redirect-cargo.json index c14a317f3..2e188b58e 100644 --- a/crates/socket-patch-core/tests/fixtures/vex-discover-golden/redirect-cargo.json +++ b/crates/socket-patch-core/tests/fixtures/vex-discover-golden/redirect-cargo.json @@ -120,6 +120,61 @@ } ] }, + "redirect/cargo/cargo/config-unterminated/expected": { + "refs": [ + { + "purl": "pkg:cargo/serde@1.0.190", + "uuid": "55555555-5555-5555-5555-555555555555", + "mode": "hosted", + "source_file": "Cargo.lock", + "artifact_rel": null, + "locked_integrity": "Sha256Hex(\"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef\")", + "integrity_required": true, + "url": "sparse+https://patch.socket.dev/patch-registry/cargo/11111111-1111-1111-1111-111111111111/55555555-5555-5555-5555-555555555555/index/", + "lockfile_basis_ok": true + } + ], + "diagnostics": [], + "recognized": [ + { + "uuid": "11111111-1111-1111-1111-111111111111", + "mode": "hosted", + "file": ".cargo/config.toml" + }, + { + "uuid": "11111111-1111-1111-1111-111111111111", + "mode": "hosted", + "file": "Cargo.lock" + }, + { + "uuid": "55555555-5555-5555-5555-555555555555", + "mode": "hosted", + "file": ".cargo/config.toml" + }, + { + "uuid": "55555555-5555-5555-5555-555555555555", + "mode": "hosted", + "file": "Cargo.lock" + } + ], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [ + { + "mode": "hosted", + "uuid": "55555555-5555-5555-5555-555555555555", + "purl": "pkg:cargo/serde@1.0.190" + } + ] + }, + "redirect/cargo/cargo/config-unterminated/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, "redirect/cargo/cargo/crlf/expected": { "refs": [ {