Skip to content

Commit 84d2e3a

Browse files
tizz98claude
andauthored
fix(tag): sync Cargo.lock on release bump (unbreak lockfile CI) (#15)
* fix(release): sync Cargo.lock to 0.4.0 (unbreak lockfile CI) The v0.4.0 release commit bumped Cargo.toml but left Cargo.lock pinning ai-meta 0.3.0, so the `lockfile` gate (cargo metadata --locked) went red on main. Resync the workspace entry; registry deps are untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(tag): refresh Cargo.lock on release bump so it can't drift `meta tag` rewrote the version in Cargo.toml but never touched Cargo.lock, which pins the crate's own version. Every bump left the lockfile stale, failing the `lockfile` CI gate and `cargo publish --locked`. For Rust projects with a Cargo.lock, run `cargo update --workspace` (offline first, networked fallback) after the version rewrite, stage the lockfile into the release commit, and hard-fail rather than commit a stale lock. A unit test covers the profile/lockfile gate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent e7c1f89 commit 84d2e3a

2 files changed

Lines changed: 58 additions & 1 deletion

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/commands/tag.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::config::EffectiveConfig;
2+
use crate::profile::ProfileKind;
23
use crate::version::{BumpLevel, Version};
34
use crate::{config, context, output, process, state};
45
use clap::Args;
@@ -108,6 +109,14 @@ pub fn run(args: TagArgs) -> anyhow::Result<i32> {
108109
anyhow::bail!("no version locations were updated — aborting before commit.");
109110
}
110111

112+
// Keep a lockfile that pins the crate's own version in step with the bump.
113+
// A stale Cargo.lock fails the `lockfile` CI gate and `cargo publish
114+
// --locked`, so refresh and stage it as part of the release commit.
115+
if let Some(lock) = sync_lockfile(&root, &cfg)? {
116+
output::ok(format!("{lock} → {next}"));
117+
changed_paths.push(lock);
118+
}
119+
111120
// Commit, tag, push.
112121
git(&root, &format!("git add -- {}", changed_paths.join(" ")))?;
113122
git(
@@ -251,6 +260,36 @@ fn tag_exists(root: &Path, tag: &str) -> bool {
251260
.unwrap_or(false)
252261
}
253262

263+
/// Whether this project has a lockfile that embeds the crate's own version and
264+
/// therefore needs resyncing after a version bump. Only Cargo does: `Cargo.lock`
265+
/// records `ai-meta v0.3.0`, which a bump to `0.4.0` leaves stale.
266+
fn should_sync_cargo_lock(root: &Path, profile: ProfileKind) -> bool {
267+
profile == ProfileKind::Rust && root.join("Cargo.lock").is_file()
268+
}
269+
270+
/// Refresh the lockfile so its pinned crate version matches the freshly-bumped
271+
/// manifest, returning the repo-relative path to stage (or `None` when the
272+
/// project has no such lockfile). Errors when a lockfile exists but cannot be
273+
/// resynced — committing a stale lock would break the release.
274+
fn sync_lockfile(root: &Path, cfg: &EffectiveConfig) -> anyhow::Result<Option<String>> {
275+
if !should_sync_cargo_lock(root, cfg.profile_kind) {
276+
return Ok(None);
277+
}
278+
// `cargo update --workspace` rewrites only the workspace members' own entries
279+
// to match their manifests; registry dependencies are untouched. Prefer the
280+
// offline run (fast, and the lock already resolves), then fall back to a
281+
// networked one before giving up.
282+
for cmd in [
283+
"cargo update --workspace --offline",
284+
"cargo update --workspace",
285+
] {
286+
if matches!(process::run_captured(cmd, root), Ok(o) if o.status == 0) {
287+
return Ok(Some("Cargo.lock".to_string()));
288+
}
289+
}
290+
anyhow::bail!("failed to refresh Cargo.lock — refusing to commit a stale lockfile");
291+
}
292+
254293
#[cfg(test)]
255294
mod tests {
256295
use super::*;
@@ -288,4 +327,22 @@ mod tests {
288327
assert_eq!(n, 1);
289328
assert!(out.contains("\"version\": \"0.4.0\""));
290329
}
330+
331+
#[test]
332+
fn syncs_cargo_lock_only_for_rust_with_a_lockfile() {
333+
let dir = tempfile::tempdir().unwrap();
334+
let root = dir.path();
335+
336+
// No Cargo.lock yet: nothing to sync even on a Rust project.
337+
assert!(!should_sync_cargo_lock(root, ProfileKind::Rust));
338+
339+
std::fs::write(root.join("Cargo.lock"), "").unwrap();
340+
// Rust + a lockfile present: sync it.
341+
assert!(should_sync_cargo_lock(root, ProfileKind::Rust));
342+
// Other profiles have no crate-version-pinning lockfile to touch, even if
343+
// a stray Cargo.lock happens to sit in the tree.
344+
assert!(!should_sync_cargo_lock(root, ProfileKind::TypeScript));
345+
assert!(!should_sync_cargo_lock(root, ProfileKind::Python));
346+
assert!(!should_sync_cargo_lock(root, ProfileKind::Generic));
347+
}
291348
}

0 commit comments

Comments
 (0)