From e79887c0697e65427c3b08e1c95de92d82ebec59 Mon Sep 17 00:00:00 2001 From: BryanFRD Date: Wed, 21 Oct 2026 18:59:22 +0200 Subject: [PATCH 1/2] fix(release): push and verify against the checked-out branch on stable releases --- src/monorepo/release.rs | 10 +++--- src/monorepo/run/context.rs | 66 +++++++++++++++++++++++++++++++++---- 2 files changed, 65 insertions(+), 11 deletions(-) diff --git a/src/monorepo/release.rs b/src/monorepo/release.rs index 2c63131a..9bfdfc6e 100644 --- a/src/monorepo/release.rs +++ b/src/monorepo/release.rs @@ -97,7 +97,10 @@ pub fn release( if attempt < MAX_RELEASE_REGENERATE_ATTEMPTS && crate::git::is_push_rejected_error(&e) => { - warn_stale_attempt(attempt, &config, &e); + let branch = open_repo(&root) + .map(|repo| crate::git::resolve_current_branch(&repo, &config.workspace.branch)) + .unwrap_or_else(|_| config.workspace.branch.clone()); + warn_stale_attempt(attempt, &branch, &e); cleanup_failed_release_attempt(&root, &config, &pre_attempt_tags)?; last_err = Some(e); @@ -129,14 +132,13 @@ fn is_single_shot(config: &Config, dry_run: bool) -> bool { ) } -fn warn_stale_attempt(attempt: usize, config: &Config, e: &anyhow::Error) { +fn warn_stale_attempt(attempt: usize, branch: &str, e: &anyhow::Error) { tracing::warn!(""); tracing::warn!( "{}", format!( "Release attempt {attempt}/{MAX_RELEASE_REGENERATE_ATTEMPTS} \ - pushed onto a stale '{}': {e:#}", - config.workspace.branch, + pushed onto a stale '{branch}': {e:#}", ) .yellow() ); diff --git a/src/monorepo/run/context.rs b/src/monorepo/run/context.rs index ad710f87..72b93b59 100644 --- a/src/monorepo/run/context.rs +++ b/src/monorepo/run/context.rs @@ -86,12 +86,6 @@ impl ReleaseInputs { None => crate::git::build_head_ancestors(&repo).ok(), }; - let target_branch = if prerelease_ctx.is_prerelease() { - current_branch - } else { - config.workspace.branch.clone() - }; - let changed_files = get_changed_files(&repo)?; if !flags.quiet() { log_changed_files(&changed_files); @@ -106,7 +100,7 @@ impl ReleaseInputs { all_tags, tag_index, fallback_ancestors, - target_branch, + target_branch: current_branch, changed_files, }) } @@ -223,3 +217,61 @@ fn log_changed_files(changed_files: &[String]) { } tracing::debug!(""); } + +#[cfg(test)] +mod tests { + use super::*; + + fn git(dir: &Path, args: &[&str]) { + let status = std::process::Command::new("git") + .current_dir(dir) + .args(args) + .env_remove("GIT_DIR") + .env_remove("GIT_WORK_TREE") + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .status() + .expect("git should be on PATH"); + assert!(status.success(), "git {args:?} failed"); + } + + fn target_branch_on(checked_out: &str, workspace_branch: &str) -> String { + let dir = tempfile::tempdir().unwrap(); + git(dir.path(), &["init", "-q", "-b", "main"]); + git(dir.path(), &["config", "user.email", "t@example.com"]); + git(dir.path(), &["config", "user.name", "t"]); + git(dir.path(), &["commit", "-q", "--allow-empty", "-m", "root"]); + git(dir.path(), &["checkout", "-q", "-B", checked_out]); + + let mut config = Config::default(); + config.workspace.branch = workspace_branch.to_string(); + + let flags = RunFlags { + dry_run: true, + verbose: false, + json: true, + release_json: false, + force: false, + draft: false, + shadow: false, + version_commands: false, + }; + let access = RepoAccess { + channel: None, + force_unlock: false, + }; + ReleaseInputs::resolve(dir.path(), &config, flags, access, &mut Timing::new(false)) + .unwrap() + .target_branch + } + + #[test] + fn stable_release_targets_the_checked_out_maintenance_branch() { + assert_eq!(target_branch_on("1.1.x", "main"), "1.1.x"); + } + + #[test] + fn stable_release_targets_workspace_branch_when_on_it() { + assert_eq!(target_branch_on("main", "main"), "main"); + } +} From 6c1afba744c5155a0273498963664d3995d4fc9b Mon Sep 17 00:00:00 2001 From: BryanFRD Date: Sat, 24 Oct 2026 10:15:34 +0200 Subject: [PATCH 2/2] refactor(release): resolve the release branch once before the retry loop --- src/monorepo/release.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/monorepo/release.rs b/src/monorepo/release.rs index 9bfdfc6e..c9edd4cc 100644 --- a/src/monorepo/release.rs +++ b/src/monorepo/release.rs @@ -62,6 +62,7 @@ pub fn release( return Ok(()); } + let branch = crate::git::resolve_current_branch(&open_repo(&root)?, &config.workspace.branch); let mut last_err: Option = None; for attempt in 1..=MAX_RELEASE_REGENERATE_ATTEMPTS { let pre_attempt_tags: std::collections::HashSet = { @@ -97,9 +98,6 @@ pub fn release( if attempt < MAX_RELEASE_REGENERATE_ATTEMPTS && crate::git::is_push_rejected_error(&e) => { - let branch = open_repo(&root) - .map(|repo| crate::git::resolve_current_branch(&repo, &config.workspace.branch)) - .unwrap_or_else(|_| config.workspace.branch.clone()); warn_stale_attempt(attempt, &branch, &e); cleanup_failed_release_attempt(&root, &config, &pre_attempt_tags)?;