From 375a17cf72fe73f3f46f18a66744de40c9311e13 Mon Sep 17 00:00:00 2001 From: Lars Francke Date: Mon, 14 Sep 2026 15:04:10 +0200 Subject: [PATCH 1/2] fix(patchable): Fail when the mirror rejects the pushed base ref libgit2 reports a reference the remote refused through the push_update_reference callback rather than by failing the push, so `init version --mirror` logged success and wrote a patchable.toml with a mirror that never received anything. --ssh selects a transport for a single invocation, but its rewritten URL was stored in patchable.toml, which forces SSH on everyone who checks that file out and makes `checkout --ssh` fail because the stored URL has no https scheme. An existing configuration is now detected before the fetch and the push, so a re-run fails immediately instead of mirroring and then refusing to overwrite. The fmt layer wrote to stderr directly instead of through the IndicatifLayer, so log lines were printed on top of a progress bar that was never cleared. --- rust/patchable/src/main.rs | 74 ++++++++++++++++++++++++++++++-------- rust/patchable/src/repo.rs | 22 +++++++----- 2 files changed, 74 insertions(+), 22 deletions(-) diff --git a/rust/patchable/src/main.rs b/rust/patchable/src/main.rs index 949407e3a..ce5e02599 100644 --- a/rust/patchable/src/main.rs +++ b/rust/patchable/src/main.rs @@ -6,9 +6,11 @@ mod utils; use core::str; use std::{ + cell::RefCell, fs::File, io::{IsTerminal, Write}, path::PathBuf, + rc::Rc, }; use git2::{Oid, Repository}; @@ -270,6 +272,8 @@ pub enum Error { source: std::io::Error, path: PathBuf, }, + #[snafu(display("configuration already exists at {path:?}, delete it to re-run init"))] + VersionConfigExists { path: PathBuf }, #[snafu(display("failed to rewrite URL for SSH"))] UrlRewrite { source: utils::UrlRewriteError }, @@ -287,6 +291,12 @@ pub enum Error { refspec: String, commit: Oid, }, + #[snafu(display("mirror {url:?} rejected the update of {reference}: {reason}"))] + MirrorRejectedPush { + url: String, + reference: String, + reason: String, + }, #[snafu(display("failed to find images repository"))] FindImagesRepo { source: repo::Error }, @@ -330,13 +340,16 @@ type Result = std::result::Result; #[snafu::report] fn main() -> Result<()> { + // The fmt layer must write through the IndicatifLayer. + // Writing to stderr directly prints log lines on top of the progress bar without clearing it first. + let indicatif_layer = IndicatifLayer::new(); tracing_subscriber::registry() .with( tracing_subscriber::fmt::layer() - .with_ansi(std::io::stdout().is_terminal()) - .with_writer(std::io::stderr), + .with_ansi(std::io::stderr().is_terminal()) + .with_writer(indicatif_layer.get_stderr_writer()), ) - .with(IndicatifLayer::new()) + .with(indicatif_layer) .with( tracing_subscriber::EnvFilter::builder() .with_default_directive(tracing_subscriber::filter::LevelFilter::INFO.into()) @@ -566,6 +579,13 @@ fn main() -> Result<()> { images_repo_root, }; + // Checked before the fetch and the push, so that a re-run fails immediately instead of + // after mirroring. + let config_path = ctx.version_config_path(); + if config_path.exists() { + return VersionConfigExistsSnafu { path: config_path }.fail(); + } + let product_repo_root = ctx.product_repo(); let product_repo = tracing::info_span!( "finding product repository", @@ -589,34 +609,52 @@ fn main() -> Result<()> { tracing::info!(?base, base.commit = ?base_commit, "resolved base commit"); let mirror_url = if mirror { - let mut mirror_url = config + let mirror_url = config .default_mirror .filter(|s| !s.is_empty()) .context(InitMirrorNotConfiguredSnafu)?; - if ssh { - mirror_url = - utils::rewrite_git_https_url_to_ssh(&mirror_url).context(UrlRewriteSnafu)? + // --ssh only picks a transport for this invocation. + // patchable.toml is shared with everyone else, including CI, so it keeps the URL + // from the product configuration. + // Otherwise it'd change it to a ssh:// URL which is not what we want. + let push_url = if ssh { + utils::rewrite_git_https_url_to_ssh(&mirror_url).context(UrlRewriteSnafu)? + } else { + mirror_url.clone() }; // Add mirror remote let mut mirror_remote = product_repo - .remote_anonymous(&mirror_url) + .remote_anonymous(&push_url) .context(AddMirrorRemoteSnafu { - url: mirror_url.clone(), + url: push_url.clone(), })?; // Push the base commit to the mirror - tracing::info!(commit = %base_commit, base = base, url = mirror_url, "pushing commit to mirror"); + tracing::info!(commit = %base_commit, base = base, url = push_url, "pushing commit to mirror"); let mut callbacks = setup_git_credentials(); + // libgit2 reports a refused reference through this callback rather than by failing + // the push, so without it a rejected push looks exactly like a successful one. + let rejection = Rc::new(RefCell::new(None)); + let rejection_sink = Rc::clone(&rejection); + callbacks.push_update_reference(move |reference, status| { + if let Some(status) = status { + *rejection_sink.borrow_mut() = + Some((reference.to_owned(), status.to_owned())); + } + Ok(()) + }); + // Add progress tracking for push operation let (span_push, mut quant_push) = utils::setup_progress_tracking(tracing::info_span!("pushing")); - let _ = span_push.enter(); + let push_progress = span_push.clone(); + let _span_push = span_push.entered(); callbacks.push_transfer_progress(move |current, total, _| { if total > 0 { - quant_push.update_span_progress(current, total, &span_push); + quant_push.update_span_progress(current, total, &push_progress); } }); @@ -631,11 +669,20 @@ fn main() -> Result<()> { mirror_remote .push(&[&refspec], Some(&mut push_options)) .context(PushToMirrorSnafu { - url: &mirror_url, + url: &push_url, refspec: &refspec, commit: base_commit, })?; + if let Some((reference, reason)) = rejection.take() { + return MirrorRejectedPushSnafu { + url: &push_url, + reference, + reason, + } + .fail(); + } + tracing::info!("successfully pushed base ref to mirror"); Some(mirror_url) } else { @@ -650,7 +697,6 @@ fn main() -> Result<()> { base: base_commit, mirror: mirror_url, }; - let config_path = ctx.version_config_path(); if let Some(config_dir) = config_path.parent() { std::fs::create_dir_all(config_dir) .context(CreatePatchDirSnafu { path: config_dir })?; diff --git a/rust/patchable/src/repo.rs b/rust/patchable/src/repo.rs index f663a352a..3e984173c 100644 --- a/rust/patchable/src/repo.rs +++ b/rust/patchable/src/repo.rs @@ -142,30 +142,36 @@ pub fn resolve_and_fetch_commitish( Ok(commit_obj) } Err(err) if !commitish_is_oid || err.code() == git2::ErrorCode::NotFound => { - tracing::info!( - error = &err as &dyn std::error::Error, - "base commit not found locally, fetching from upstream" - ); + if commitish_is_oid { + tracing::info!( + error = &err as &dyn std::error::Error, + "base commit not found locally, fetching from upstream" + ); + } else { + tracing::info!("base is not a commit id, resolving it against upstream"); + } let (span_recv, mut quant_recv) = setup_progress_tracking(tracing::info_span!("receiving")); let (span_index, mut quant_index) = setup_progress_tracking(tracing::info_span!("indexing")); - let _ = span_recv.enter(); - let _ = span_index.enter(); + let recv_progress = span_recv.clone(); + let index_progress = span_index.clone(); + let _span_recv = span_recv.entered(); + let _span_index = span_index.entered(); let mut callbacks = setup_git_credentials(); callbacks.transfer_progress(move |progress| { quant_recv.update_span_progress( progress.received_objects(), progress.total_objects(), - &span_recv, + &recv_progress, ); quant_index.update_span_progress( progress.indexed_objects(), progress.total_objects(), - &span_index, + &index_progress, ); true }); From e7245ac6208cea747b774d7ae4d136f02eccfdb4 Mon Sep 17 00:00:00 2001 From: Lars Francke Date: Mon, 14 Sep 2026 17:11:35 +0200 Subject: [PATCH 2/2] chore: Bump libssh2-sys to 0.3.3 0.3.2 is yanked, which fails the cargo-deny advisories check. It is reached through git2, so both boil and patchable depend on it. --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 278d32207..0c4c01652 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1074,9 +1074,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libssh2-sys" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c04141a07bb0c0bc461cb657808764de571702a59bc5c726c400ac9a7625e3ab" +checksum = "0f5eb74291e8691cab524a01274a1b1e7742b1a94f29d8b101d8aadc8372c1cd" dependencies = [ "cc", "libc",